# DevOps Nightmare: una cadena DFIR pensada para leer compromiso, persistencia y exfiltración

9 min read
Table of Contents

Máquina creada por: Oscar
Plataforma: The Hackers Labs
Sistema operativo: Linux
Dificultad: media


Sobre este CTF

DevOps Nightmare es una máquina que diseñé para trabajar una idea muy concreta: cómo reconstruir una intrusión a partir de evidencias dispersas, correlacionando autenticación, actividad de sistema, tráfico de red y rastros de exfiltración. No la planteé como una colección de pruebas aisladas, sino como una cadena coherente en la que cada artefacto responde a una fase del ataque.

La intención del laboratorio no es premiar a quien memoriza comandos, sino obligar a leer el incidente con criterio. Primero aparece un acceso inicial identificable en auth.log, después una descarga sospechosa en syslog, más tarde un canal de persistencia visible en red y en logs, y finalmente movimiento lateral y exfiltración sobre un activo interno. Esa secuencia reproduce errores que no son exclusivos de un CTF: reutilización o exposición de credenciales, ejecución de payloads desde infraestructura externa, listeners no autorizados y transferencias internas que terminan saliendo del entorno.

Por eso el valor de la máquina no está solo en responder preguntas del solucionario. Está en entender por qué la cadena tiene sentido, qué evidencia deja cada fase y cómo una investigación razonable puede reconstruirla sin depender de una única fuente.


Información técnica

CampoValor
NombreDevOps Nightmare
IP objetivoNo especificada en las notas
ServiciosSSH, tráfico HTTP saliente, servicio persistente en puerto alto, comunicación interna hacia servidor de base de datos
Cadena principalAcceso inicial por credenciales comprometidas → descarga de payload → persistencia mediante listener → movimiento lateral hacia activo interno → exfiltración
DificultadMedia

Descubrimiento de la evidencia

El punto de partida del laboratorio es una colección de artefactos que obliga a combinar fuentes distintas. No quise que la investigación dependiera de una sola pista evidente, sino de la relación entre logs de autenticación, logs del sistema, captura de red y un artefacto de base de datos presente como contexto.

Terminal window
mkdir investigacion
tar -xzvf DevOps_Nightmare.tar.gz -C investigacion/
cd investigacion
ls -l

En las notas aparecen estos ficheros:

auth.log
syslog
network.pcap
app_database.db

Desde el diseño, esta fase busca introducir una mentalidad básica de DFIR: antes de responder qué ha pasado, hay que entender qué fuentes existen y qué parte de la historia puede aportar cada una. auth.log sirve para fijar el acceso inicial, syslog para ver actividad local, network.pcap para validar comportamiento y temporalidad, y app_database.db queda como artefacto contextual, no necesariamente como pieza central de la resolución.


Acceso inicial

La primera parte de la cadena está en auth.log. Aquí la intención era enseñar una lectura sencilla pero útil: un patrón de fallo seguido de éxito sobre el mismo usuario y desde la misma IP en una ventana muy corta suele ser suficiente para orientar la investigación, incluso sin disponer de telemetría más rica.

Las notas muestran primero mucho ruido de accesos válidos por clave pública:

Nov 15 23:55:49 ubuntu sshd[3258]: Accepted publickey for admin from 10.0.0.62 port 36536
Nov 15 23:55:54 ubuntu sshd[9827]: Accepted publickey for jenkins from 10.0.0.254 port 62422
Nov 15 23:56:25 ubuntu sshd[4264]: Accepted publickey for developer1 from 10.0.0.165 port 48274
...

Ese ruido está puesto a propósito. En una investigación real rara vez se trabaja sobre un log limpio. Hay eventos legítimos, automatizaciones y accesos esperables que no ayudan a explicar el incidente. La pista relevante aparece al apartar ese tráfico normal:

Terminal window
grep -v "Accepted publickey" auth.log
Nov 15 03:27:45 ubuntu sshd[10612]: Failed password for developer1 from 45.63.89.234 port 39654
Nov 15 03:28:15 ubuntu sshd[10612]: Accepted password for developer1 from 45.63.89.234 port 39654

Técnicamente, la conclusión es clara: la IP 45.63.89.234 consigue autenticarse como developer1 tras un fallo previo. No necesitaba una fuerza bruta masiva para que la idea funcionase; bastaba con una secuencia mínima que obligara a detectar la relación temporal y contextual.

Lo que quise enseñar aquí es que el acceso inicial no siempre se presenta como una firma espectacular. A veces es solo una transición muy breve entre falló y entró, y la capacidad de verla depende más de filtrar bien el ruido que de buscar indicadores complejos.


Descarga del payload y dominio de mando y control

Una vez fijado el acceso inicial, el siguiente paso del laboratorio lleva al sistema. Quería que la investigación no se quedara en hubo un login sospechoso, sino que avanzara hacia lo que el intruso hizo después de entrar. Para eso, syslog contiene una evidencia explícita de descarga, pero codificada de forma lo bastante simple como para exigir una validación manual.

La búsqueda se concentra alrededor de la hora del compromiso:

Terminal window
grep "wget" syslog
Nov 15 03:28:30 ubuntu wget[3225]: Download completed: aHR0cDovL2Nkbi11cGRhdGUuZGV2b3BzLnRlY2gvdXBkYXRlX2FnZW50LnNo

La cadena en base64 se decodifica así:

Terminal window
echo "aHR0cDovL2Nkbi11cGRhdGUuZGV2b3BzLnRlY2gvdXBkYXRlX2FnZW50LnNo" | base64 -d
http://cdn-update.devops.tech/update_agent.sh

A nivel técnico, esta fase identifica dos piezas: el fichero descargado, update_agent.sh, y el dominio usado para servirlo, cdn-update.devops.tech.

A nivel de diseño, esta parte representa algo muy habitual: tras obtener acceso, el atacante no necesita ejecutar un implante sofisticado si puede traer un script desde infraestructura externa y apoyarse en utilidades nativas del sistema. La codificación en base64 no pretende ser malware avanzado; pretende enseñar una lección más útil: muchos indicadores no están ocultos, solo empaquetados lo justo para que una revisión superficial los pase por alto.


Persistencia y canal de control

La fase siguiente está pensada para forzar correlación entre red y sistema. No quise dejar la persistencia resuelta en un único log inequívoco. Preferí que primero apareciera como anomalía de tráfico y solo después pudiera confirmarse en syslog.

Según las notas, el análisis del PCAP parte de un resumen de conversaciones TCP:

Terminal window
tshark -r network.pcap -q -z conv,tcp > trafico.txt
cat trafico.txt | grep -v ":80"

La observación relevante es una conexión asociada al puerto 54321, fuera del patrón esperado. La validación posterior en los logs del sistema cierra la hipótesis:

Terminal window
grep "54321" syslog
Started listener on port 54321

Con eso, la persistencia queda asociada a un listener local en un puerto alto no estándar.

Lo importante aquí no es el número de puerto en sí, sino el razonamiento. En muchos entornos reales, la persistencia no se descubre por una firma inequívoca, sino por la combinación de dos hechos discretos: un patrón de red extraño y un rastro local que explica quién abrió ese canal. Diseñé esta parte para enseñar esa lectura cruzada. También buscaba reflejar un error operativo común: procesos no autorizados escuchando en puertos altos que terminan normalizándose porque nadie revisa esos detalles mientras el servicio principal sigue funcionando.


Movimiento lateral hacia el segundo servidor

Una vez comprometido el host inicial, la máquina empuja la investigación hacia el interior de la red. La idea era evitar que toda la historia girase alrededor del atacante externo y mostrar que, tras consolidar acceso, el siguiente paso razonable es explorar activos internos de más valor.

Las notas proponen identificar conexiones salientes desde la víctima hacia otros sistemas internos:

Terminal window
tshark -r network.pcap -Y "ip.src == 192.168.1.100 and ip.dst != 45.63.89.234 and tcp.flags.syn==1"

A partir de ahí aparece una IP interna relevante: 192.168.1.200.

La correlación con syslog revela el hostname:

Terminal window
grep "192.168.1.200" syslog
firewall: dbmaster01 (192.168.1.200) added to authorized whitelist

La pista clave es dbmaster01.

Desde el punto de vista del diseño, esta fase existe para enseñar dos cosas. La primera es que el movimiento lateral no siempre deja nombres claros en la captura; a menudo el nombre útil aparece en otra fuente, como logs de firewall, inventario o sistema. La segunda es que un atacante que ya dispone de un punto de apoyo raramente se queda en la máquina comprometida si detecta un servidor de base de datos accesible en red interna. Por eso el salto a dbmaster01 no es gratuito: representa una progresión lógica hacia un activo con mayor impacto.


Exfiltración y nombre del archivo robado

La última fase está construida para cerrar la cadena completa: no basta con ver intrusión y persistencia; hay que identificar qué salió del entorno. Aquí introduje una evidencia pequeña, pero suficiente, en el payload TCP.

Las notas extraen el contenido hexadecimal de una transferencia desde el servidor interno hacia la IP atacante:

Terminal window
tshark -r network.pcap \
-Y "ip.src == 192.168.1.200 and ip.dst == 45.63.89.234" \
-T fields -e tcp.payload | head -1
7af9d12b5f636f6e666964656e7469616c5f64756d702e73716c2e677a

Al reconstruirlo desde hexadecimal:

Terminal window
echo "7af9d12b5f636f6e666964656e7469616c5f64756d702e73716c2e677a" | xxd -r -p
7af9d12b_confidential_dump.sql.gz

Si además se extraen solo los caracteres imprimibles, el nombre legible del archivo queda todavía más claro:

Terminal window
echo "7af9d12b5f636f6e666964656e7469616c5f64756d702e73716c2e677a" | xxd -r -p | strings
confidential_dump.sql.gz

Aquí conviene limpiar una inconsistencia presente en las notas. En el solucionario aparece como respuesta a la pregunta sobre los primeros 8 caracteres del hash MD5 la cadena 7af9d12b_confidential_dump.sql.gz, pero la parte que realmente responde a esa pregunta es 7af9d12b. El nombre del archivo exfiltrado, por otro lado, es confidential_dump.sql.gz, o bien la cadena completa observada en el payload: 7af9d12b_confidential_dump.sql.gz.

Esta fase la diseñé para que el participante entendiera que la exfiltración no siempre exige reconstruir un archivo completo. A veces basta con recuperar fragmentos de naming, prefijos hash o metadatos embebidos en el flujo para saber qué salió y desde dónde. Es una lección útil fuera del laboratorio, sobre todo cuando se trabaja con capturas parciales o con tráfico incompleto.


Qué quería enseñar al diseñar DevOps Nightmare

DevOps Nightmare está pensado como una cadena de fallos y evidencias, no como una sucesión de adivinanzas. Quería que el laboratorio obligara a relacionar acceso inicial, ejecución posterior, persistencia, movimiento lateral y exfiltración bajo una lógica reconocible en un incidente real.

La máquina enseña que una investigación sólida no depende de encontrar una bala de plata en un solo archivo. Depende de saber leer contexto, tiempos y relaciones entre artefactos. También quería que cada fase representara un error operativo concreto: una cuenta expuesta, un sistema capaz de descargar y ejecutar contenido externo, un listener persistente sin control, una red interna alcanzable y un activo sensible finalmente exfiltrado.

FaseQué enseñaError representado
Acceso inicialDetectar compromiso correlando fallo y éxito de autenticaciónCredenciales comprometidas o débiles en cuenta con acceso SSH
Descarga de payloadIdentificar ejecución posterior al login y rastrear infraestructura externaCapacidad de descargar y ejecutar contenido externo sin control efectivo
PersistenciaValidar una anomalía de red con evidencia del sistemaListener persistente no autorizado en puerto alto
Movimiento lateralRelacionar tráfico interno con metadatos del entornoSegmentación insuficiente o confianza excesiva entre sistemas internos
ExfiltraciónExtraer indicadores del archivo robado desde payload parcialFalta de visibilidad o control sobre salidas de datos sensibles

En conjunto, la cadena busca dejar una idea sencilla: cuando varias pequeñas debilidades conviven en el mismo entorno, el impacto no lo marca cada fallo por separado, sino su combinación.


Recursos y referencias

  • grep, cat, awk para filtrado y lectura inicial de evidencias.
  • tshark o Wireshark para análisis de conversaciones y payloads.
  • base64 para decodificar artefactos simples ofuscados.
  • xxd y strings para reconstrucción e interpretación de contenido hexadecimal.
  • Revisión cruzada entre auth.log, syslog y network.pcap como método base de correlación.

Resumen operativo de hallazgos

PreguntaRespuesta
IP del atacante inicial45.63.89.234
Usuario comprometido primerodeveloper1
Fichero malicioso descargadoupdate_agent.sh
Dominio usado para C2 o distribucióncdn-update.devops.tech
Puerto usado para persistencia54321
Hostname del segundo servidor comprometidodbmaster01
Primeros 8 caracteres observados del identificador/hash7af9d12b
Nombre legible del archivo robadoconfidential_dump.sql.gz
My avatar

¿Te ha resultado útil o interesante este post? Soy Oscar, Senior Platform Engineer y SRE, y en este blog comparto mis reflexiones, experimentos y retos técnicos sobre automatización, seguridad (especialmente el diseño de CTFs), optimización de rendimiento y el impacto de la tecnología en el desarrollo profesional.

Conecta y conversemos: LinkedIn Mi código y proyectos en: GitHub


More Posts