1Por qué importa
Cuando piensas en atacar un programa, imaginas un fallo en su código. Pero mucha de la
escalada real en Linux no toca el código: toca lo que el programa hereda del entorno en
que arranca. Un script de despliegue que llama a tar por su nombre confía en
que el tar que se ejecute sea el del sistema. Esa confianza vive enteramente
en el PATH, una variable heredada. Si alguien puede colocar un
tar propio en un directorio que aparece antes en ese PATH, el
script ejecutará su código, con la identidad del script, y nadie habrá explotado nada.
El otro lado de la moneda es lo que el proceso revela. El kernel mantiene un sistema
de archivos virtual, /proc, con una carpeta por proceso. Ahí está su línea de
comandos completa, su entorno completo y la lista de sus descriptores de archivo. Si el
proceso recibió una contraseña como argumento o como variable de entorno, esa contraseña
está en /proc, en texto plano, mientras el proceso viva. Entender qué filtra
/proc es entender por qué "pasar el secreto por variable de entorno" no es
tan privado como parece.
No estás aprendiendo trucos de /proc. Estás aprendiendo que un proceso
tiene dos superficies que casi nadie mira: lo que hereda (y que decide qué ejecuta) y
lo que publica sobre sí mismo. Cerrar las dos es endurecer el proceso antes de que
exista un atacante.
2Conceptos
- Proceso / PID: una ejecución en curso y su número identificador.
- /proc: sistema de archivos virtual del kernel; una carpeta por proceso.
- /proc/<pid>/environ: las variables de entorno con las que arrancó el proceso.
- /proc/<pid>/cmdline: su línea de comandos exacta, argumentos incluidos.
- /proc/<pid>/fd: los descriptores de archivo abiertos (qué archivos y sockets usa).
- PATH: lista ordenada de directorios donde la shell busca un comando por nombre.
- Variable de entorno: un par nombre=valor que un proceso hereda de su padre.
- LD_PRELOAD: variable que fuerza a cargar una biblioteca propia antes que las del sistema.
3Explicación profunda
/proc no está en el disco: lo genera el kernel al vuelo. Cada proceso vivo
tiene un directorio /proc/<pid> con archivos que son en realidad ventanas
al estado del proceso. cmdline te da los argumentos con los que se lanzó;
environ, su entorno; maps, las regiones de memoria y las
bibliotecas cargadas; fd/, enlaces a cada archivo o socket abierto;
exe, un enlace al binario en ejecución. Nada de esto es un fallo: es
introspección legítima, la base de herramientas como ps, lsof o
top. El problema de seguridad aparece por quién puede leerlo.
Por defecto puedes leer el /proc de tus propios procesos, y algunos
campos de los ajenos. En un sistema donde varios usuarios comparten máquina, o donde una
aplicación comprometida corre junto a otras, esto importa: si un proceso recibió un secreto
por línea de comandos (mysql -pMiClave) o por entorno, ese secreto es legible
para quien pueda mirar su /proc. Por eso pasar contraseñas como argumento está
prohibido en cualquier guía seria, y por eso pasar secretos por variable de entorno, aunque
es mejor que el argumento, tampoco es privado frente a quien comparte la máquina con los
privilegios adecuados.
La segunda mitad de la lección es la herencia que decide qué se ejecuta. Cuando
escribes tar sin ruta, la shell recorre los directorios del PATH
en orden y ejecuta el primer tar que encuentra. El orden es todo. Si el
PATH es /opt/util:/usr/bin y /opt/util es escribible
por un usuario cualquiera, ese usuario planta un /opt/util/tar con su código y
lo ejecutará cualquier proceso que llame a tar por su nombre y tenga ese
PATH. Es la razón por la que un directorio world-writable dentro del
PATH es tan grave: convierte "escribir un archivo" en "ejecutar código en otro
proceso". El clásico peligroso es el . (directorio actual) en el
PATH: hace que cualquier carpeta en la que entres pueda contener un binario que
suplante un comando común.
El entorno también secuestra la ejecución sin tocar el PATH. El enlazador
dinámico lee variables como LD_PRELOAD y LD_LIBRARY_PATH. Con
LD_PRELOAD apuntando a una biblioteca propia, esa biblioteca se carga
antes que las del sistema y puede sustituir funciones estándar: el programa cree que
llama a getuid() del sistema y llama a la tuya. El kernel protege el caso más
grave —ignora LD_PRELOAD para binarios SUID— pero en procesos normales que
heredan un entorno controlado por el atacante, es una vía directa a ejecutar su código dentro
del proceso. La conclusión de diseño es dura y simple: el entorno de un proceso privilegiado
es parte de su superficie de ataque, y hay que fijarlo, no heredarlo.
4Ejemplo sencillo
# Un cocinero que pide "el cuchillo" sin mirar de cuál se fía El cocinero (proceso) grita "traedme el cuchillo" (tar, cp, id...). Un ayudante recorre los cajones EN ORDEN (el PATH) y le trae el primero que encuentre con ese nombre. Si el primer cajon es de acceso publico (directorio world-writable en el PATH), cualquiera deja ahi un "cuchillo" propio. El cocinero lo usara sin darse cuenta, creyendo que es el de siempre. # Y una libreta que el cocinero deja abierta El cocinero anota en una libreta (environ) las claves de la caja fuerte que le dieron al empezar. Cualquiera que pueda asomarse a su puesto (/proc) lee esas claves mientras el cocinero trabaja.
5Ejemplo técnico
Mira lo que /proc filtra de un proceso y cómo se resuelve un comando por el
PATH:
| Ruta | Qué contiene | Riesgo |
|---|---|---|
/proc/<pid>/cmdline | Argumentos exactos del proceso | Un secreto pasado como argumento queda a la vista. |
/proc/<pid>/environ | Variables de entorno de arranque | Tokens y claves puestos en el entorno son legibles. |
/proc/<pid>/fd/ | Descriptores abiertos | Revela qué archivos y sockets toca el proceso. |
Los comandos que lo hacen concreto. El primero enseña el entorno del propio proceso; el
segundo demuestra cómo el orden del PATH decide qué binario gana:
# el entorno con el que arranco este proceso (separado por NUL) cat /proc/self/environ | tr '\0' '\n' # qué 'tar' se ejecutaria: el primero del PATH, en orden echo "$PATH" which -a tar # lista cada tar del PATH, por orden de prioridad # un PATH con un directorio escribible delante es una via de ejecucion # /opt/util:/usr/bin -> un /opt/util/tar propio gana a /usr/bin/tar
6Diagrama
/usr/bin deja al proceso ejecutar código ajeno creyendo que llama al binario de siempre.Ver el mismo diagrama como código Mermaid (editable)
flowchart LR
P[proceso root · llama a: backup] -->|busca en el PATH| R{primer match}
R -->|gana| M[/opt/util/backup · world-writable/]
R -.se ignora.-> L[/usr/bin/backup · legítimo/]
7Código
La defensa se escribe como configuración, no como intención. Un servicio se lanza con un
PATH fijo y mínimo, llama a sus binarios por ruta absoluta y arranca con el
entorno limpio de variables peligrosas:
#!/usr/bin/env bash # arranque endurecido de un servicio set -euo pipefail # 1) PATH fijo, minimo y sin directorios escribibles ni '.' export PATH='/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin' # 2) limpiar variables que secuestran la carga de bibliotecas unset LD_PRELOAD LD_LIBRARY_PATH LD_AUDIT # 3) llamar a los binarios por ruta absoluta, no por nombre /usr/bin/tar -czf /var/backups/datos.tgz /srv/datos
En systemd lo mismo se declara en la unit: Environment=PATH=...
fija el PATH, y las directivas de aislamiento (que ves en la lección de
systemd) evitan que el servicio herede un entorno del que no te fías. La regla
práctica: un proceso privilegiado nunca debe usar el PATH ni el entorno que le
llegue "por defecto"; los fija él.
8Qué puede salir mal
Secretos por línea de comandos o entorno en máquina compartida.
app --password=... o un token en una variable quedan legibles en
/proc para quien pueda mirar. En hosts compartidos, eso es filtrar el
secreto.
Un directorio escribible en el PATH. Basta que un proceso
privilegiado tenga /opt/util (0777) antes que /usr/bin para
que un usuario cualquiera plante un binario y lo ejecute con esos privilegios.
El . en el PATH. Poner el directorio actual en el
PATH convierte cada carpeta que visitas en una trampa: un
ls o un make falsos dejados ahí se ejecutan en tu lugar.
Llamar a binarios por nombre en scripts privilegiados. Un script de root que
hace tar, cp o find sin ruta absoluta depende del
PATH heredado, y ese PATH puede no ser el que crees.
9Cómo lo abusaría un atacante
Quien acaba de conseguir ejecución como un usuario cualquiera enumera primero. Mira
/proc: recorre los procesos, lee el environ de los que puede y
busca tokens, contraseñas de base de datos, claves de API que alguien pasó por entorno.
Mira cmdline por si un cron lanzó algo con un secreto en los argumentos.
Nada de esto rompe nada: el kernel se lo está enseñando.
Después busca el secuestro de ejecución. Enumera el PATH de los servicios y
los directorios escribibles que aparecen en él. Si encuentra uno, planta un binario con
el nombre de algo que un proceso privilegiado llama —tar,
backup, logrotate— y espera a que ese proceso lo ejecute con
su identidad. Si controla el entorno de un proceso que va a lanzar otro, prueba
LD_PRELOAD para inyectar su biblioteca.
Lo relevante para ti como defensor es el patrón: el atacante no explota el programa, explota lo que el programa hereda y lo que el programa filtra. Son huecos de configuración, invisibles en el día a día, que llevaban ahí desde el despliegue.
10Cómo defenderlo
- Nunca pases secretos por argumento. Prefiere archivos con permisos
600o un gestor de secretos a las variables de entorno en hosts compartidos. - Fija un
PATHmínimo y absoluto en cada servicio. Sin., sin directorios escribibles por usuarios. - En scripts privilegiados, llama a los binarios por ruta absoluta:
/usr/bin/tar, notar. - Limpia el entorno peligroso al arrancar:
unset LD_PRELOAD LD_LIBRARY_PATH LD_AUDIT. - Monta
/procconhidepid=2para que un usuario no vea el/procde procesos ajenos. - Da a cada servicio su propio usuario: así su
/procno es legible por los demás y su entorno no se comparte.
11Laboratorio
Reutilizamos la caja de F03. Trae un directorio world-writable, /opt/util,
perfecto para ver el secuestro por PATH. Entra como el usuario sin
privilegios:
cd cursos/seguridad/labs/lab-linux-privesc
docker compose up -d
docker compose exec caja su - becario
Observa qué filtra /proc y cómo el orden del PATH cambia qué se ejecuta:
Ahora defiende: desde otra terminal, como root
(docker compose exec caja bash), cierra el directorio con
chmod 755 /opt/util y borra el binario plantado. Como becario,
intenta de nuevo escribir en /opt/util: debe fallar. Cuando termines:
docker compose down -v
12Ejercicios
Qué filtra cada archivo de /proc
Di qué expone cada uno y por qué puede importar para la seguridad:
(1) /proc/<pid>/cmdline; (2) /proc/<pid>/environ;
(3) /proc/<pid>/fd/; (4) /proc/<pid>/exe.
cmdline son argumentos; environ, variables; fd, archivos abiertos.
(1) cmdline: los argumentos exactos; un --password= queda a
la vista. (2) environ: las variables de entorno; tokens y claves puestos
ahí son legibles. (3) fd/: los descriptores abiertos; revela qué archivos
y sockets usa el proceso. (4) exe: un enlace al binario en ejecución;
dice qué se está corriendo de verdad.
Clave: cmdline y environ son los peligrosos para secretos; fd y exe revelan qué toca y qué ejecuta el proceso.
Endurece el PATH de un arranque
Tienes un script de servicio que empieza sin fijar el PATH y llama a
tar y gzip por su nombre. Reescríbelo para que sea seguro:
fija un PATH mínimo, limpia el entorno peligroso y usa rutas absolutas.
Justifica cada línea.
PATH y entorno de quien lo lanza.export PATH=..., unset LD_* y rutas absolutas.PATH mínimo típico no incluye . ni ningún directorio escribible.set -euo pipefail
export PATH='/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
unset LD_PRELOAD LD_LIBRARY_PATH LD_AUDIT
/usr/bin/tar -czf /var/backups/d.tgz /srv/datos
Clave: fijar PATH mínimo, quitar LD_* y llamar por ruta absoluta. Las tres cosas cortan la herencia de la que dependía la ejecución.
Por qué el SUID ignora LD_PRELOAD
LD_PRELOAD permite cargar una biblioteca propia antes que las del sistema.
Investiga por qué el enlazador dinámico ignora LD_PRELOAD (y otras
variables similares) cuando el binario es SUID o SGID, y qué pasaría si no lo hiciera.
AT_SECURE en la documentación de ld.so.
Un binario SUID hereda el entorno de quien lo lanza, que es un usuario sin
privilegios, pero corre con la identidad del dueño (a menudo root). Si respetara
LD_PRELOAD, ese usuario podría cargar su biblioteca dentro de un proceso
root y sustituir funciones: ejecución como root inmediata. Por eso el enlazador entra
en "secure-execution mode" (marcado con AT_SECURE) cuando detecta el bit
SUID/SGID e ignora LD_PRELOAD, LD_LIBRARY_PATH y compañía.
Clave: el entorno lo controla quien lanza, no el dueño del binario; respetar LD_PRELOAD en SUID sería entregar el proceso privilegiado. El modo seguro del enlazador lo evita.
Red Team · secuestra un comando por el PATH
Dentro del lab-linux-privesc, y solo dentro de él: como becario,
planta en /opt/util un binario llamado id que imprima un mensaje
propio, y consigue que se ejecute en lugar del id real anteponiendo
/opt/util al PATH. Explica qué identidad usó y por qué ganó tu binario.
PATH se queda con el primer resultado./opt/util/id y ajusta el PATH.PATH="/opt/util:$PATH" id hace que se busque primero en /opt/util.printf '#!/bin/bash\necho SECUESTRADO como $(id -un)\n' > /opt/util/id chmod +x /opt/util/id PATH="/opt/util:$PATH" id # ejecuta tu 'id', no /usr/bin/id
Clave: el binario plantado corre como becario (tu identidad); ganó porque /opt/util iba antes en el PATH. Si el proceso víctima fuera root, tu código correría como root. La defensa es que /opt/util no sea escribible ni esté en el PATH.
Blue Team · cierra el PATH y verifica
Corrige el fallo del laboratorio: quita la escritura de /opt/util y elimina
el binario plantado. Después demuestra, con comandos, que becario ya no puede
escribir ahí y que el id que se ejecuta vuelve a ser el del sistema. Escribe
la comprobación como un check que devuelva éxito o fallo.
chmod 755 /opt/util como root cierra la escritura ajena.... && echo FALLA || echo OK.Como root: chmod 755 /opt/util y rm -f /opt/util/id. Verificación como becario:
# debe fallar ahora: si puede escribir, la defensa no sirvio touch /opt/util/prueba 2>/dev/null \ && echo "FALLA: aun escribe" || echo "OK: sin escritura" command -v id # debe apuntar a /usr/bin/id
Clave: cerrar la escritura del directorio y confirmar que ya no se puede plantar nada; el comando por nombre vuelve a resolver al binario del sistema.
Architecture Challenge · secretos y entorno de un servicio
Diseña cómo un servicio recibe su token de base de datos sin que quede legible en
/proc para otros procesos de la máquina, y cómo fijas su PATH y
su entorno para que no herede nada del atacante. Define dónde vive el secreto, con qué
permisos, y qué directivas usarías en systemd o en el compose.
600 del usuario del servicio, o un gestor de secretos, no aparece en /proc de otros.hidepid=2, usuario propio y Environment=PATH=... cierran la herencia.
Una propuesta razonable: el token vive en un archivo config.env propiedad
del usuario del servicio con permisos 600, y el proceso lo lee al arrancar
en vez de recibirlo por línea de comandos (que quedaría en cmdline). El
servicio corre con su propio usuario sin login, con /proc montado
hidepid=2 para que otros usuarios no vean su environ. En la
unit se fija Environment=PATH=/usr/local/bin:/usr/bin:/bin y se limpia el
entorno heredado. Mejor aún: sacar el secreto a un gestor y que el proceso lo pida por
identidad, sin que toque nunca el disco ni el entorno.
Clave: no hay una única respuesta. Se evalúa que el secreto no quede en cmdline/environ visibles, que el PATH sea fijo y mínimo, y que el servicio no herede entorno del atacante.
13Preguntas de comprensión
¿Qué expone /proc/<pid>/environ?
environ guarda el entorno de arranque. Un token o una clave puestos ahí son legibles para quien pueda mirar ese /proc.
¿Por qué es peligroso un directorio escribible situado antes en el PATH?
La resolución del PATH se queda con el primer resultado. Un directorio escribible por delante deja plantar un binario que gana al legítimo.
¿Qué permite LD_PRELOAD?
LD_PRELOAD fuerza a cargar tu biblioteca antes que las estándar, de modo que el programa llama a tus versiones de las funciones. Por eso el enlazador lo ignora en binarios SUID.
¿Cómo se endurece la resolución de comandos de un servicio?
Un PATH fijo y mínimo, sin directorios escribibles ni ., más llamadas por ruta absoluta, cortan el secuestro por herencia.
14Reto adicional
Encuentra un secreto en /proc y ciérralo
En un Linux tuyo (o en el contenedor del laboratorio), lanza un proceso que reciba un
valor "secreto" por variable de entorno y otro que lo reciba por argumento. Léelos desde
/proc. Luego explica qué método filtró el secreto, a quién, y cuál sería la
forma correcta de pasárselo al proceso.
environ y cmdline son los dos sitios donde buscar.SECRETO=abc sleep 300 & y mira su /proc/<pid>/environ.600 o un gestor de secretos, no el argumento.
SECRETO=abc sleep 300 & deja el valor en
/proc/<pid>/environ; sleep 300 --clave=abc lo deja en
cmdline. Ambos son legibles para quien pueda mirar ese /proc
(el propio usuario siempre; otros según hidepid y privilegios). La forma
correcta es que el proceso lea el secreto de un archivo 600 de su usuario
o lo pida a un gestor: así no aparece ni en cmdline ni en
environ.
Clave: argumento y entorno filtran el secreto vía /proc; un archivo 600 o un gestor de secretos no. "Pasarlo por entorno" es mejor que por argumento, pero no es privado en máquina compartida.
15Resumen
/proc/<pid>publicacmdline,environ,fdymaps: un secreto por argumento o entorno queda legible ahí.- La resolución del
PATHse queda con el primer resultado; un directorio escribible por delante es una vía de ejecución. - El
.en elPATHconvierte cada carpeta que visitas en una posible trampa. LD_PRELOADyLD_LIBRARY_PATHsecuestran la carga de bibliotecas; el enlazador los ignora en SUID por eso mismo.- La defensa es fijar un
PATHmínimo, usar rutas absolutas y limpiar el entorno peligroso al arrancar. hidepid=2y un usuario propio por servicio evitan que un proceso filtre a otros a través de/proc.
/proc/<pid>/environ, en texto plano, legible mientras el proceso viva. En máquina compartida, no es privado.PATH es /opt/util:/usr/bin y /opt/util es 0777. ¿Qué riesgo hay?/opt/util/<comando> y se ejecuta en lugar del de /usr/bin, con la identidad del proceso que lo llame./usr/bin/tar y no a tar?tar por nombre depende del PATH heredado; la ruta absoluta no se puede secuestrar.hidepid=2 al montar /proc?/proc de los procesos que no son suyos, cerrando la fuga de environ y cmdline ajenos.16Mis notas
Estas notas se guardan solo en este navegador. No se envían a ningún servidor. Expórtalas desde el panel de progreso si cambias de máquina.
17Recursos
Los comentarios se cargan solo si los pides, desde GitHub Discussions. No se carga nada de terceros mientras no pulses.