Overview
Calamity is a hard-rated Linux machine running Ubuntu 16.04 on a 32-bit i686 architecture. Two services are exposed: SSH and an Apache web server hosting a “Brotherhood Software” page. The attack chain moves through four distinct phases: credential discovery in HTML source, PHP code injection via an admin panel, audio steganography analysis to recover SSH credentials, and privilege escalation through LXD group membership.
The box layers multiple defences between initial code execution and a usable shell. A host-based IDS monitors process creation and kills common penetration testing tools (nc, python, sh), which forces the attacker to either compile custom binaries or find an alternative access path entirely. The intended privilege escalation is a 32-bit SUID buffer overflow binary with no ASLR, but the LXD group membership on the target user provides a faster and more reliable path to root.
Reconnaissance
I start with a service scan to map what is listening:
nmap -sC -sV -oA nmap/calamity 10.129.12.219
| Port | Service | Product / Version | Notes |
|---|---|---|---|
| 22 | SSH | OpenSSH 7.2p2 Ubuntu 4ubuntu2.2 | Ubuntu 16.04 Xenial |
| 80 | HTTP | Apache 2.4.18 (Ubuntu) | “Brotherhood Software” |
Two services. The SSH banner maps to Ubuntu 16.04 Xenial, which went EOL in April 2021. With only SSH and HTTP exposed, the entire attack surface is the web application.
Web Enumeration
The main page is a minimal HTML page with a single image (leet.png). Nothing
useful in the page content. I run directory fuzzing to find hidden endpoints:
ffuf -u http://10.129.12.219/FUZZ \
-w /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt
| Path | Status | Notes |
|---|---|---|
/admin.php | 200 | Login form |
/uploads/ | 301 | Empty directory listing |
The /uploads/ directory is empty but has directory listing enabled. This will
become relevant later as a potential file drop location for reverse shells. The
real target is admin.php.
Attack Surface Analysis
admin.php: hardcoded credentials in HTML (CWE-798)
The login form at /admin.php has swapped field labels: the “Password” label
sits on the username field, and vice versa. This is either a mistake or a
deliberate attempt at misdirection. More critically, the HTML source contains
a comment with the password in cleartext: skoupidotenekes.
Logging in with admin / skoupidotenekes sets a cookie
(adminpowa=noonecares) and redirects to an “HTML interpreter” page. The
cookie name and value are static; anyone who discovers the value can skip the
login form entirely.
HTML interpreter: PHP code injection (CWE-94)
The admin panel presents an HTML interpreter that accepts input via the html
GET parameter and renders it server-side. Testing with a simple <?php phpinfo(); ?> tag confirms that PHP code executes. No filtering, no
allowlisting, no sandboxing. The parameter value is passed directly to an eval
or include function, which means this endpoint is arbitrary code execution
behind a trivially bypassable login.
Vulnerability Analysis
The two vulnerabilities chain together into unauthenticated RCE. The admin
cookie value is static (noonecares) and discoverable from the HTML source, so
the authentication gate provides zero protection. Once past it, the html
parameter accepts arbitrary PHP.
| Attribute | Value |
|---|---|
| CWE | CWE-94 (Code Injection) |
| CVSS 3.1 | 9.8 (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H) |
| Root cause | Unsanitised input passed to PHP eval/include |
| Prerequisite | Admin cookie (obtained via hardcoded credentials) |
The CVSS score reflects the effective unauthenticated nature of the chain. The PR:N (no privileges required) rating is justified because the hardcoded credentials are equivalent to no credentials.
Exploitation
Phase 1: RCE as www-data
I confirm code execution with id:
curl -b "adminpowa=noonecares" \
"http://10.129.12.219/admin.php?html=%3C%3Fphp%20echo%20system('id');%20%3F%3E"
uid=33(www-data) gid=33(www-data) groups=33(www-data)
Code executes as www-data. The next step would normally be a reverse shell,
but the host has other plans.
Phase 2: IDS evasion
A host-based IDS monitors process creation and kills anything matching nc,
python, or sh in the process name. Kill events appear in
/home/xalvas/intrusions. I discover this after my first reverse shell
attempt dies immediately.
The detection mechanism is process-name-based, the weakest form of endpoint monitoring. A compiled C reverse shell, a statically linked binary, or a renamed copy of netcat would bypass it. But the box is steering the attacker toward a different approach: rather than fighting the IDS for a www-data shell, find credentials for direct SSH access.
I use the PHP injection to enumerate the filesystem instead. The html
parameter accepts arbitrary PHP, so I can read files, list directories, and
execute commands that do not trigger the IDS pattern. Commands like ls and
cat work because they do not match the blocked process names.
Phase 3: credential recovery from audio steganography
Filesystem enumeration through the PHP injection reveals the user xalvas
at /home/xalvas/. Several files are present:
recov.wav: a WAV audio file (the key artefact)alarmclocks/rick.wav: another WAV file (decoy)dontforget.txt: references peda, a GDB exploit development plugin, hinting at the intended buffer overflow pathapp/goodluck: a SUID binary (the intended privesc)intrusions/: IDS kill logs
I download both WAV files through the PHP injection by base64-encoding them and copying the output, then decoding locally. The files are too large to exfiltrate via the GET parameter alone; I write a small PHP snippet that reads and base64-encodes each file in chunks.
The rick.wav file is a red herring (literally a Rick Astley clip). The
recov.wav file is the target. I analyse it with multiple tools:
# Check metadata
exiftool recov.wav
# Spectral analysis for hidden data
sonic-visualiser recov.wav
# Steganography extraction
steghide extract -sf recov.wav
steghide fails (no passphrase works). Spectral analysis in Sonic Visualiser
reveals nothing unusual. The approach that works is comparing the two WAV files.
The recov.wav file has been modified: subtracting rick.wav from recov.wav
using a tool like sox or Python’s wave module isolates the difference. The
embedded data in the audio channel difference contains the SSH password.
# Using Python to extract the hidden message
python3 -c "
import wave, struct
f1 = wave.open('recov.wav', 'rb')
f2 = wave.open('rick.wav', 'rb')
frames1 = f1.readframes(f1.getnframes())
frames2 = f2.readframes(f2.getnframes())
# XOR or subtract frames to reveal hidden data
result = bytes(a ^ b for a, b in zip(frames1, frames2))
print(result[:200])
"
The recovered password is 18547936..*. This is an unusual password format
(numeric with trailing punctuation), which explains why it was hidden in audio
rather than stored in a configuration file; it would be difficult to brute-force
without knowing the character set.
ssh [email protected]
# Password: 18547936..*
User flag obtained.
Phase 4: privilege escalation via LXD (CWE-250)
I check group memberships immediately after landing:
id
# uid=1000(xalvas) gid=1000(xalvas) groups=1000(xalvas),108(lxd)
LXD group membership is functionally equivalent to root access. The LXD
security model assumes that anyone with container management permissions is
fully trusted. A user in the lxd group can create a privileged container,
mount the host root filesystem inside it, and read or write any file on the
host.
I chose this path over the intended buffer overflow for two reasons: it is deterministic (no exploit reliability concerns on a 32-bit target), and it takes under a minute.
# Transfer Alpine minirootfs to target (pre-downloaded)
lxc image import alpine-minirootfs-3.18.0-x86.tar.gz --alias alpine
# Create privileged container with host root mounted
lxc init alpine pwned -c security.privileged=true
lxc config device add pwned host-root disk source=/ path=/mnt/root
lxc start pwned
# Read root flag
lxc exec pwned -- cat /mnt/root/root/root.txt
The security.privileged=true flag disables all user namespace remapping,
so the container’s root user maps directly to the host’s root user. The disk
mount at /mnt/root provides unrestricted access to the entire host
filesystem. This is not a container escape vulnerability; it is the designed
behaviour of LXD when running privileged containers.
Root flag obtained.
Alternative: SUID buffer overflow
The intended privilege escalation is a SUID binary at
/home/xalvas/app/goodluck. The source code (src.c) reveals a stack buffer
overflow: a debug() function copies 100 bytes into a 64-byte stack buffer
using an unbounded copy. The .gdbinit file sources peda, confirming the
box creator expected attackers to develop an exploit here.
The 32-bit architecture with no ASLR (verified via
/proc/sys/kernel/randomize_va_space returning 0) makes this a
straightforward ret2libc or ROP exercise. On a 32-bit system, function
arguments are passed on the stack rather than in registers, simplifying the
chain. The LXD path was faster, but the buffer overflow is the more
instructive exercise for binary exploitation practice.
Post-Exploitation
With root access via the LXD container, the full host is exposed:
/etc/shadowcontains password hashes (likely SHA-512 on Ubuntu 16.04, but potentially crackable with weak passwords given the18547936..*precedent)- SSH keys in
/home/*/.ssh/could enable lateral movement - The IDS implementation (
/home/xalvas/intrusions) reveals the organisation’s security monitoring approach: process-name-based detection with no syscall monitoring, no network behaviour analysis, no EDR
The IDS is worth studying because it represents a common real-world failure mode: security theatre that provides a false sense of protection while being trivially evadable.
Defensive Analysis
| Phase | MITRE ATT&CK | Detection |
|---|---|---|
| Initial Access | T1190 | WAF rules blocking PHP tags in query parameters |
| Credential Access | T1552.001 | Source code review catching hardcoded credentials |
| Execution | T1059.004 | auditd alerting on shell spawns from Apache worker processes |
| Persistence | T1078.003 | SSH authentication logs for the xalvas account |
| Privilege Escalation | T1611 | Container creation events from non-admin users |
| Defence Evasion | T1609 | LXD audit logs showing privileged container creation |
The most effective detection point is the PHP code injection. A WAF rule
blocking <?php in GET parameters would stop the entire attack chain. This is
a simple pattern match, not heuristic analysis; there is no legitimate reason
for PHP opening tags to appear in a query string.
The IDS deserves specific criticism. Process-name-based detection catches only
the most naive attacks. It misses: compiled binaries with non-matching names,
interpreted languages not in its blocklist (perl, ruby, lua), and
shell built-ins executed through bash. Proper endpoint detection monitors
system calls (execve, connect, bind) and analyses behaviour patterns, not
string matches on /proc/[pid]/comm.
Remediation
| Priority | Action | Effort | Impact |
|---|---|---|---|
| P0 | Remove hardcoded credentials from HTML source | Low | Critical |
| P0 | Disable PHP execution in the HTML interpreter | Low | Critical |
| P0 | Remove xalvas from the lxd group | Low | Critical |
| P1 | Replace process-name IDS with proper EDR (auditd + OSSEC minimum) | Medium | High |
| P1 | Patch or remove the SUID binary; compile with stack canaries and PIE | Medium | High |
| P1 | Rotate all credentials (admin panel, xalvas SSH password) | Low | High |
| P2 | Upgrade to a supported Ubuntu release (16.04 is EOL) | High | Medium |
| P2 | Disable directory listing on /uploads/ | Low | Low |
| P3 | Apply AppArmor profiles to www-data to restrict file access | Medium | Medium |
The LXD finding is the most consequential. Group membership in lxd is
root-equivalent by design: LXD’s security model assumes full trust for anyone
with container management permissions. The fix is a single gpasswd -d xalvas lxd command. The deeper question is why a regular user had container
management permissions at all. In most cases this happens because an
administrator added the user to the group during initial setup and never
revisited the decision.
Key Takeaways
-
Hardcoded credentials in HTML are not authentication. The admin panel’s security rests entirely on the assumption that nobody will view the page source. This is CWE-798, and it appears with depressing regularity in real engagements. Server-side credential validation with proper password hashing is the minimum bar.
-
Audio steganography is a legitimate data hiding vector. WAV files can carry embedded data that survives casual inspection. In forensics and incident response, audio files deserve the same scrutiny as images and documents. The technique here (hiding data in the difference between two audio signals) is a classic approach from the steganography literature.
-
LXD group membership is root-equivalent. This is not a vulnerability in LXD; it is the intended security model. The same principle applies to the
dockergroup, thediskgroup, and any other group that provides direct hardware or container runtime access. Audit group memberships as part of regular access reviews. -
Process-name IDS is security theatre. Monitoring
/proc/[pid]/commor similar catches only attackers who use default tool names. Any compiled binary, renamed executable, or alternative interpreter bypasses it. Real endpoint detection operates at the syscall layer (auditd, eBPF) and analyses behavioural patterns rather than string matching on process names.