Skip to content
Back to all posts

HTB: TenTen

· 18 min medium Linux TenTen

A WordPress Job Manager plugin leaks uploaded file names through predictable post IDs, revealing a steganographic image that hides an encrypted SSH key. A misconfigured sudo rule on /bin/fuckin completes the chain to root.

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
PortServiceProduct / VersionNotes
22SSHOpenSSH 7.2p2 Ubuntu 4ubuntu2.1Ubuntu 16.04 banner
80HTTPApache 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:

WeaknessCWEDescription
Post ID enumerationCWE-200 (Exposure of Sensitive Information)Sequential IDs expose uploaded file names to unauthenticated users
Steganographic credential storageCWE-312 (Cleartext Storage of Sensitive Information)SSH private key embedded in a publicly accessible image
Insecure sudo configurationCWE-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

PhaseMITRE ATT&CKDetection
ReconnaissanceT1595.002 Active ScanningSequential requests to /?p=1 through /?p=25
Resource DevelopmentT1588.004 Digital CertificatesSSH key extracted from steganographic image
Initial AccessT1078.003 Valid Accounts: LocalSSH login with key-based auth from external IP
Privilege EscalationT1548.003 Sudo and Sudo Cachingsudo /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

PriorityActionEffortImpact
P0Remove /bin/fuckin from sudoers; audit all sudo rulesLowCritical
P0Rotate takis’s SSH key; revoke the compromised keyLowCritical
P0Delete HackerAccessGranted.jpg from uploadsLowCritical
P1Disable direct post ID access for non-public typesMediumHigh
P1Remove takis from the lxd groupLowHigh
P1Migrate SSH keys to OpenSSH format (bcrypt KDF)LowHigh
P2Restrict WordPress author enumerationLowMedium
P2Set upload directory permissions to prevent browsingLowMedium
P3Upgrade to Ubuntu 22.04+ and current WordPressHighHigh

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

  1. 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=1 through /?p=50 on any WordPress target. The defence is restricting /?p=N access for non-public post types at the plugin or theme level, since WordPress itself will not change this behaviour.

  2. 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.

  3. 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.