Laboratorio · Lección 01

Anatomía del laboratorio

Antes de la primera técnica hace falta un sitio donde equivocarse sin consecuencias. Ese sitio no es “una máquina virtual por ahí”: es una topología concreta, con piezas que tienen nombre y con aislamientos que están donde están por un motivo. Esta lección desmonta el lab-00-base pieza a pieza y responde la pregunta que vas a repetir en las 79 lecciones siguientes: si esto se rompe, ¿hasta dónde llega el daño?

Al terminar sabrás

  • Nombrar las cinco piezas del laboratorio de referencia y decir qué papel cumple cada una.
  • Explicar por qué el laboratorio tiene dos redes y no una, y qué cambia internal: true.
  • Distinguir “el contenedor está aislado” de “el contenedor está en una red sin salida”: no es lo mismo.
  • Leer un docker-compose.yml y decir, sin ejecutarlo, qué queda expuesto al host.
  • Justificar por qué la aplicación vulnerable no publica ningún puerto y el proxy sí.
  • Comprobar con comandos que el aislamiento que crees tener es el que de verdad hay.
Antes de empezar · qué sabes ya

Tres preguntas rápidas. No puntúan para completar la lección: sirven para que la explicación se ajuste a lo que ya traes.

En una red de Docker declarada con internal: true, ¿qué deja de poder hacer un contenedor?

Dentro de la red se sigue viendo todo con todo. Lo que desaparece es la ruta hacia fuera.

Publicas un puerto como 8080:80, sin nada delante. ¿Quién puede llegar a ese servicio?

Sin dirección delante, Docker escucha en todas las interfaces. Y además programa la regla de reenvío por debajo de tu firewall local.

Dos servicios del mismo docker-compose.yml comparten red. ¿Cómo se encuentran?

Docker levanta un resolutor propio por red. El nombre del servicio es el nombre de host, y eso es lo que hace reproducible el laboratorio.

0 / 3

1Por qué importa

Hay dos formas de aprender seguridad ofensiva y solo una termina bien. La primera es leer una técnica y probarla contra lo primero que responda; la segunda es montar un objetivo propio, romperlo, mirar los logs y reconstruirlo. La diferencia no es solo legal: la primera no enseña casi nada, porque cuando algo funciona no puedes ver por qué funcionó, y cuando falla no puedes distinguir si falló tu técnica o si el objetivo ya estaba parcheado.

Un laboratorio bien montado te da tres cosas que ningún objetivo ajeno te da nunca. Observabilidad: puedes leer los logs de las dos puntas del ataque a la vez. Reproducibilidad: si algo funcionó, lo puedes repetir mañana en el mismo estado exacto. Y reversibilidad: destruyes y reconstruyes en segundos, así que puedes permitirte romper cosas de verdad, que es la única manera de entenderlas.

Hay una cuarta razón, menos evidente y más importante: un laboratorio de seguridad es una máquina llena de software deliberadamente vulnerable. Si no está aislado, no es un laboratorio, es una brecha esperando su turno dentro de tu propia casa. La disciplina de aislamiento que vas a aprender aquí no es burocracia del curso; es lo único que separa “estoy practicando” de “he montado un punto de entrada en mi red doméstica”.

El objetivo real

Al final de esta lección tienes que ser capaz de mirar cualquier docker-compose.yml —el del curso o el de tu trabajo— y responder en treinta segundos: qué se publica al host, qué puede salir a internet, y qué pieza es la única que cruza la frontera. Esas tres respuestas son un modelo de amenazas en miniatura.

2Conceptos

Vocabulario de esta lección
  • Laboratorio: conjunto de piezas desechables, reproducibles y aisladas donde se practica de verdad.
  • Topología: quién puede hablar con quién. Es la parte del laboratorio que de verdad importa.
  • Frontera de confianza: el punto donde termina lo que controlas y empieza lo que solo esperas que se porte bien.
  • Superficie de ataque: todo lo que un atacante puede tocar. En un laboratorio hay dos: la interna (a propósito) y la que apunta a tu máquina (que debe ser cero).
  • Red interna: red de Docker sin ruta de salida. Los contenedores se ven entre ellos y no llegan a internet.
  • Publicar un puerto: pedirle al host que reenvíe un puerto suyo a un contenedor. Es la única forma en que el laboratorio toca tu máquina.
  • Pieza de borde: el único servicio con un pie en cada red. En el laboratorio de referencia, el proxy inverso.

3Explicación profunda

El laboratorio de referencia del curso tiene cinco servicios. No son cinco por gusto: cada uno representa una categoría de pieza que vas a encontrar en cualquier sistema real, y quitar cualquiera de ellos deja un hueco pedagógico concreto.

PiezaServicioPara qué está
Proxy inversoproxyÚnico punto de entrada. Es la frontera de confianza dibujada en YAML, y la única pieza que ve las dos redes.
Aplicación vulnerableappEl objetivo. Sirve contenido y, según la fase, tendrá fallos plantados a propósito.
Base de datosdbEl activo. Sin un sitio donde vivan los datos, media docena de ataques no tienen sentido.
ObservabilidadmonitorEl lado azul. Sin logs no hay defensa, y la mitad del curso es mirar aquí después de atacar.
Puesto de atacanteatacanteDesde dónde se lanzan las pruebas. Vive dentro, no en tu máquina, y eso es deliberado.

El detalle que más gente pasa por alto es el último. El atacante vive dentro del laboratorio, no en tu terminal. Cuesta un poco más al principio —hay que entrar con docker compose exec— y a cambio compra dos cosas. La primera es que tus herramientas ofensivas no se instalan en tu máquina de trabajo. La segunda es que, cuando pruebas si algo tiene salida a internet, la respuesta que obtienes es la del laboratorio y no la de tu portátil, que sí la tiene.

Dos redes, y por qué no una

Docker crea por defecto redes de tipo bridge: un puente virtual en tu máquina, con un rango propio, y reglas de NAT que le dan salida a internet a todo lo que se conecte. Ese comportamiento por defecto es exactamente lo que no queremos para software deliberadamente roto.

Añadir internal: true a una red cambia una sola cosa, y es la decisiva: Docker no programa la regla de enmascaramiento que da salida a esa red. Los contenedores siguen teniendo dirección, siguen viéndose entre ellos, siguen resolviendo nombres… y no tienen por dónde salir. No es un cortafuegos con reglas que se puedan equivocar: es la ausencia de una ruta.

bridge normalbridge con internal: true
Contenedor ↔ contenedor
DNS por nombre de servicio
Contenedor → internetno
Host → contenedor (puerto publicado)no, en la práctica

De ahí sale la topología: lab_borde es una red normal, con salida, y solo la toca el proxy. lab_interna es interna, y ahí viven las otras cuatro piezas. El proxy tiene un pie en cada una. Esa asimetría es todo el diseño: hay exactamente un camino entre el laboratorio y el mundo, y está escrito en cuatro líneas que puedes auditar de un vistazo.

Resolución de nombres: por qué el laboratorio es reproducible

Cada red de Docker trae un resolutor propio. Dentro de una red, el nombre del servicio del compose resuelve a la dirección del contenedor, y se actualiza solo si el contenedor se recrea. Por eso el proxy puede escribir http://app en su configuración y funcionar en tu máquina, en la mía y dentro de dos años.

Es también la razón de que en este curso no aparezcan direcciones escritas a mano. Fijar una dirección literal en un laboratorio es la forma más rápida de que deje de levantar en otra máquina, y además rompe el hábito que quieres construir: en infraestructura moderna, las direcciones son efímeras y los nombres son el contrato.

Publicar un puerto no es “abrirlo un poco”

Un contenedor que no publica puertos no es alcanzable desde tu máquina, aunque escuche en el 80 dentro de sí mismo. Publicar es pedirle al host que reenvíe. Y la sintaxis tiene una trampa de diseño heredada: si escribes 8080:80, la parte de la dirección se omite y Docker asume 0.0.0.0, es decir, todas las interfaces de tu máquina.

Traducido: tu aplicación deliberadamente vulnerable queda accesible para cualquiera que comparta red contigo. La red del trabajo, la de la cafetería, la wifi de invitados del hotel. Escribir 127.0.0.1:8080:80 es una sola diferencia de nueve caracteres y significa “solo procesos de esta máquina”. En este curso esa forma no es una recomendación de estilo; es obligatoria en todos los laboratorios.

Y hay un agravante que casi nadie espera la primera vez: en Linux, Docker inserta sus reglas de reenvío en una cadena que se evalúa antes que las reglas del firewall del sistema. Un puerto publicado en todas las interfaces puede seguir siendo alcanzable aunque tu firewall diga que ese puerto está cerrado. Volveremos a esto en la lección de reset y firewall; por ahora quédate con que la seguridad del puerto la decide el compose, no el firewall.

4Ejemplo sencillo

# Un laboratorio es un edificio con una sola puerta

proxy       la porteria: unica puerta a la calle, y da a un portal cerrado
app         el piso que vas a forzar
db          la caja fuerte del piso
monitor     las camaras: existen para que puedas ver que paso
atacante    tu, dentro del edificio, sin llaves de la calle

# Lo que define el edificio no son las habitaciones, son las puertas

lab_borde     la calle          solo la porteria da a ella
lab_interna   el portal         todos se ven, nadie sale
127.0.0.1     tu propia acera   la porteria solo abre hacia ahi
Las piezas se olvidan; la topología es lo que hay que recordar. Si sabes decir quién habla con quién, el resto del laboratorio se deduce.

5Ejemplo técnico

Levanta el laboratorio base y pregúntale a Docker —no al compose— cómo quedó de verdad. Estas tres respuestas son las que auditas cada vez que montas un entorno:

qué está publicado y qué no
docker compose ps --format "{{.Service}}\t{{.Ports}}" proxy 127.0.0.1:8080->80/tcp app db monitor atacante # una sola linea con puertos, y con direccion delante: correcto docker compose exec atacante sh -c "getent hosts app db monitor" 172.28.0.1 app 172.28.0.1 db 172.28.0.1 monitor # el DNS interno resuelve por nombre de servicio (direcciones de ejemplo)

La primera comprobación es la que de verdad importa y casi nadie hace: contar las líneas con puertos. En este laboratorio debe haber exactamente una. Si aparecen dos, alguien publicó algo sin querer; si la que hay no lleva dirección delante, el laboratorio está expuesto a la red local aunque “funcione igual”.

La segunda muestra el resolutor interno funcionando. Ese es el mecanismo que permite que la configuración del proxy diga proxy_pass http://app; sin conocer ninguna dirección, y el que hace que el laboratorio se comporte igual en cualquier máquina.

6Diagrama

Topología del laboratorio base: el host llega solo al proxy por el puerto local; el proxy es la única pieza con un pie en la red de borde y otro en la red interna, donde viven aplicación, base de datos, monitorización y puesto de atacante tu maquina 127.0.0.1:8080 proxy unica frontera lab_interna · internal: true · sin salida app db monitor atacante se ven entre ellos por nombre de servicio ninguno alcanza internet lab_borde
Toda la seguridad del laboratorio está en el ancho de la flecha izquierda: un único puerto, en una única dirección, hacia una única pieza.
Ver el mismo diagrama como código Mermaid (editable)
flowchart LR
    H[host 127.0.0.1:8080] --> P[proxy]
    P --> A[app]
    P --> M[monitor]
    subgraph interna[lab_interna internal:true]
      A
      M
      D[db]
      T[atacante]
    end
    A --> D
    T --> A
    T --> D

7Código

Todo lo anterior cabe en el esqueleto del compose. Estas son las líneas que definen la seguridad del laboratorio; el resto son detalles de las imágenes:

# las dos redes: aqui esta el 90% del diseno
networks:
  lab_borde:
    driver: bridge          # tiene salida; solo el proxy la toca
  lab_interna:
    driver: bridge
    internal: true           # sin ruta de salida: no es un filtro, es una ausencia

services:
  proxy:
    ports:
      - "127.0.0.1:8080:80"   # con direccion: solo esta maquina
    networks:
      - lab_borde              # un pie fuera...
      - lab_interna            # ...y otro dentro. La unica pieza asi.

  app:
    networks:
      - lab_interna            # sin "ports": inalcanzable desde el host

  atacante:
    networks:
      - lab_interna            # el atacante vive dentro, no en tu terminal

Léelo como se lee un plano y no como configuración. Las dos preguntas que responde son “¿cuántas piezas aparecen bajo ports?” y “¿cuántas piezas listan más de una red?”. Las respuestas correctas en un laboratorio son una y una, y tiene que ser la misma pieza. Cualquier otra combinación necesita una justificación escrita.

8Qué puede salir mal

Los cinco fallos que se repiten

Publicar sin dirección. 8080:80 en lugar de 127.0.0.1:8080:80. Es la forma que aparece en el 90% de los tutoriales de internet, porque en un tutorial no hay software vulnerable dentro. En un laboratorio, es una invitación abierta a quien comparta wifi contigo.

Dar salida al contenedor equivocado. Añadir lab_borde a la lista de redes del atacante o de la aplicación vulnerable. Es una línea, no rompe nada visiblemente, y convierte tu laboratorio en algo que puede alcanzar máquinas de terceros.

Montar tu directorio personal. Un volumes: - ~/:/host “para pasar un archivo” le da a la aplicación vulnerable acceso de lectura y escritura a tus claves, tus tokens y tu historial. Los montajes de un laboratorio deben ser rutas del propio laboratorio, y de solo lectura siempre que se pueda.

Etiquetas móviles. Usar latest hace que el laboratorio de hoy no sea el de mañana. Cuando un ejercicio deje de reproducirse no vas a saber si fue culpa tuya o de una imagen que cambió por debajo.

Dejarlo levantado. Un laboratorio que arranca solo con restart: unless-stopped y sobrevive al reinicio es un servicio vulnerable corriendo permanentemente en tu equipo. Se levanta para trabajar y se destruye al terminar.

9Cómo lo abusaría un atacante

Vista desde el otro lado

Un laboratorio de seguridad mal aislado es uno de los objetivos más golosos que existen en una red compartida, y por una razón elegante: alguien ya se molestó en instalar software vulnerable, en configurarlo para que el fallo funcione y en dejarlo corriendo. No hay que buscar el defecto; viene documentado en el LEEME.

La forma de encontrarlo es aburrida: barrer los puertos altos habituales —el 8080, el 3000, el 8000— en el rango de la red local. Un servicio de práctica se identifica solo, porque suele responder con una portada que dice exactamente qué es. A partir de ahí el trabajo ya está hecho por la víctima.

Y lo interesante no es el laboratorio, es lo que hay detrás. Un contenedor con ejecución de comandos y salida a internet sirve para tres cosas: mirar qué más hay en la red interna del anfitrión, montar un punto de entrada persistente, y usar tu conexión para atacar a terceros. Ese tercer punto es el que convierte un descuido de aprendizaje en un problema legal con tu nombre en los registros.

La segunda vía es más silenciosa: los montajes. Si la aplicación vulnerable tiene montado un directorio de tu máquina, la lectura de archivos que practicas como ejercicio deja de ser un ejercicio en cuanto la ruta apunta fuera del contenedor. No hace falta escapar de nada; se lo diste tú.

10Cómo defenderlo

La lista que se revisa antes de cada up -d
  • Un solo puerto publicado, siempre con 127.0.0.1: delante. Si necesitas dos, escribe por qué en un comentario del compose.
  • Una sola pieza con dos redes. El resto vive únicamente en la red con internal: true.
  • Verifica en vez de suponer: docker compose ps para los puertos y un curl hacia fuera desde el atacante para la salida.
  • Montajes solo del laboratorio, con :ro cuando el contenedor no necesite escribir. Nunca tu directorio personal, nunca la raíz del repositorio.
  • Etiquetas fijas de imagen. Un laboratorio que no se reproduce no sirve para aprender, porque no puedes comparar dos ejecuciones.
  • Destruye al terminar con docker compose down -v. La -v se lleva los volúmenes, y eso también es defensa: los datos de práctica no deben acumularse.
  • Trata el compose como código revisable. Un cambio en ports o en networks merece la misma atención que un cambio en el control de acceso de una aplicación, porque es lo mismo.

11Laboratorio

Estado: laboratorio local lab-00-base
Targethttp://localhost:8080
Autorizaciónsistema local creado por ti para entrenamiento
Todo lo que se practica en este curso se practica aquí dentro. Ninguna técnica de este material debe apuntarse a un sistema que no sea tuyo o para el que no tengas autorización escrita.

Levanta el laboratorio y audita la topología antes de tocar nada:

cd cursos/seguridad/labs/lab-00-base docker compose up -d docker compose ps

Ahora las tres preguntas de la lección, una por comando. Primero, qué toca tu máquina:

docker compose ps --format "{{.Service}} {{.Ports}}"

Segundo, quién puede salir. Y tercero, quién ve a quién:

auditar el aislamiento
docker compose exec atacante sh -c "curl -m 5 -sS https://example.com" curl: (6) Could not resolve host: example.com # correcto: lab_interna no tiene ruta de salida docker compose exec atacante sh -c "getent hosts db && nc -z -w2 db 5432 && echo abierto" abierto # la base de datos SI es alcanzable desde dentro: eso es a proposito curl -m 3 -sS http://localhost:5432 curl: (7) Failed to connect to localhost port 5432 # y NO lo es desde el host: no publica puerto

Dibuja en papel lo que acabas de comprobar y compáralo con el diagrama de la sección 6. Si tu dibujo tiene una flecha que el diagrama no tiene, o al revés, una de las dos cosas está mal y merece la pena averiguar cuál. Cuando termines:

docker compose down -v

12Ejercicios

Clasifica cinco piezas

Para cada servicio del lab-00-base responde tres cosas en una línea: (1) ¿publica algún puerto al host?, (2) ¿puede salir a internet?, (3) ¿qué se pierde del curso si lo borras del compose?

Las dos primeras respuestas están en el propio compose: mira ports y networks de cada servicio.
La salida a internet no depende del servicio, depende de si alguna de sus redes carece de internal: true.
Para la tercera columna, piensa en qué fase del ciclo del curso se queda coja: construir, atacar, observar, defender o verificar.
Solución

proxy: publica el 8080 en localhost, sí sale a internet, y sin él desaparece la frontera de confianza (y el único acceso desde el host). app: no publica, no sale, y sin ella no hay objetivo. db: no publica, no sale, y sin ella no hay activo que proteger ni ataques con sentido.

monitor: no publica, no sale, y sin él se pierde toda la mitad defensiva del curso, que consiste en mirar logs después de atacar. atacante: no publica, no sale, y sin él tendrías que atacar desde tu propia máquina, que sí tiene internet y sí tiene tus datos.

Clave: las columnas 1 y 2 tienen que salirte leyendo, sin ejecutar nada. Si necesitaste levantar el laboratorio para responder, vuelve a leer el compose hasta que no haga falta.

Añade una sexta pieza sin romper el aislamiento

Copia el lab-00-base a un directorio propio y añade un servicio cache con Redis que solo puedan usar app y atacante. Requisitos: no publica puertos, no tiene salida a internet, y monitor no debe poder alcanzarlo. Demuestra las tres condiciones con comandos.

Si dos servicios comparten red, se ven. Para que monitor no llegue, la separación tiene que estar en las redes, no en la configuración de Redis.
Nada te obliga a tener solo dos redes. Un servicio puede estar en varias, y ahí es donde se dibuja quién habla con quién.
Declara una tercera red también con internal: true y ponle solo los tres servicios que deben verse.
Solución

Se añade lab_cache con internal: true. El servicio cache vive solo en ella; app y atacante se añaden a su lista de redes sin quitarles lab_interna. monitor no se toca, así que no tiene forma de resolver ni de alcanzar el nuevo servicio.

networks:
  lab_cache:
    driver: bridge
    internal: true

services:
  cache:
    image: redis:7-alpine
    networks: [lab_cache]
  app:
    networks: [lab_interna, lab_cache]
  atacante:
    networks: [lab_interna, lab_cache]

Comprobaciones: docker compose ps sigue mostrando una única línea con puertos; desde cache un curl hacia fuera falla; y docker compose exec monitor sh -c "getent hosts cache" no resuelve.

Clave: lo que se evalúa es que la separación esté en la topología. Una contraseña en Redis no es aislamiento: es una puerta con cerradura en una habitación a la que no debería llegarse.

Demuestra qué hace internal: true por debajo

internal: true no es magia: se traduce en reglas concretas del anfitrión. Investiga qué crea Docker en el sistema para una red normal que no crea para una interna, y escribe un párrafo explicando por qué el resultado es “no hay ruta” y no “hay una ruta bloqueada”. Explica también qué implicación tiene esa diferencia para alguien que ya tiene ejecución dentro del contenedor.

Compara las dos redes desde el propio Docker antes de mirar el sistema: docker network inspect muestra la diferencia declarada.
La palabra que buscas es enmascaramiento (NAT de salida). Busca qué componente lo programa y para qué redes.
En Linux, mira las cadenas que Docker gestiona en el cortafuegos del núcleo y compara las entradas asociadas al puente de cada red.
Solución

Para una red normal, Docker crea el puente, le asigna un rango y programa una regla de enmascaramiento para el tráfico que sale de ese rango hacia el exterior; además permite el reenvío entre el puente y la interfaz de salida. Con internal: true el puente y el rango existen igual, pero no se programa el enmascaramiento y se bloquea el reenvío hacia fuera del puente.

La implicación práctica es la que importa: alguien con ejecución dentro del contenedor no puede “desbloquear” nada desde dentro, porque el contenedor no tiene privilegios sobre la red del anfitrión. La única forma de dar salida es modificar el compose y recrear la red, es decir, desde fuera y a propósito. Un control que solo se puede desactivar desde fuera del sistema controlado es mucho más fuerte que uno que se configura dentro.

Clave: tiene que aparecer la distinción entre “ausencia de ruta” y “regla que deniega”, y la consecuencia sobre quién puede revertirla. Si tu párrafo no dice quién puede desactivar el control, está incompleto.

Red Team · mapea el laboratorio desde dentro

Desde el contenedor atacante, y sin leer el docker-compose.yml, reconstruye la topología: qué máquinas hay en tu red, qué puertos tienen abiertos y cuál de ellas es la frontera hacia el exterior. Entrega un dibujo. Después compáralo con el compose y anota qué no pudiste ver desde dentro.

Empieza por ti mismo: qué dirección tienes y qué rango cubre tu red. Todo lo demás está ahí dentro.
El contenedor atacante ya trae nmap y utilidades de DNS instaladas.
Un barrido del rango de tu propia red te da los vecinos vivos; después, un escaneo de puertos sobre cada uno te dice qué es cada cual por lo que escucha.
Solución

El procedimiento: ip -4 addr para saber tu dirección y tu máscara, un barrido del rango para encontrar vecinos vivos, y un escaneo de puertos sobre cada uno. El 5432 identifica la base de datos, el 80 aparece dos veces (la aplicación y el lado interno del proxy), y los contenedores de utilidades no escuchan nada.

La pieza de borde se identifica por descarte y por comportamiento: es la única que responde en dos redes distintas. Lo que no puedes ver desde dentro es igual de instructivo: no ves el mapeo de puertos hacia el host, no ves que existe lab_borde ni quién está en ella, y no ves los volúmenes. Esa es exactamente la asimetría de información que tiene un atacante real cuando cae dentro de una red.

Clave: el entregable no es la lista de puertos, es la lista de lo que no pudiste deducir desde dentro. Guárdala: describe la ventaja que tiene el defensor.

Blue Team · ¿deja rastro un escaneo interno?

Repite el escaneo del ejercicio anterior mientras sigues los logs de proxy y de app. Responde: ¿qué parte del escaneo aparece en los logs y qué parte es invisible? Después propón la mínima instrumentación que haría visible lo invisible, y di qué coste tiene.

Usa docker compose logs -f en otra terminal antes de lanzar el escaneo, no después.
Un servidor web registra peticiones. Un escaneo de puertos no siempre llega a hacer una petición.
Piensa en qué capa vive cada evento: una conexión TCP que se abre y se cierra no es un evento de aplicación.
Solución

Lo que aparece: las peticiones HTTP completas, con ruta y agente. Lo que no aparece: el descubrimiento de máquinas, las conexiones que se abren y se cierran sin enviar datos, y todo el tráfico hacia servicios que no registran conexiones rechazadas. Un escaneo cuidadoso puede recorrer entera la red interna sin dejar una sola línea en los logs de aplicación.

La instrumentación mínima es registro a nivel de red: contar conexiones por origen y por puerto de destino, y alertar cuando un origen toca muchos destinos distintos en poco tiempo. El coste es volumen —mucho más que los logs de aplicación— y una tasa de falsos positivos que hay que domar con contexto: los comprobadores de salud y los orquestadores hacen exactamente ese patrón todo el día.

Clave: la conclusión que se evalúa es que los logs de aplicación no cubren la capa de red. Si tu respuesta no nombra ese hueco, el escaneo te pasó por delante sin que lo vieras.

Architecture Challenge · un laboratorio para un equipo de veinte

Diseña cómo distribuirías este laboratorio a veinte personas de una empresa para un taller de un día. Decide: dónde corre (portátil de cada uno o infraestructura compartida), cómo garantizas que nadie exponga su instancia a la red corporativa, qué haces con las credenciales de ejemplo, cómo lo destruyes al terminar y cómo demuestras que se destruyó.

Las dos opciones son legítimas y tienen riesgos opuestos: en local dependes de la disciplina de veinte personas; centralizado tienes un único objetivo muy apetecible.
Si eliges local, el control no puede ser un párrafo en un correo. Tiene que ser algo que se ejecute y falle solo.
Un script de arranque que verifique la topología antes de levantar es más barato que confiar en que nadie edite el compose.
Solución

Una propuesta razonable: local en cada portátil, porque evita crear un objetivo común y elimina la tentación de dejarlo corriendo entre sesiones. El control no es documental sino ejecutable: un script previo al arranque que revisa el compose, falla si algún ports no empieza por 127.0.0.1: y falla si algún servicio distinto del proxy lista una red sin internal: true. Ese script se ejecuta también al final, con docker compose ls, para demostrar que no queda nada arriba.

Credenciales: se generan por participante al levantar, nunca se comparten, y se destruyen con los volúmenes. El punto no es que sean secretas —son de laboratorio— sino no enseñar el hábito de reutilizar una contraseña conocida por veinte personas.

Clave: no hay una única respuesta. Se evalúa que el control sea verificable por una máquina y que exista una prueba de destrucción. “Se pidió a los asistentes que apagaran su laboratorio” no es una prueba.

13Preguntas de comprensión

Comprobación

¿Por qué el puesto de atacante es un contenedor y no tu propia terminal?

Tu máquina tiene internet, tus claves y tu historial. Un atacante que vive dentro solo puede alcanzar lo que el laboratorio le deja alcanzar.

Un compañero te pasa un laboratorio donde la aplicación vulnerable publica 3000:3000. ¿Cuál es el problema?

Sin dirección, Docker escucha en todas las interfaces. Cualquiera que comparta red contigo alcanza la aplicación vulnerable.

Dentro de lab_interna, ¿puede atacante conectarse al puerto de la base de datos?

La red interna es un muro hacia fuera, no entre las piezas. Dentro se ve todo con todo, y eso es lo que permite practicar movimiento lateral.

¿Cuál de estas comprobaciones detecta el error más grave en menos tiempo?

Un laboratorio expuesto a la red local carga perfectamente en el navegador. La única comprobación que lo detecta es mirar la dirección de los puertos publicados.

0 / 4

14Reto adicional

Escribe el verificador de topología

Escribe un script que reciba un docker-compose.yml y falle con código distinto de cero si: algún puerto publicado no empieza por 127.0.0.1:, más de un servicio pertenece a una red sin internal: true, o algún montaje apunta fuera del directorio del laboratorio. Pruébalo contra el lab-00-base —debe pasar— y contra una copia rota a propósito.

No lo hagas con búsqueda de texto: el YAML admite varias formas de escribir lo mismo y una expresión regular se te escapa por la forma larga.
Docker sabe normalizar el archivo por ti, y su salida es mucho más fácil de comprobar que el original.
Genera la forma canónica con docker compose config --format json y recorre esa estructura, no el archivo escrito a mano.
Solución

La clave del ejercicio es no leer el YAML original. docker compose config expande las formas cortas, resuelve variables y aplica los valores por defecto; sobre esa salida las tres reglas son comprobaciones directas: por cada servicio, recorrer ports y exigir host_ip; cruzar networks con la definición de redes y contar cuántos servicios tocan alguna que no sea interna; y comprobar que cada montaje de tipo enlace resuelve a una ruta dentro del directorio del laboratorio.

El detalle que separa un verificador útil de uno decorativo es el mensaje de error: tiene que decir qué servicio, qué línea conceptual y cuál es la forma correcta. Un script que solo dice “topología inválida” se desactiva la tercera vez que alguien lo ve.

Clave: guárdalo. Es el mismo patrón que vas a aplicar en la fase de CI/CD sobre manifiestos reales, y la diferencia entre este script y una política de plataforma es solo dónde se ejecuta.

15Resumen

Puntos clave
  • Un laboratorio son cinco papeles: borde, objetivo, activo, observación y atacante. Quitar uno deja un hueco concreto en lo que puedes aprender.
  • Lo que define el laboratorio no son las piezas sino la topología: quién habla con quién.
  • internal: true quita la ruta de salida sin tocar la comunicación interna. No es un filtro que se pueda equivocar: es una ausencia.
  • Solo una pieza cruza la frontera, y solo un puerto se publica, siempre con 127.0.0.1: delante.
  • Publicar un puerto sin dirección expone tu laboratorio vulnerable a toda la red donde estés conectado, y el firewall del sistema puede no salvarte.
  • El atacante vive dentro. Si atacas desde tu terminal, estás midiendo la conectividad de tu portátil, no la del laboratorio.
  • Verifica en vez de suponer: los puertos con ps, la salida con un curl desde dentro, la visibilidad con el DNS interno.
¿Qué cambia exactamente internal: true en una red de Docker?
Docker no programa la ruta ni el enmascaramiento de salida. Los contenedores se siguen viendo entre ellos y resolviendo por nombre; simplemente no tienen por dónde salir.
¿Qué diferencia hay entre 8080:80 y 127.0.0.1:8080:80?
La primera publica en todas las interfaces de la máquina, así que cualquiera de tu red local llega. La segunda solo acepta conexiones de procesos de la propia máquina.
¿Cuántas piezas de un laboratorio deben tener dos redes, y por qué?
Una: el proxy. Es la frontera de confianza, y tenerla en un solo sitio significa que auditar el aislamiento son cuatro líneas del compose.
¿Cómo se encuentran dos servicios del mismo compose sin conocer direcciones?
Por el DNS interno de la red: el nombre del servicio es el nombre de host. Por eso el laboratorio es reproducible en cualquier máquina.

16Mis notas

Cierre de la lección

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.