Web Application Penetration Test: MetaTwo
Target: metapress.htb web application and host (Hack The Box lab machine, publicly available)
Assessment type: Black-box external web application penetration test, progressing to full host compromise
Report date: 14 October 2026
Prepared by: Magnus Ensted Danielsen
Pen-tester IP <redacted>
Portfolio sample. This report documents an authorised assessment of the publicly available Hack The Box "MetaTwo" training machine, written to professional engagement-report standard to demonstrate testing methodology and reporting. It contains no real client, customer or confidential data.
Scope
| Item | Detail |
|---|---|
| In scope | The metapress.htb web application and the Linux host serving it, assessed from the public internet with no credentials provided. |
| Out of scope | Denial-of-service testing and social engineering. |
| Objective | Assess the security of the public-facing web application and determine the highest level of access an unauthenticated attacker could reach. |
The assessment was conducted as a black-box external test. Testing began from the position of an anonymous internet user with no accounts, credentials or prior knowledge, modelling a real attacker who finds the site and attempts to compromise it. Because the engagement objective was to determine maximum impact, the assessment continued past the initial web foothold onto the host itself, up to full root access, to show how far a single public vulnerability could be taken.
Executive Summary
Starting from nothing more than the public website, with no password or account of any kind, the assessment was able to take complete control of the server that runs the site, reaching the highest level of access a Linux system has (the root account). At that level an attacker can read, change or delete anything on the server, including all website data and any data belonging to its users.
The entry point was the public website itself. A booking feature on the site used an out-of-date add-on with a well-known flaw, which let anyone on the internet pull stored login details straight out of the site's database without logging in first. One of those passwords was weak and was recovered in seconds using a standard password-guessing list.
With that single account, a second known flaw in the out-of-date website software allowed the server's internal files to be read. Those files turned out to contain more passwords, stored in plain, readable text. One of them had been reused as the login password for the server itself, which gave a direct way in. Once on the server, a stored password vault belonging to a user was protected only by another weak password, and inside it was the master administrator password.
In business terms, every step relied on software that was months or years out of date and on passwords that were either weak or written down in plain text where the website could read them. None of it required advanced skill. The most important actions are to keep the website software and its add-ons updated, and to stop storing passwords in plain text in files the website can reach. A prioritised list of fixes follows immediately below, with full technical detail for each issue in the sections after it.
Findings Summary
| # | Finding | Severity |
|---|---|---|
| 1 | Unauthenticated SQL Injection in BookingPress Plugin (CVE-2022-0739) | High |
| 2 | Authenticated XXE Arbitrary File Read in Outdated WordPress Core (CVE-2021-29447) | High |
| 3 | Plaintext Credentials Stored in Web-Readable Configuration and Application Files | High |
| 4 | Root Password Recoverable from a User-Readable Password Store with a Weak Passphrase | High |
| 5 | Password Reuse Across Services | Medium |
| 6 | Weak, Dictionary-Crackable Passwords | Medium |
Remediation Summary
Actions are ordered by priority, reflecting the combination of severity and ease of exploitation. The first two items remove the public entry points and should be treated as the immediate priority.
| Priority | Finding | Recommended action | Effort |
|---|---|---|---|
| 1 | Finding 1 | Update or remove the BookingPress plugin to a version that fixes CVE-2022-0739, and audit all installed plugins for known vulnerabilities. | Low |
| 2 | Finding 2 | Update WordPress core to a patched release (5.7.1 or later) to close the media-library XXE, and establish a routine patching schedule. | Low |
| 3 | Finding 3 | Remove plaintext credentials from configuration and application files, store secrets outside the web root in a secrets manager or environment variables, and rotate every exposed credential. | Medium |
| 4 | Finding 4 | Stop storing privileged credentials, especially the root password, in a user-accessible password store. Protect any such store with a strong passphrase and restrict its file permissions. | Medium |
| 5 | Finding 5 | Ensure every account and service uses a unique password, so one leaked credential cannot unlock another system. | Low |
| 6 | Finding 6 | Enforce a strong, breach-screened password policy with a high minimum length across all accounts and credential stores. | Medium |
Recommended sequence. Close the two public vulnerabilities first (items 1 and 2), because they are the unauthenticated entry point and are trivially exploitable with public tools. Then remove the plaintext credentials and rotate them (item 3), which breaks the lateral movement. Address the password and credential-store weaknesses (items 4, 5, 6) in parallel. After remediation, a focused retest is recommended to confirm the full chain can no longer be reproduced.
Attack Chain: High-Level Overview
This a high level overview of how the assessment moved from the public website to full control of the server. Each step used only what the previous step provided.
- The public website ran an out-of-date booking add-on with a known flaw. That flaw let anyone, without logging in, pull stored usernames and scrambled passwords out of the site's database.
- One of those scrambled passwords was weak and was unscrambled in seconds, giving a working website login.
- Logged in, a second known flaw in the out-of-date website software allowed internal server files to be read. These files contained more passwords, stored in plain text.
- One of those plaintext passwords had been reused as the login for the server itself, giving a direct remote shell on the machine.
- On the server, a user's personal password vault was protected by another weak password. Unlocking it revealed the master administrator password, which was used to take full control.
The themes behind these steps, out-of-date software, passwords stored in plain text, reused passwords and weak passwords, are documented as formal findings later in this report.
Attack Chain: Technical Detail
This section goes more technically indepth showcasing the assessments attack chain. Each step cross-references the formal finding that documents it in full, including commands and evidence.
Reconnaissance. An nmap sweep returned three open ports: FTP (21), SSH (22) and HTTP (80, nginx 1.18.0). The web server redirected to the virtual host metapress.htb, which was added to /etc/hosts. The site was a WordPress 5.6.2 installation. WPScan identified the outdated core, the twentytwentyone theme, the users admin and manager, and an enabled XML-RPC endpoint. The events page hosted a booking form backed by the BookingPress plugin.
Step 1, unauthenticated SQL injection (Findings 1 and 6). The booking form submitted requests to admin-ajax.php using the BookingPress action bookingpress_front_save_appointment_booking. The installed BookingPress version was vulnerable to CVE-2022-0739, an unauthenticated SQL injection. Using a _wpnonce value taken from the /events/ page source, the injection dumped the WordPress wp_users table, yielding the phpass password hashes for admin and manager. The manager hash was cracked offline with hashcat (mode 400, phpass) against rockyou.txt in seconds.
Step 2, authenticated file read via XXE (Findings 2 and 3). With the manager credentials, the WordPress 5.6.2 media library was exploited through CVE-2021-29447, an authenticated XML external entity (XXE) vulnerability triggered by uploading a crafted WAV file. This provided arbitrary file read as the web server user. Reading /etc/passwd confirmed jnelson as the only interactive user, and reading the nginx site configuration located the web root. The WordPress wp-config.php file was then read, exposing database and FTP credentials in plain text.
Step 3, lateral movement through reused credentials (Findings 3, 5 and 6). The FTP credentials from wp-config.php allowed access to the WordPress file store, where a PHPMailer script contained a plaintext SMTP password for jnelson. That same password had been reused for jnelson's system account, so it worked directly over SSH, giving an interactive shell on the host.
Step 4, privilege escalation to root (Findings 4 and 6). In jnelson's home directory, a Passpie password store (.passpie) held credentials for jnelson and root, encrypted with a GPG key. The private key was extracted and its passphrase recovered offline with gpg2john and John the Ripper against rockyou.txt in a few seconds. Exporting the Passpie store with the recovered passphrase revealed the root account's password in plain text, which was used with su to become root, completing full host compromise.
Finding 1: Unauthenticated SQL Injection in BookingPress Plugin (CVE-2022-0739)
Severity: High
CWE: CWE-89 (Improper Neutralization of Special Elements used in an SQL Command)
CVSS 3.1: 7.5 (High), CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N (the public CVE is scored 9.8, on the basis that the same flaw can also modify data)
Summary
The website ran a version of the BookingPress plugin vulnerable to CVE-2022-0739, an unauthenticated SQL injection reachable through the admin-ajax.php endpoint. User-supplied input in the plugin's appointment-booking action was passed into a database query without proper sanitisation. Because the endpoint requires no authentication, any anonymous internet user could read arbitrary data from the WordPress database, including the user table and its stored password hashes.
Impact
This was the unauthenticated entry point for the entire compromise. It allowed the password hashes of the admin and manager WordPress accounts to be extracted with no login at all. In a production setting the same flaw exposes every record the web database holds, which commonly includes customer data, and the public CVE notes it can also be used to modify database contents. Here it directly provided the credentials used to progress the attack.
Remediation
- Update the BookingPress plugin to a version that fixes CVE-2022-0739, or remove it if it is not needed.
- Audit all installed plugins and themes for known vulnerabilities and keep them patched, since plugins are the most common source of WordPress compromise.
- Apply least privilege to the application's database account, so a single injection cannot reach data the application does not need.
References
- CWE-89: https://cwe.mitre.org/data/definitions/89.html
- CVE-2022-0739: https://nvd.nist.gov/vuln/detail/CVE-2022-0739
- OWASP SQL Injection: https://owasp.org/www-community/attacks/SQL_Injection
Technical reproduction and evidence
The booking form posts to admin-ajax.php with the action bookingpress_front_save_appointment_booking. Using a valid _wpnonce taken from the /events/ page source, a public proof-of-concept for CVE-2022-0739 dumped the WordPress user table:
$ python3 bookingpress_exploit.py -u http://metapress.htb/wp-admin/admin-ajax.php -n <wpnonce>
-- db fingerprint: MariaDB
-- users: 2
|admin|[email protected]|<redacted phpass hash>|
|manager|[email protected]|<redacted phpass hash>|
The manager hash was then cracked offline (see Finding 6):
$ hashcat -m 400 manager.hash rockyou.txt # phpass
Status...........: Cracked
Finding 2: Authenticated XXE Arbitrary File Read in Outdated WordPress Core (CVE-2021-29447)
Severity: High
CWE: CWE-611 (Improper Restriction of XML External Entity Reference)
CVSS 3.1: 7.7 (High), CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N
Summary
The site ran WordPress 5.6.2, which is vulnerable to CVE-2021-29447, an XML external entity (XXE) flaw in the media library when running on PHP 8. By uploading a crafted WAV audio file whose metadata contains an external entity reference, an authenticated user can make the server fetch an attacker-controlled definition file and read the contents of files on the server. The flaw requires a logged-in user, a requirement already satisfied by the credentials recovered in Finding 1.
Impact
The vulnerability provided arbitrary file read as the web server user. This was used to read /etc/passwd, the nginx configuration and the WordPress wp-config.php file. Arbitrary file read on a web server is high impact on its own, because configuration files routinely contain database and service credentials. That is exactly what happened here, and it is what enabled the move onto the host.
Remediation
- Update WordPress core to a patched version (5.7.1 or later) that fixes the media-library XXE.
- Establish a routine patching schedule for WordPress core, plugins and themes, so known vulnerabilities are closed promptly.
- Restrict the web server's file-system access so it cannot read sensitive files outside what the application genuinely needs.
References
- CWE-611: https://cwe.mitre.org/data/definitions/611.html
- CVE-2021-29447: https://nvd.nist.gov/vuln/detail/CVE-2021-29447
Technical reproduction and evidence
Using the manager credentials from Finding 1, a public proof-of-concept for CVE-2021-29447 uploaded a crafted WAV file and read server files. Reading /etc/passwd confirmed jnelson as the only interactive user:
$ python3 CVE-2021-29447.py --target http://metapress.htb --user manager --password '<redacted>' --file /etc/passwd
root:x:0:0:root:/root:/bin/bash
...
jnelson:x:1000:1000:jnelson,,,:/home/jnelson:/bin/bash
...
The same technique read /var/www/metapress.htb/blog/wp-config.php (see Finding 3).
Finding 3: Plaintext Credentials Stored in Web-Readable Configuration and Application Files
Severity: High
CWE: CWE-312 (Cleartext Storage of Sensitive Information), CWE-522 (Insufficiently Protected Credentials)
CVSS 3.1: 7.7 (High), CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N
Summary
Several sets of credentials were stored in plain text inside files the web server could read. The WordPress wp-config.php file contained the database password and a set of FTP credentials. A PHPMailer script reachable over FTP contained a plaintext SMTP password for the jnelson account. Once the arbitrary file read in Finding 2 was available, all of these were recovered directly.
Impact
These plaintext credentials turned a file-read vulnerability into full lateral movement. The FTP credentials opened the application file store, and the SMTP password in the mailer script, reused as a system password (Finding 5), provided an interactive shell on the host. Storing credentials in plain text in web-reachable locations means that any file-disclosure flaw, of which there are many classes, immediately becomes a credential breach.
Remediation
- Remove plaintext secrets from configuration and application files. Store them in environment variables or a dedicated secrets manager, outside the web root where possible.
- Rotate every credential that was exposed, including the database, FTP and SMTP passwords.
- Avoid storing service credentials in application code, and review the codebase and configuration for any other embedded secrets.
References
- CWE-312: https://cwe.mitre.org/data/definitions/312.html
- CWE-522: https://cwe.mitre.org/data/definitions/522.html
Technical reproduction and evidence
Reading wp-config.php through the XXE in Finding 2 exposed the database and FTP credentials (values redacted):
define( 'DB_NAME', 'blog' );
define( 'DB_USER', 'blog' );
define( 'DB_PASSWORD', '<redacted>' );
define( 'FS_METHOD', 'ftpext' );
define( 'FTP_USER', 'metapress.htb' );
define( 'FTP_PASS', '<redacted>' );
define( 'FTP_HOST', 'ftp.metapress.htb' );
Logging into FTP with those credentials revealed a PHPMailer script holding a plaintext SMTP password for jnelson:
$mail->Host = "mail.metapress.htb";
$mail->Username = "[email protected]";
$mail->Password = "<redacted>";
Finding 4: Root Password Recoverable from a User-Readable Password Store with a Weak Passphrase
Severity: High
CWE: CWE-522 (Insufficiently Protected Credentials), CWE-326 (Inadequate Encryption Strength)
CVSS 3.1: 7.8 (High), CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
Summary
The jnelson user's home directory contained a Passpie password store (.passpie), which keeps credentials encrypted under a GPG key. The store held entries for both jnelson and root. The GPG private key that protects the store was readable by the jnelson account and was itself protected only by a weak passphrase. Recovering that passphrase decrypted the store and exposed the root account's password.
Impact
This was the privilege-escalation step from a normal user to full root control of the host. Storing the root password in a user-accessible store, protected by a crackable passphrase, means that compromise of that single unprivileged user leads directly to complete control of the system. At root level an attacker can read or destroy all data, install persistence, and pivot further into any connected environment.
Remediation
- Do not store privileged credentials, especially the root password, in a user-accessible password store. Administrative access should use dedicated, audited mechanisms.
- Protect any password store with a strong passphrase and restrict file permissions so the encrypted material is not readable by other users or processes.
- Rotate the root password and any other credential held in the store.
References
- CWE-522: https://cwe.mitre.org/data/definitions/522.html
- CWE-326: https://cwe.mitre.org/data/definitions/326.html
Technical reproduction and evidence
The GPG private key was extracted from the Passpie store and its passphrase recovered offline with gpg2john and John the Ripper:
$ gpg2john private.asc > gpg.hash
$ john gpg.hash --wordlist=rockyou.txt --format=gpg
<redacted> (Passpie)
Status: Cracked
With the passphrase, the store was exported, revealing the root password in plain text, which was used to switch to root:
$ passpie export creds.txt # entered the recovered passphrase
# creds.txt contained:
# login: root password: <redacted>
# login: jnelson password: <redacted>
$ su root # using the recovered root password
root@meta2:/home/jnelson#
Finding 5: Password Reuse Across Services
Severity: Medium
CWE: CWE-522 (Insufficiently Protected Credentials)
CVSS 3.1: 6.3 (Medium), CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:N
Summary
The SMTP password stored for the jnelson mail account in the PHPMailer script was the same password used for the jnelson system account. A credential recovered from one service therefore unlocked a completely different and more valuable one, in this case interactive SSH access to the host.
Impact
Password reuse removed any barrier between the application-level credential leak and access to the host. Even if the other weaknesses had been partially addressed, reuse means a single leaked password can cascade across systems. It is one of the most common ways that a limited foothold becomes a full host compromise.
Remediation
- Use a unique password for every account and service, so a credential exposed in one place cannot be used anywhere else.
- Use a managed password solution to make unique, random passwords practical to maintain.
- Monitor for the reuse of known-leaked credentials across accounts.
References
- CWE-522: https://cwe.mitre.org/data/definitions/522.html
- OWASP Credential Stuffing: https://owasp.org/www-community/attacks/Credential_stuffing
Technical reproduction and evidence
The SMTP password recovered from the mailer script in Finding 3 was tested against SSH for the same username and succeeded:
$ ssh [email protected] # using the SMTP password from the mailer script
jnelson@meta2:~$ id
uid=1000(jnelson) gid=1000(jnelson) groups=1000(jnelson)
Finding 6: Weak, Dictionary-Crackable Passwords
Severity: Medium
CWE: CWE-521 (Weak Password Requirements)
CVSS 3.1: 5.9 (Medium), CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N
Summary
Multiple secrets in the environment were weak enough to be recovered almost instantly with the standard public rockyou.txt wordlist. These included the manager WordPress password and the passphrase protecting the GPG key on the Passpie store. In both cases the surrounding control, a password hash and an encrypted key store, would have offered meaningful protection if the underlying password had been strong.
Impact
Weak passwords removed the effort from two separate steps in the chain, the initial WordPress login and the privilege escalation to root. Strong, high-entropy passwords in those two places would have left the attacker with material that was computationally infeasible to crack, which would likely have stopped the chain at those points.
Remediation
- Enforce a strong password policy with a high minimum length (15 or more characters) and screening against breached-password and dictionary lists.
- Apply the same standard to passphrases protecting key material and password stores, not only to user login passwords.
References
- CWE-521: https://cwe.mitre.org/data/definitions/521.html
- NIST SP 800-63B, Digital Identity Guidelines (password guidance)
Technical reproduction and evidence
Both the WordPress manager hash and the GPG passphrase fell to rockyou.txt in seconds:
$ hashcat -m 400 manager.hash rockyou.txt # phpass, WordPress
Status...........: Cracked
$ john gpg.hash --wordlist=rockyou.txt --format=gpg # Passpie GPG key
Status: Cracked