El puerto que el firewall no protegía
El firewall decía que sólo pasaban tres puertos. Un contenedor levantado con el archivo equivocado publicó una base sin contraseña y en dos minutos entró un gusano de minería. Por qué Docker se salta las reglas del firewall del sistema y cómo quedó cerrado.
Un servidor con una decena de aplicaciones, operado por una sola persona. El firewall del sistema —el de siempre, el que se configura con tres comandos— decía exactamente lo que tenía que decir: abiertos 22, 80 y 443, todo lo demás cerrado.
Dos minutos después de un despliegue mal hecho, una base de datos en memoria sin contraseña estaba respondiendo desde internet, con claves nuevas apuntando a un proceso de minería. El firewall seguía diciendo que sólo pasaban tres puertos. No mentía: nunca estuvo mirando los contenedores.
Por qué el firewall no ve a los contenedores
En Linux, tanto el firewall de usuario como Docker terminan escribiendo reglas en la misma maquinaria de filtrado del kernel. La diferencia está en el orden.
El recorrido de un paquete que llega de internet
- Entra por la interfaz de red del servidor.
- Pasa por la cadena de reenvío hacia contenedores, donde Docker inserta sus propias reglas al publicar un puerto.
- Esas reglas se evalúan antes que las del firewall de usuario, que está pensado para el tráfico dirigido al host.
- El paquete llega al contenedor. El firewall nunca tuvo la oportunidad de rechazarlo.
Por eso ports: "6379:6379" en un archivo de composición abre ese puerto a internet aunque el
firewall diga lo contrario. No es un bug: es la consecuencia de que Docker administre su propio
reenvío. Y es exactamente la clase de detalle que uno descubre el día que importa.
Qué pasó exactamente
El disparador no fue un ataque sofisticado, fue un comando de más:
La secuencia
- Se recreó una aplicación combinando su archivo de composición de desarrollo con el de producción.
- Al combinar archivos, las listas de puertos se suman, no se reemplazan. Y como ambos archivos resolvían al mismo nombre de proyecto, los contenedores recreados fueron los de producción.
- Producción quedó corriendo con los puertos de desarrollo publicados: base de datos, almacenamiento de objetos y una base en memoria.
- Esa base en memoria, pensada para una red privada, no pedía contraseña.
- En unos dos minutos entró un gusano automatizado, dejó claves con un cron de minería y ejecutó un borrado total de la base.
Nada llegó a ejecutarse en el host: el intento de persistencia dependía de escribir en lugares a los que el contenedor no tenía acceso. Pero el borrado se llevó puesto el registro que usaba la cola de trabajos para saber en qué tick iba, y el planificador quedó muerto en silencio. Ese fue el daño real: no la intrusión, sino una funcionalidad que dejó de correr sin que nada avisara.
Antes de seguir, predecí
Cómo quedó cerrado
El arreglo tiene dos mitades: una que bloquea el tráfico aunque alguien vuelva a publicar un puerto, y otra que avisa cuando eso pasa.
| Medida | Qué resuelve | Qué no resuelve |
|---|---|---|
| Reglas propias en la cadena que Docker consulta primero | Sólo se aceptan 80, 443 y un puerto de mensajería desde la red pública | Un contenedor que salga hacia afuera |
| Atar los puertos de desarrollo a la dirección local | Que un `up` sin argumentos no pueda exponer nada | El error de combinar archivos |
| Nombre de proyecto distinto en los archivos de desarrollo | Que desarrollo no pueda recrear los contenedores de producción | Nada del firewall |
| Aviso automático ante un puerto publicado fuera de la lista | Enterarse en minutos, no en semanas | El tiempo entre que se abre y llega el aviso |
| Contraseña en las bases, aunque estén en red privada | Que publicar por error no sea fatal | La exposición en sí |
La auditoría, en cambio, es de una línea: recorrer los contenedores en ejecución preguntando qué puertos publica cada uno. En un servidor sano, la respuesta debería ser aburrida —el proxy y poco más—.
El incidente en el log
Buscá en el log
El firewall del sistema decía que sólo estaban abiertos 22, 80 y 443. ¿Qué línea muestra por qué la base quedó igual expuesta?
debug 2info 6warn 2error 212 de 12
La publicación de puertos del contenedor escribió su propia regla de reenvío en una cadena que se evalúa antes que la del firewall del sistema. No es un agujero ni un error del firewall: son dos motores escribiendo en la misma tabla, y el de los contenedores gana. Por eso la única verificación que vale es desde afuera del servidor, y la única defensa que no depende de acordarse es no publicar el puerto: que la base escuche en la red interna de contenedores y nada más.
La regla que lo hace imposible
Cierre
Autoevaluación
¿Lo entendiste?
Práctica