Fundamentos · Lección 02

Superficie de ataque y fronteras de confianza

Casi nadie sabe de memoria por cuántos sitios se entra a su propio sistema. Se sabe el puerto principal, se sabe el dominio, y a partir de ahí empieza la niebla: el endpoint de depuración que quedó, la cabecera que la aplicación se cree, la dependencia que ejecuta código en el arranque, el compañero que tiene la clave. Esta lección te da dos herramientas para disipar esa niebla: enumerar la superficie de ataque y dibujar las fronteras de confianza. Vas a hacerlo sobre el lab-00-base, que es lo bastante pequeño como para caber entero en una hoja y lo bastante realista como para que la hoja duela.

Al terminar sabrás

  • Enumerar la superficie de ataque de un sistema sin quedarte solo en los puertos.
  • Distinguir superficie expuesta de superficie alcanzable, y por qué la segunda es la que importa.
  • Localizar una frontera de confianza preguntando por el dato, no por la topología.
  • Reconocer las fronteras implícitas: encabezados, variables de entorno, dependencias, colas.
  • Escribir un inventario de superficie que un compañero pueda revisar y un pipeline pueda validar.
  • Demostrar sobre el laboratorio que “no publicado” y “no alcanzable” no son lo mismo.

1Por qué importa

En f01-cia-riesgo aprendiste a escribir un riesgo con actor, vía, activo y consecuencia. El campo que más cuesta rellenar es la vía, y no por falta de vocabulario: se atasca porque nadie tiene la lista completa de vías. Sin esa lista, la estimación de probabilidad es una encuesta de opinión.

La superficie de ataque es esa lista. Y tiene una propiedad que la hace especialmente valiosa como métrica de ingeniería: es la única cifra de seguridad que puedes bajar a propósito. No puedes reducir el número de atacantes que hay en el mundo ni el valor de tus datos. Sí puedes borrar el endpoint que nadie usa, cerrar el puerto que quedó abierto y quitar la dependencia que solo se usaba en una pantalla que ya no existe. Cada una de esas acciones elimina riesgo entero, no lo mitiga: no hay control que fallar, porque no hay superficie.

Las fronteras de confianza son la otra mitad. La superficie te dice por dónde se entra; la frontera te dice dónde deja de ser válido dar algo por supuesto. Casi todos los fallos de validación que vas a ver en el resto del curso —inyección de comandos, path traversal, control de acceso roto— tienen la misma forma: alguien validó en un lado de la frontera y consumió en el otro, o directamente no vio que había una frontera ahí.

El objetivo real

El entregable de esta lección no es una definición: es un dibujo. Una hoja con cajas, líneas discontinuas donde cambia la confianza, y una cruz en cada punto por el que entran datos que tú no escribiste. Si ese dibujo no existe, la arquitectura de seguridad del sistema vive en la cabeza de alguien, y esa persona se va a ir del equipo.

2Conceptos

Los cinco términos
  • Superficie de ataque: el conjunto de puntos por los que un actor no confiable puede meter datos, sacarlos o provocar una acción.
  • Superficie expuesta: lo que se anuncia al mundo. Un puerto publicado, un dominio, un formulario.
  • Superficie alcanzable: lo que un actor concreto puede tocar desde donde está. Es siempre distinta para cada actor.
  • Frontera de confianza: el punto donde cambia el nivel de confianza de los datos o de quien los produce. Ahí, y solo ahí, es obligatorio validar.
  • Actor de amenaza: quién intenta cruzar. Se describe por acceso inicial, capacidad y motivación, no por adjetivos.

3Explicación profunda

La superficie de ataque se suele explicar con puertos porque los puertos se cuentan fácil. Es una simplificación que deja fuera la mayor parte del problema. Una superficie completa tiene al menos cinco familias, y la lista de puertos solo cubre la primera.

FamiliaEjemplosCómo se enumera
RedPuertos que escuchan, protocolos, servicios de descubrimientoss -lntup, docker compose ps, escaneo desde dentro
AplicaciónRutas HTTP, parámetros, campos de formulario, encabezados que se leen, ficheros que se subenRutas del enrutador, especificación de la API, registro de peticiones
Datos y mensajesColas, webhooks, importaciones de CSV, mensajes de otro servicioGrep de consumidores, esquema de eventos
Cadena de suministroDependencias, imágenes base, acciones del pipeline, extensiones del editorFichero de bloqueo, docker image inspect, permisos del pipeline
Humana y de operaciónSoporte que resetea contraseñas, claves en portátiles, accesos de proveedoresLista de quién puede hacer qué sin revisión

La distinción que más cambia decisiones es expuesta frente a alcanzable. La superficie expuesta es una propiedad del sistema; la alcanzable es una propiedad de la pareja sistema-actor. Un endpoint de administración sin autenticar que solo escucha en la red interna tiene superficie expuesta cero hacia internet y superficie alcanzable total para cualquiera que ya haya entrado. Contar solo lo expuesto es exactamente el error que convierte una intrusión pequeña en un incidente completo, porque el segundo salto no encuentra ninguna resistencia.

Las fronteras de confianza se localizan con una pregunta muy concreta, y no es “¿dónde está el cortafuegos?”. Es: “¿este dato lo escribió alguien de quien me puedo fiar?”. Donde la respuesta cambia de sí a no, hay frontera. Eso implica que una frontera puede no coincidir con ningún límite físico ni de red. La frontera entre tu código y el contenido de un fichero que tu propio proceso acaba de leer es real si ese fichero lo subió un usuario.

De ahí sale la regla operativa más útil de la lección: la validación pertenece a la frontera, no al principio del programa. Validar “en la entrada” funciona mientras solo haya una entrada. En cuanto el mismo dato llega también por una cola, por una importación nocturna o por un endpoint interno que alguien añadió para depurar, la validación de la entrada principal deja de cubrir nada y nadie se entera, porque los tests siguen pasando.

Hay un tipo de frontera que se rompe con una frecuencia desproporcionada: la frontera implícita en un encabezado. Un proxy inverso añade información sobre el cliente y la aplicación de detrás la trata como verdad porque “viene del proxy”. El problema es que parte de esa información la escribió el cliente y el proxy se limitó a reenviarla. La confianza se hereda por el canal cuando debería depender del origen del dato. Vas a verlo en el laboratorio, en un fichero de doce líneas.

Una opinión de diseño, para que quede marcada como tal: la superficie de ataque es una métrica de producto, no de seguridad. Crece cuando se añaden funciones y baja cuando se retiran, y quien decide ambas cosas es quien prioriza el roadmap. Un equipo de seguridad que intenta reducirla sin poder retirar funciones solo puede añadir controles, que es la forma cara de hacerlo. El contraejemplo honesto: hay superficie que no se puede quitar porque es el producto. El formulario de registro de una tienda no se retira; se defiende. La lección de esto es que hay que separar la superficie esencial de la accidental antes de gastar dinero.

4Ejemplo sencillo

# Una casa, otra vez. Superficie de ataque = todo por donde entra algo.

puerta principal      esperada, con cerradura       superficie esencial
ventana del bano      no la miras nunca             superficie accidental
buzon                 entra papel de desconocidos   frontera de confianza
llave del vecino      confianza delegada            frontera humana
tuberia del gas       la instalo un tercero         cadena de suministro

# Y la diferencia entre expuesta y alcanzable

expuesta      lo que se ve desde la calle: puerta, ventanas, buzon
alcanzable    lo que puede tocar QUIEN YA ESTA DENTRO: todas las puertas
              interiores, que no tienen cerradura
El buzón es el ejemplo perfecto de frontera de confianza: es una entrada legítima, diseñada, que acepta contenido escrito por cualquiera. No se cierra, se trata con desconfianza lo que sale de él.

Fíjate en lo que hace incómodo el ejemplo: las puertas interiores. Nadie pone cerradura a la puerta de la cocina, porque la cerradura de la calle “ya resuelve eso”. Ese razonamiento es exactamente el modelo de perímetro, y es la razón de que el segundo movimiento de un atacante sea casi siempre más fácil que el primero.

5Ejemplo técnico

El lab-00-base tiene cinco servicios, dos redes y exactamente un puerto publicado. Parece una superficie mínima. Vamos a enumerarla de verdad, por familias, y a contar cuántas fronteras hay.

Punto de entradaQuién llega¿Frontera?Qué se valida hoy
127.0.0.1:8080 → proxyCualquier proceso de tu máquinaSí, la principalNada: nginx reenvía todo a la app
Encabezados HTTP reenviadosEl mismo cliente anteriorSí, y es implícitaNada: X-Forwarded-For arrastra lo que envió el cliente
proxy → app por lab_internaSolo el proxy… en teoríaNo hay ningunaLa app no sabe quién le habla
Cualquier contenedor → db:5432app, monitor y atacanteSolo la contraseñaUna contraseña compartida por todos
Imágenes base y paquetes de apk addQuien controle esos repositoriosSí, cadena de suministroNada: no hay fijación de versión ni verificación

El punto más instructivo es el segundo. Mira estas dos líneas del conf/proxy.conf del laboratorio; están juntas y hacen cosas profundamente distintas:

proxy_set_header X-Real-IP         $remote_addr;
proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;

$remote_addr lo calcula nginx a partir del socket: no se puede falsear con un encabezado. Está del lado de dentro de la frontera. $proxy_add_x_forwarded_for, en cambio, es el valor que envió el cliente, más $remote_addr al final. Si el cliente manda su propio X-Forwarded-For, ese texto viaja hasta la aplicación con aspecto de haberlo puesto la infraestructura. Una aplicación que lea el primer valor de la lista para decidir si una petición “viene de dentro” está leyendo una entrada de usuario y creyéndose que es un dato de red.

Ese es el patrón general, y conviene nombrarlo: la confianza no se hereda del canal, se hereda del origen del dato. Que un dato llegue por un canal confiable no dice nada de quién lo escribió. Este error reaparece en tokens reenviados, en mensajes de cola con un campo usuario_id, y en cualquier cabecera que empiece por X-.

6Diagrama

Tres zonas del laboratorio: host, red de borde y red interna, con dos fronteras de confianza marcadas y ninguna frontera entre los servicios internos tu maquina navegador lab_borde proxy lab_interna · internal: true app db monitor atacante sin frontera: todos con todos F1 · 127.0.0.1:8080 F2 · encabezados
El laboratorio solo tiene dos fronteras, y la segunda es invisible en el compose: vive en dos líneas del fichero de nginx. Dentro de la tercera zona no hay ninguna, y por eso el contenedor atacante puede hablar con la base de datos.
Ver el mismo diagrama como código Mermaid (editable)
flowchart LR
    subgraph HOST[tu maquina]
        N[navegador]
    end
    subgraph BORDE[lab_borde]
        P[proxy]
    end
    subgraph INTERNA[lab_interna internal true]
        A[app]
        D[db]
        M[monitor]
        X[atacante]
    end
    N -->|F1 127.0.0.1:8080| P
    P -->|F2 encabezados| A
    A --- D
    M --- D
    X --- D
    X --- A

7Código

Un dibujo se desincroniza en un trimestre. Un fichero versionado junto al código, no. Este inventario de superficie es el equivalente al riesgos.yml de la lección anterior, y se revisa en la misma revisión de código que lo cambia:

# superficie.yml — inventario de entradas, versionado con el codigo
- punto: proxy/http
  familia: red
  escucha: 127.0.0.1:8080
  alcanzable_por: cualquier proceso local
  frontera: si
  valida: nada        # <-- hallazgo, no descripcion
  esencial: si          # es la puerta del laboratorio

- punto: encabezado X-Forwarded-For
  familia: aplicacion
  origen: cliente, reenviado por el proxy
  frontera: si          # implicita: parece infraestructura
  valida: nada
  esencial: no          # se puede sustituir por $remote_addr

- punto: db:5432
  familia: red
  alcanzable_por: app, monitor, atacante
  frontera: no          # y deberia haberla
  valida: contrasena compartida
  esencial: parcial     # la app si, los otros dos no

Dos campos hacen todo el trabajo. alcanzable_por obliga a nombrar actores en vez de decir “interno”, que es una palabra que no significa nada verificable. Y esencial separa lo que hay que defender de lo que hay que borrar: en cuanto un punto está marcado como no esencial y sigue vivo un trimestre después, tienes una conversación concreta que tener con quien prioriza.

8Qué puede salir mal

Los cuatro errores clásicos

Confundir “no publicado” con “no alcanzable”. El servicio db del laboratorio no tiene ports, y cualquiera diría que está fuera de la superficie. Está a un comando de distancia desde tres contenedores. Lo que reduce superficie no es la ausencia de ports, es la ausencia de ruta.

Inventariar solo lo que se ve desde fuera. Un escaneo desde internet produce una lista tranquilizadora y falsa. La superficie hay que enumerarla también desde dentro, desde el puesto de cada actor posible: un contenedor comprometido, un empleado, un proveedor con VPN.

Dibujar fronteras donde están los cortafuegos. La frontera la define el origen del dato, no la topología. Un fichero subido por un usuario y guardado en tu propio disco sigue siendo dato hostil cuando lo lees, aunque ya no haya ninguna red por medio.

Dejar que la superficie crezca sin que nadie lo note. Cada dependencia nueva, cada endpoint de depuración, cada integración “temporal” añade puntos. Sin un inventario que cambie en el mismo commit, la superficie real y la superficie conocida divergen a partir del primer mes.

9Cómo lo abusaría un atacante

Vista desde el otro lado

Lo primero que hace alguien que ataca es exactamente lo mismo que acabas de hacer tú: enumerar. La diferencia es que él no tiene el docker-compose.yml, así que lo reconstruye a base de respuestas: qué rutas devuelven 404 y cuáles 403, qué cabeceras revelan la versión, qué tiempo tarda un login con usuario válido frente a uno inválido. Cada diferencia observable es un trozo del mapa.

Después busca la frontera peor dibujada, que casi nunca es la principal. La puerta de entrada está mirada por todo el mundo. Lo que no está mirado es el endpoint interno que se añadió para un script de migración, el consumidor de la cola que se fía del campo rol del mensaje, o la cabecera que decide si la petición “viene de dentro”. Ahí es donde una petición cuidadosamente construida cruza sin que nadie valide nada.

Y el tercer movimiento es el que hace daño de verdad: una vez dentro de la zona donde no hay fronteras, la superficie alcanzable se multiplica. En el laboratorio, controlar el contenedor app pone la base de datos, el monitor y el atacante a un salto, con una contraseña que está escrita en el mismo fichero que ya se leyó. Ese salto no requiere ningún exploit: requiere que nadie haya dibujado una frontera interior.

10Cómo defenderlo

Qué hacer con esto mañana
  • Enumera desde cada actor, no solo desde internet: desde un contenedor cualquiera, desde una cuenta de empleado, desde el pipeline.
  • Marca cada punto como esencial o accidental. Lo accidental se borra; discutir controles para algo que se puede borrar es tirar dinero.
  • Pon la validación en la frontera, no en la entrada principal. Si el mismo dato entra por tres sitios, la comprobación va donde se consume.
  • Trata los encabezados reenviados como entrada de usuario. Si necesitas la dirección real, usa la que calcula el servidor a partir del socket y define qué proxies son de confianza.
  • Convierte el inventario en una comprobación del pipeline: un punto nuevo sin entrada en superficie.yml es un fallo de revisión, no un descuido.
  • Mide la superficie como número y vigila la tendencia. Un equipo que solo la ve crecer nunca ha retirado nada, y eso es un dato de gestión.

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.

Es el mismo laboratorio de la lección anterior. No hace falta tocar ningún fichero para este recorrido: solo mirar.

cd cursos/seguridad/labs/lab-00-base docker compose up -d docker compose ps --format "table {{.Service}}\t{{.Ports}}"

Esa última columna es la superficie expuesta: una sola línea. Ahora enumera la superficie alcanzable desde el puesto del atacante, que es un actor que ya está dentro:

enumerar desde dentro
docker compose exec atacante sh -c "nmap -Pn -p 80,5432 app db proxy monitor" Nmap scan report for app PORT STATE SERVICE 80/tcp open http 5432/tcp closed postgresql Nmap scan report for db PORT STATE SERVICE 80/tcp closed http 5432/tcp open postgresql # la base de datos NO publica puerto en el host y aun asi # esta abierta para un contenedor cualquiera de lab_interna

Ahora la frontera implícita. Manda un X-Forwarded-For propio y mira qué llega al otro lado del proxy:

cruzar la frontera F2
docker compose exec atacante sh -c "curl -s -o /dev/null -H 'X-Forwarded-For: 127.0.0.1' http://proxy/" docker compose logs --tail 2 app lab00-app | GET / HTTP/1.0" 200 - "-" "curl/8.9.1" # nginx anadio su propia direccion DETRAS de la que enviaste: # el primer valor de la lista lo eligio el cliente, no el proxy

Con eso ya tienes las dos mitades: una superficie que es cinco veces mayor de lo que dice ports, y una frontera que no aparece en el compose porque vive en conf/proxy.conf. Cuando termines:

docker compose down -v

12Ejercicios

Clasifica seis entradas

Para cada punto del lab-00-base, di a qué familia de superficie pertenece y si hay o no una frontera de confianza en él: (1) el puerto 127.0.0.1:8080; (2) el volumen ./app montado en modo solo lectura; (3) el encabezado X-Forwarded-For; (4) el apk add del contenedor atacante; (5) la variable POSTGRES_PASSWORD del compose; (6) el endpoint /salud del proxy.

Para cada uno pregúntate quién escribió el contenido que entra por ahí, no por dónde entra.
Dos de los seis son cadena de suministro aunque no lo parezcan: piensa en quién controla lo que se descarga y quién escribió el fichero montado.
El caso (6) es el interesante: es superficie mínima, pero tiene access_log off, así que además es un punto ciego.
Solución

(1) red, frontera sí, es la principal. (2) datos, frontera sí si ese contenido no lo escribiste tú; el :ro reduce el impacto pero no elimina la frontera. (3) aplicación, frontera sí e implícita. (4) cadena de suministro, frontera sí: se ejecuta código de terceros en el arranque, sin versión fijada. (5) operación y datos: no es una entrada, es un secreto en claro que amplía el impacto de cualquier lectura del repositorio. (6) red, superficie mínima y sin frontera de datos, pero con registro desactivado.

Clave: cinco de los seis son superficie y solo uno era obvio. Si tu primera lista tenía únicamente el puerto 8080, acabas de ver el problema entero de la lección.

Mide cuánto crece la superficie con una línea

Con el laboratorio levantado, edita el docker-compose.yml y publica el puerto de db en el host, con el binding a 127.0.0.1 que exige el curso. Levanta de nuevo, comprueba desde el host qué es alcanzable ahora que antes no lo era, escribe la entrada correspondiente en tu superficie.yml, y revierte. Responde: ¿cuántos actores nuevos aparecieron en alcanzable_por?

Antes de tocar nada, apunta qué responde el host en ese puerto. Necesitas el “antes” para poder afirmar el “después”.
Un puerto publicado en 127.0.0.1 no lo alcanza la red local, pero sí lo alcanza cualquier proceso de tu máquina, incluido el navegador y cualquier dependencia de desarrollo.
El comando para el “después” es un cliente de postgres o simplemente nc -z 127.0.0.1 5432 desde el host; luego docker compose up -d tras revertir.
Solución

La línea es ports: ["127.0.0.1:5432:5432"] en el servicio db. Después del cambio, alcanzable_por pasa de “app, monitor, atacante” a “app, monitor, atacante y cualquier proceso de tu usuario en el host”. Eso incluye cosas que no piensas como atacantes: un script de npm, una extensión del editor, un navegador con una pestaña abierta que pueda hacer peticiones a localhost.

La entrada de inventario correcta marca esencial: no, porque el acceso a la base se puede obtener con docker compose exec db psql sin publicar nada. Ese es el patrón que se repite en producción: se publica un puerto por comodidad de depuración y se queda publicado para siempre.

Clave: revierte y comprueba que el puerto vuelve a estar cerrado desde el host. El entregable es la entrada de superficie.yml, con el campo esencial justificado.

Publicar un puerto y el cortafuegos del host

Investiga fuera de esta lección: cuando Docker publica un puerto sin especificar dirección, ¿en qué interfaces queda escuchando, y qué relación tiene eso con las reglas del cortafuegos del sistema en Linux? Explica por qué una regla de denegación por defecto configurada con la herramienta habitual del sistema puede no aplicarse a un contenedor, y qué diferencia hay con macOS o Windows, donde Docker corre dentro de una máquina virtual. Termina con la regla práctica que usarías en cualquier compose de laboratorio.

La pregunta de fondo es en qué punto de la cadena de filtrado se insertan las reglas que crea el motor de contenedores, y si el tráfico reenviado pasa por las mismas cadenas que el tráfico dirigido al host.
Busca la documentación del propio motor de contenedores sobre filtrado de paquetes y cortafuegos; el término que necesitas es la cadena de reenvío, no la de entrada.
Empieza levantando el laboratorio con el puerto del proxy publicado sin prefijo de dirección y comprobando desde otra máquina de tu red si responde; luego compara con el compose original.
Solución

Sin prefijo de dirección, la publicación queda escuchando en todas las interfaces, de modo que el servicio es alcanzable desde cualquier equipo de la red local. En Linux, el motor de contenedores inserta sus propias reglas en la cadena de reenvío, que es un camino distinto del que filtra el tráfico dirigido al propio host: por eso una política de denegación por defecto configurada solo para la entrada al host deja pasar el tráfico hacia los contenedores. En macOS y Windows el motor corre dentro de una máquina virtual y la publicación la hace un proceso del host, así que el cortafuegos del sistema sí lo ve, pero el resultado desde la red sigue siendo el mismo: el puerto responde.

Regla práctica: en un laboratorio, todo ports lleva 127.0.0.1: delante, sin excepciones, y se revisa en la revisión de código igual que se revisa un secreto. Es lo que hace el lab-00-base, y es la diferencia entre practicar y ser un problema para la gente conectada al mismo wifi.

Clave: la respuesta debe distinguir el camino de reenvío del camino de entrada al host. Un argumento que solo diga “usa el cortafuegos” no ha entendido por qué el cortafuegos no se enteró.

Red Team · dibuja el mapa sin mirar el compose

Dentro del lab-00-base, y solo dentro de él: ponte en el papel de quien ya controla el contenedor atacante y no tiene el docker-compose.yml. Reconstruye el mapa completo —cuántos servicios hay, qué escucha cada uno y cuál cruza a la otra red— usando únicamente lo que se ve desde ese puesto. Después compara tu mapa con el fichero real y anota qué no pudiste descubrir.

Tienes resolución de nombres dentro de la red del laboratorio: los nombres de servicio se resuelven. Empieza por ahí antes de escanear nada.
El contenedor trae nmap y bind-tools. Un barrido de puertos habituales y una consulta de nombres te dan casi todo el mapa.
Para saber cuál de los servicios tiene salida a internet, prueba desde cada uno con docker compose exec <servicio> sh -c "wget -q -T 5 -O- http://localhost/" y compara con el que sí resuelve nombres externos.
Solución

Desde atacante se descubren los cuatro compañeros por nombre, se ve el 80/tcp de app y de proxy y el 5432/tcp de db, y se puede comprobar que ninguno de los contenedores de la red interna resuelve nombres externos. Lo que no se descubre desde ahí es que el proxy tiene un segundo pie en lab_borde ni que el host publica el 8080 en 127.0.0.1: eso solo se ve desde el otro lado de la frontera.

Esa asimetría es el punto del ejercicio. El mapa de un atacante interno es muy bueno hacia los lados y muy pobre hacia arriba. Por eso los controles que más frenan el movimiento lateral son los que no se pueden enumerar desde dentro.

Clave: el entregable son dos listas: lo que descubriste y lo que no. La segunda lista es la que te dice qué defensas siguen siendo invisibles para quien ya entró.

Blue Team · caza el encabezado falseado en los logs

Genera tráfico normal contra el proxy y, mezcladas, dos peticiones con un X-Forwarded-For puesto por el cliente. Ahora mira docker compose logs proxy y responde: con el formato actual, ¿puedes distinguir las peticiones falseadas de las normales? Si no, ¿qué cambio mínimo en conf/proxy.conf lo haría posible sin añadir ninguna herramienta nueva?

El formato combined registra la dirección del cliente y la petición, pero no registra los encabezados que el cliente envió.
nginx permite definir un formato de registro propio y meter en él el valor de cualquier encabezado recibido.
Empieza por registrar juntas la dirección que calcula el servidor a partir del socket y la lista completa que llegó en el encabezado; la discrepancia entre ambas es la señal.
Solución

Con combined no se distingue: el encabezado no aparece en el registro. El cambio mínimo es un log_format propio que incluya, además de lo habitual, el encabezado recibido tal cual y la dirección que calcula el servidor. Cuando el encabezado trae más de un valor y el primero no coincide con ningún proxy que tú controles, la petición está declarando un origen que nadie ha verificado.

La corrección de fondo no es de registro, es de configuración: o se sobrescribe el encabezado con la dirección real en el borde, o se declara explícitamente qué proxies son de confianza para que el servidor descarte el resto de la lista. Registrar sin corregir solo te da una alerta que se repite todos los días.

Clave: el entregable es el log_format y una frase que diga qué patrón concreto dispara la alerta. “Vigilar el encabezado” no es una regla de detección.

Architecture Challenge · un inventario de superficie que no se pudra

Diseña cómo mantendrías superficie.yml sincronizado con un sistema que cambia todas las semanas. Define: qué parte se genera automáticamente y cuál se escribe a mano y por qué, qué comprobación del pipeline detecta un punto de entrada nuevo sin entrada en el inventario, qué hacer con los puntos marcados como no esenciales que llevan meses vivos, y cómo evitas que el fichero se convierta en un trámite que todo el mundo aprueba sin leer.

Parte de la superficie es derivable del código: rutas del enrutador, puertos del compose, dependencias del fichero de bloqueo. Otra parte no lo es en absoluto.
Lo que se genera solo se compara; lo que se escribe a mano se revisa. Piensa en un fichero con dos zonas y reglas distintas para cada una.
Un punto no esencial que sobrevive un trimestre no es un problema técnico, es una decisión de producto que nadie ha tomado. Diseña cómo se hace visible esa decisión.
Solución

Una propuesta razonable: dos zonas. La zona derivada la genera el pipeline a partir del código —puertos declarados, rutas registradas, dependencias directas— y el pipeline bloquea cuando lo generado y lo comprometido no coinciden, porque regenerar es trivial. La zona escrita a mano contiene justo lo que ninguna herramienta puede saber: quién alcanza cada punto, si hay frontera, si es esencial. Esa zona no se puede validar sola, así que se revisa como se revisa el código, y solo se exige actualizarla cuando la zona derivada cambia.

Para los puntos no esenciales, un informe periódico con su antigüedad, que va al mismo sitio donde se prioriza el trabajo de producto y no al canal de seguridad. Contra el trámite vacío: que el inventario no sea un fichero de solo lectura sino la fuente de la que salen los objetivos de las pruebas —si un punto está inventariado, alguien lo prueba; si no está, no se prueba y eso se nota.

Clave: no hay una única respuesta. Se evalúa que separes lo derivable de lo humano, que solo bloquees lo que es barato de arreglar, y que el inventario tenga un consumidor real además de la auditoría.

13Preguntas de comprensión

Comprobación

El servicio db del laboratorio no publica ningún puerto en el host. ¿Está fuera de la superficie de ataque?

Lo que reduce superficie es la ausencia de ruta, no la ausencia de ports. Para el actor “contenedor comprometido”, la base está abierta.

El proxy rellena X-Forwarded-For con $proxy_add_x_forwarded_for. ¿Por qué eso es una frontera mal dibujada si la aplicación se fía del primer valor?

La confianza no se hereda del canal, se hereda del origen del dato. El proxy reenvía texto de usuario con aspecto de dato de infraestructura.

Tienes un endpoint de depuración que nadie usa. ¿Cuál de estas acciones reduce superficie de ataque de verdad?

Un punto borrado no tiene control que pueda fallar. Filtrar y documentar son útiles para lo esencial; para lo accidental son gasto.

¿Qué define una frontera de confianza?

Por eso una frontera puede caer dentro de un mismo proceso: leer un fichero subido por un usuario cruza una frontera sin tocar la red.

0 / 4

14Reto adicional

Dibuja la superficie de este propio curso

Estas páginas son ficheros estáticos, guardan tu progreso en el navegador y declaran una política de contenido que puedes leer en el head. Enumera su superficie de ataque por familias y dibuja sus fronteras de confianza. ¿Cuántas hay? ¿Cuál es la única pieza de terceros que puede ejecutarse, y qué la contiene? ¿Qué entrada de usuario existe, y quién la consume?

Sin servidor propio, la familia “red” casi desaparece y el peso se traslada a la cadena de suministro y al navegador.
Mira la directiva de la política de contenido que menciona marcos: ahí hay una frontera declarada de forma explícita.
Las notas que escribes en cada lección son entrada de usuario. Pregúntate quién las lee después y con qué mecanismo se muestran.
Solución

Familias: red, prácticamente nula al servirse ficheros estáticos; aplicación, reducida a lo que el propio navegador guarda y vuelve a leer; cadena de suministro, que es la grande: las fuentes externas, la hoja de estilos remota y el sistema de comentarios. Fronteras: la política de contenido es una frontera escrita, y la más nítida es la del marco de comentarios, que solo se carga si lo pides y vive aislado en su propio origen.

La entrada de usuario son tus notas y tus respuestas, y las consume el propio navegador al repintar la página. La frontera está en cómo se insertan: si se escribieran como marcado en vez de como texto, tus propias notas serían un vector contra ti mismo. Que la política prohíba el código en línea hace que ese error, si existiera, no llegue a ejecutarse. Ese es el valor real de una política restrictiva: no impide el fallo, impide que el fallo sirva de algo.

Clave: el ejercicio se supera si tu mapa incluye la cadena de suministro. Un mapa que solo tenga “no hay servidor, no hay superficie” se ha saltado la mitad más probable.

15Resumen

Puntos clave
  • La superficie de ataque es la única cifra de seguridad que puedes bajar a propósito: lo accidental se borra, no se defiende.
  • Enumerarla solo por puertos deja fuera cuatro familias: aplicación, datos, cadena de suministro y personas.
  • Expuesta es una propiedad del sistema; alcanzable lo es de la pareja sistema-actor, y es la que decide el segundo salto.
  • Una frontera de confianza está donde cambia el origen del dato, no donde cambia la topología.
  • La validación pertenece a la frontera. Validar “en la entrada” deja de cubrir en cuanto hay una segunda entrada.
  • La confianza no se hereda del canal: un encabezado reenviado por un proxy sigue siendo entrada de usuario.
¿Qué diferencia hay entre superficie expuesta y superficie alcanzable?
La expuesta es lo que se anuncia al mundo; la alcanzable es lo que un actor concreto puede tocar desde donde está. La segunda cambia con cada actor y es la que explica el movimiento lateral.
¿Cómo se localiza una frontera de confianza?
Preguntando por cada dato: “¿lo escribió alguien de quien me puedo fiar?”. Donde la respuesta cambia de sí a no, hay frontera, aunque no haya red por medio.
¿Por qué un encabezado añadido por el proxy puede no ser de fiar?
Porque parte de su contenido lo escribió el cliente y el proxy solo lo reenvió. La confianza depende del origen del dato, no del canal por el que llega.
¿Qué campo del inventario de superficie decide si algo se defiende o se borra?
esencial. Lo esencial es producto y hay que protegerlo; lo accidental se retira, y así desaparece el riesgo entero en vez de mitigarse.

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.