Todo parece bloqueado. Tu escaneo IPv4 está limpio. El ticket de cambio dice “firewall habilitado”. Entonces un cliente envía por correo una captura de pantalla de tu pantalla de inicio de sesión —por IPv6. Eso no es una laguna teórica; es real, y aparece justo cuando menos la quieres: durante auditorías, incidentes o un perezoso mantenimiento del domingo.
Ubuntu 24.04 facilita quedar medio protegido. No porque esté roto, sino porque los valores por defecto, las capas de herramientas y la realidad cloud crean la trampa perfecta de “juro que lo bloqueamos”. Esta es la guía práctica para cerrar el agujero correctamente —nftables, UFW, servicios y el borde de la red— para que dual-stack signifique “seguro en ambas pilas”, no “sorpresa, dejamos una puerta lateral abierta”.
Qué falla realmente: el patrón de “exposición en la sombra” de IPv6
El modo de fallo es aburrido y consistente:
- Despliegas reglas de firewall que claramente bloquean el tráfico entrante IPv4.
- El host tiene una dirección IPv6 globalmente enrutables (o un /64 en la interfaz).
- Tu herramienta de firewall o bien no se aplica a IPv6, se aplica de forma distinta, o la salta otra capa.
- Un servicio se enlaza a
::(todas las interfaces IPv6), lo que a menudo también significa que acepta IPv4 mediante comportamiento de mapeo v6 dependiendo de la configuración. - Pruebas desde un punto de vista solo IPv4 y declaras victoria.
Ubuntu 24.04 es “Linux moderno”. Eso es bueno. También significa que netfilter es nftables-primero, UFW es una capa de compatibilidad, iptables puede ejecutarse como nft por debajo, y los metadatos cloud más la autoconfiguración pueden añadir direcciones que no esperabas. Si solo piensas “reglas iptables = firewall”, ya vas tarde.
Un chiste corto, para limpiar el paladar: Un firewall que bloquea IPv4 pero no IPv6 es como cerrar la puerta principal y dejar el garaje abierto—excepto que el garaje tiene un letrero de neón que dice “nuevo protocolo, ¿quién es?”.
Además: “desactivar IPv6” no es una estrategia. Es un recurso de último recurso que rompe cosas reales (mirrors de paquetes, CDNs modernas, algunas redes corporativas) y a menudo vuelve después con un fallo aún más raro. Lo que quieres es política: denegar inbound por defecto en ambas pilas, permitir explícitamente lo que necesitas, validar desde ambas pilas y aplicar en más de una capa.
Hechos y contexto interesantes (porque la historia se repite)
- IPv6 está estandarizado desde finales de los años 90. El protocolo no es nuevo; lo que queda atrás son las prácticas operativas.
- Los primeros firewalls en Linux eran mayormente centrados en iptables. Muchos equipos crearon memoria muscular alrededor de las tablas IPv4 y trataron IPv6 como “luego”.
- IPv6 elimina NAT como muleta por defecto. Con direccionamiento global, “está detrás de NAT así que está bien” deja de ser válido.
- UFW históricamente apoyaba iptables/ip6tables. En la era de nftables, las capas de traducción pueden sorprenderte si asumes “mismas reglas, mismo comportamiento”.
- Algunos escáneres y comprobaciones de cumplimiento todavía usan IPv4 por defecto. Puedes aprobar un informe mientras sigues completamente abierto en IPv6.
- El descubrimiento de vecinos de IPv6 reemplaza ARP. Tráfico de plano de control distinto significa modos de fallo diferentes cuando la gente bloquea “ICMP raro”.
- ICMPv6 no es opcional. Si lo bloqueas indiscriminadamente romperás discovery de MTU y conectividad básica de formas intermitentes y agradables.
- Dual-stack suele habilitarse “por accidente”. Imágenes cloud, anuncios del router o DHCPv6 pueden darte una dirección global sin que nadie abra un ticket.
- “Escuchar en ::” es un valor predeterminado común. Muchos demonios enlazan el wildcard IPv6 y se vuelven accesibles en v6 aunque solo probaste v4.
Un modelo mental correcto: paquetes, hooks y quién puede decir “deny”
En Ubuntu 24.04, “el firewall” no es una sola cosa. Es una tubería:
- El borde de la red (security groups del cloud, ACLs del router, firewall on-prem, listeners del load balancer).
- El filtrado de paquetes del kernel del host (ruleset de nftables, potencialmente gestionado por UFW, firewalld o automatización propia).
- Aceptación local (un servicio escuchando en un socket, activación por systemd socket, y ACLs a nivel de aplicación).
nftables es el motor. UFW es un gestor. Los comandos iptables pueden ser un frontend de compatibilidad que escribe reglas nft. Puedes tener múltiples gestores y pueden pisarse entre sí. Si tienes mala suerte, tienes reglas para IPv4, política vacía para IPv6 y un servicio en ::. Esa es toda la película.
La gente de confiabilidad suele tener una cita para esto. Aquí hay una que aguanta, presentada como idea parafraseada porque la redacción exacta varía: idea parafraseada — “La esperanza no es una estrategia.”
— a menudo atribuida a círculos de liderazgo de ingeniería y operaciones.
Traduce eso al trabajo de firewall: no esperes que “UFW habilitado” implique “IPv6 bloqueado”. Verifica el ruleset del kernel y verifica con un escaneo capaz de IPv6 desde fuera de la máquina.
Guion de diagnóstico rápido
Si sospechas exposición IPv6 en Ubuntu 24.04, haz esto en orden. Está diseñado para encontrar el cuello de botella (o el control que falta) rápidamente, no para saciar la curiosidad.
1) Confirma que el host es realmente accesible por IPv6
Primera pregunta: ¿tienes una dirección IPv6 global? Si sí, asume que eres alcanzable a menos que pruebes lo contrario. Si no, el riesgo inmediato puede ser menor, pero no te relajes—las interfaces y rutas cambian.
2) Identifica qué está escuchando en IPv6
Encuentra servicios enlazados a :: o a direcciones v6 específicas. Los sockets en escucha son el “por qué”. Las reglas de firewall son el “cómo”.
3) Inspecciona el verdadero ruleset (nftables), no solo el estado de la herramienta
El estado de UFW es una pista. El ruleset de nftables es la verdad. Busca cadenas y políticas ip6.
4) Verifica la aplicación: contadores, logs y una sonda IPv6 real
Los contadores te dicen si las reglas coinciden. Una sonda desde otro host te dice si tu modelo mental es correcto. “curl local” no es una prueba de seguridad.
5) Revisa los controles en el borde (SG del cloud / ACL del router)
Si el borde permite IPv6 ampliamente, el firewall del host es tu última línea. Si el borde bloquea, el firewall del host sigue siendo necesario (para movimiento lateral y tráfico mal enrutado), pero tu exposición puede estar contenida.
Tareas prácticas (comandos, salidas, decisiones)
Estas no son “ejecútalas por diversión.” Cada tarea tiene: un comando, un fragmento de salida realista, qué significa y la decisión que tomas. Hazlas en una terminal. Guarda las salidas en el ticket del incidente. El tú del futuro te lo agradecerá.
Task 1 — See if IPv6 is enabled and what addresses you have
cr0x@server:~$ ip -6 addr show
2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000
inet6 2001:db8:10:20::123/64 scope global dynamic
valid_lft 86390sec preferred_lft 14390sec
inet6 fe80::a00:27ff:fe4e:66a1/64 scope link
valid_lft forever preferred_lft forever
Qué significa: Tienes una dirección IPv6 global (2001:db8:...). Este host es potencialmente alcanzable desde Internet (dependiendo de enrutamiento y reglas en el borde).
Decisión: Trata la exposición IPv6 como real. Continúa con las comprobaciones de listeners y firewall.
Task 2 — Check if IPv6 is disabled via sysctl (don’t assume)
cr0x@server:~$ sysctl net.ipv6.conf.all.disable_ipv6 net.ipv6.conf.default.disable_ipv6
net.ipv6.conf.all.disable_ipv6 = 0
net.ipv6.conf.default.disable_ipv6 = 0
Qué significa: IPv6 está habilitado a nivel de kernel.
Decisión: Mantén IPv6 habilitado; arregla firewall y servicios en vez de intentar “apagar Internet”.
Task 3 — List listening sockets, including IPv6
cr0x@server:~$ ss -lntup
Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
tcp LISTEN 0 4096 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=1123,fd=3))
tcp LISTEN 0 4096 [::]:22 [::]:* users:(("sshd",pid=1123,fd=4))
tcp LISTEN 0 4096 [::]:8080 [::]:* users:(("java",pid=2210,fd=99))
Qué significa: SSH y un servicio Java están escuchando en wildcard IPv6. Aunque tu firewall IPv4 sea estricto, IPv6 todavía podría alcanzar esos puertos.
Decisión: Debes o bien (a) bloquear inbound IPv6 para esos puertos en nftables/UFW, o (b) enlazar servicios a interfaces específicas, o ambas cosas.
Task 4 — Check UFW IPv6 setting (this is where people get fooled)
cr0x@server:~$ sudo grep -n '^IPV6=' /etc/default/ufw
13:IPV6=no
Qué significa: UFW no gestionará reglas IPv6. Tu postura de “UFW habilitado” solo aplica a IPv4.
Decisión: Si usas UFW, pon IPV6=yes y recarga, luego valida las reglas nftables. Si no usas UFW, asegúrate de que nftables tenga filtrado ip6 explícito.
Task 5 — See UFW status, but treat it as a dashboard light
cr0x@server:~$ sudo ufw status verbose
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), disabled (routed)
New profiles: skip
To Action From
22/tcp ALLOW IN 10.0.0.0/8
Qué significa: UFW está activo para lo que gestiona. Observa que nada aquí indica que IPv6 esté cubierto.
Decisión: Verifica nftables para ip6. Si UFW IPv6 está desactivado, esta salida puede ser peligrosamente engañosa.
Task 6 — Inspect the actual nftables ruleset
cr0x@server:~$ sudo nft list ruleset
table inet ufw {
chain input {
type filter hook input priority filter; policy drop;
ct state established,related accept
iif "lo" accept
ip protocol icmp accept
tcp dport 22 ip saddr 10.0.0.0/8 accept
counter packets 1840 bytes 220800 drop
}
}
Qué significa: Este ruleset está en una tabla inet, que puede coincidir tanto IPv4 como IPv6. Pero mira con atención: acepta ip protocol icmp (ICMP IPv4) y permite SSH solo con ip saddr (coincidencia de origen IPv4). No hay una autorización explícita para ICMPv6 ni una autorización explícita de SSH para IPv6. Con un drop por defecto, IPv6 podría estar bloqueado—o podría manejarse en otro lugar si hay otras tablas/cadenas que no viste en el fragmento.
Decisión: Confirma que exista una cadena de entrada autoritativa enganchada para inet y que incluya lo esencial para IPv6 (ICMPv6 para neighbor discovery) y permisos explícitos.
Task 7 — Confirm what hooks exist (are you filtering IPv6 input at all?)
cr0x@server:~$ sudo nft list chains
table inet ufw
chain input
chain forward
chain output
Qué significa: Tienes cadenas, pero aún necesitas confirmar que estén realmente enganchadas con type filter hook input para la familia correcta.
Decisión: Si no ves una cadena input enganchada en una tabla inet/ip6, podrías estar ejecutando “sin firewall IPv6” a pesar de las afirmaciones de las herramientas.
Task 8 — Verify IPv6 neighbor discovery isn’t being murdered
cr0x@server:~$ ip -6 neigh show dev enp0s3
fe80::1 lladdr 52:54:00:12:34:56 router REACHABLE
Qué significa: El neighbor discovery funciona al menos hasta el router. Si ND falla, puedes tener “firewall seguro” y “red misteriosamente inestable” al mismo tiempo.
Decisión: Al endurecer reglas IPv6, permite explícitamente los tipos ICMPv6 necesarios (o permite ICMPv6 entrante si no tienes una razón sólida para lo contrario).
Task 9 — Check for iptables legacy confusion (nft backend vs legacy)
cr0x@server:~$ sudo update-alternatives --display iptables | sed -n '1,12p'
iptables - auto mode
link best version is /usr/sbin/iptables-nft
link currently points to /usr/sbin/iptables-nft
link iptables is /usr/sbin/iptables
Qué significa: Los comandos iptables usan el backend nft. Está bien, pero significa que la salida de “iptables-save” puede no coincidir con lo que esperas de libros de jugadas antiguos.
Decisión: Estandariza: gestiona reglas con nftables directamente o con una sola herramienta (UFW) y deja de mezclar scripts iptables con reglas nft salvo que te guste depurar a las 2 a.m.
Task 10 — Confirm there is no second firewall manager (firewalld) competing
cr0x@server:~$ systemctl is-active firewalld; systemctl is-active ufw
inactive
active
Qué significa: Bien. Un gestor está activo.
Decisión: Si ambos están activos, elige uno. Desactiva el otro. La propiedad mixta crea huecos y pérdida “aleatoria” de reglas.
Task 11 — Identify whether systemd socket activation opened something you forgot
cr0x@server:~$ systemctl list-sockets --all | sed -n '1,12p'
LISTEN UNIT ACTIVATES DESCRIPTION
[::]:22 ssh.socket ssh.service OpenSSH Server Socket
[::]:631 cups.socket cups.service CUPS Scheduler
Qué significa: systemd está escuchando en direcciones IPv6 para servicios. Incluso si “detuviste el servicio”, el socket puede seguir aceptando conexiones y arrancarlo.
Decisión: Deshabilita sockets que no necesites (systemctl disable --now cups.socket) y asegúrate de que las reglas de firewall cubran el puerto en cualquier caso.
Task 12 — Check SSH daemon bind behavior explicitly
cr0x@server:~$ sudo sshd -T | egrep '^(addressfamily|listenaddress|port)'
addressfamily any
port 22
Qué significa: SSH escucha en IPv4 e IPv6 (“any”) a menos que se restrinja.
Decisión: Si pretendes que SSH solo esté en una subred de gestión, hazlo cumplir en reglas de firewall para ambas familias y considera enlazar SSH a esa interfaz/dirección.
Task 13 — Confirm inbound path by probing from another machine (IPv6)
cr0x@server:~$ nc -6vz 2001:db8:10:20::123 22
Connection to 2001:db8:10:20::123 22 port [tcp/ssh] succeeded!
Qué significa: El puerto es alcanzable por IPv6. Este es el “agujero real” en texto claro.
Decisión: Bloquéalo en el borde y en el firewall del host, o restrínjalo a los rangos de origen adecuados, luego vuelve a probar hasta que falle desde orígenes no autorizados.
Task 14 — Watch nft counters while testing (proves which rule matched)
cr0x@server:~$ sudo nft -a list chain inet ufw input
table inet ufw {
chain input { # handle 3
type filter hook input priority filter; policy drop;
ct state established,related accept # handle 10
iif "lo" accept # handle 11
ip protocol icmp accept # handle 12
tcp dport 22 ip saddr 10.0.0.0/8 accept # handle 13
counter packets 1901 bytes 228120 drop # handle 14
}
}
Qué significa: Puedes ver contadores moverse cuando el tráfico coincide. Si tu sonda IPv6 tiene éxito pero los contadores aquí no cambian, tu tráfico IPv6 no está siendo filtrado por esta cadena. Ese es el indicio claro.
Decisión: Encuentra la cadena que realmente engancha la entrada IPv6, o crea una cadena inet adecuada que cubra ambas familias.
UFW en Ubuntu 24.04: lo que realmente hace para IPv6
UFW es popular porque es legible. Eso no es lo mismo que “completo”. La mayor trampa operativa es que el soporte IPv6 es un interruptor en /etc/default/ufw. Si está apagado, puedes tener una política IPv4 limpia y aplicada y un mundo IPv6 intacto.
Habilitar IPv6 en UFW correctamente (si usas UFW)
Edita /etc/default/ufw y pone IPV6=yes. Luego recarga.
cr0x@server:~$ sudo sed -i 's/^IPV6=.*/IPV6=yes/' /etc/default/ufw
cr0x@server:~$ sudo ufw reload
Firewall reloaded
Qué significa: UFW ahora generará y aplicará reglas IPv6 también.
Decisión: Verifica inmediatamente con sudo nft list ruleset y una sonda IPv6 externa. No confíes en el mensaje de recarga.
Establecer valores por defecto sensatos para dual-stack
cr0x@server:~$ sudo ufw default deny incoming
Default incoming policy changed to 'deny'
(be sure to update your rules accordingly)
cr0x@server:~$ sudo ufw default allow outgoing
Default outgoing policy changed to 'allow'
(be sure to update your rules accordingly)
Qué significa: Estás denegando por defecto el inbound en ambas pilas (una vez que IPv6 esté habilitado) y permitiendo el outbound. Esa es la línea base para servidores.
Decisión: Añade permisos explícitos para los puertos entrantes requeridos, con alcance por origen cuando sea posible, en términos tanto IPv4 como IPv6.
Permitir SSH desde un prefijo de gestión en IPv6 (ejemplo)
cr0x@server:~$ sudo ufw allow from 2001:db8:100::/56 to any port 22 proto tcp
Rule added
Rule added (v6)
Qué significa: Ahora tienes una regla de permiso específica para v6 además de las reglas v4. La línea “(v6)” es lo que quieres ver.
Decisión: Si no ves “(v6)”, tu interruptor IPv6 o la sintaxis de la regla no están haciendo lo que crees.
Dos opiniones que te ahorrarán tiempo
- No confíes en “allow 22/tcp” sin límites de origen en máquinas expuestas a Internet. En IPv6, el espacio de direcciones no detiene la fuerza bruta. Los atacantes no se preocupan porque el escaneo sea “más difícil” cuando ya conocen tu dirección por DNS o logs.
- No bloquees ICMPv6 en exceso. Romperás IPv6 de maneras que parecen pérdida aleatoria de paquetes. Mantenlo simple: permite ICMPv6 esencial entrante y aprieta después si tienes evidencia y pruebas.
Chequeo de realidad de nftables: tablas, cadenas, prioridades
Si quieres menos sorpresas, gestiona nftables directamente y mantén a UFW fuera—o comprométete con UFW y verifica sus reglas nft generadas. Lo que no debes hacer es tener tres sistemas “ayudando.” Ahí es donde obtienes un servidor que está abierto y además intermitentemente roto.
Usa una tabla inet para filtrado dual-stack
La familia inet permite que una cadena coincida tanto IPv4 como IPv6. Esa suele ser la forma más simple de evitar “olvidamos ip6.” Un patrón mínimo:
- Drop por defecto en input
- Permitir established/related
- Permitir loopback
- Permitir ICMP + ICMPv6 (o al menos los tipos ICMPv6 necesarios)
- Permitir servicios específicos desde rangos de origen específicos
Ejemplo: crea una cadena de entrada dual-stack simple (ilustrativo—adáptalo a tu entorno, no pegues ciegamente en prod):
cr0x@server:~$ sudo nft add table inet filter
cr0x@server:~$ sudo nft 'add chain inet filter input { type filter hook input priority 0; policy drop; }'
cr0x@server:~$ sudo nft add rule inet filter input ct state established,related accept
cr0x@server:~$ sudo nft add rule inet filter input iif "lo" accept
cr0x@server:~$ sudo nft add rule inet filter input ip protocol icmp accept
cr0x@server:~$ sudo nft add rule inet filter input ip6 nexthdr ipv6-icmp accept
cr0x@server:~$ sudo nft add rule inet filter input tcp dport 22 ip saddr 10.0.0.0/8 accept
cr0x@server:~$ sudo nft add rule inet filter input tcp dport 22 ip6 saddr 2001:db8:100::/56 accept
Qué significa: Una cadena cubre ambas pilas. Permites explícitamente ICMP IPv4 e ICMP IPv6, y restringes SSH por origen para cada familia.
Decisión: Decide si quieres gestionar reglas con nft directamente. Si sí, desactiva UFW para evitar cadenas en conflicto, luego persiste las reglas vía tu método de gestión de configuración.
Persistir reglas nftables: sé explícito sobre la propiedad
Los sistemas Ubuntu varían en cómo se maneja la persistencia de nftables en tu entorno. La clave no es el mecanismo; es que el mecanismo sea único y auditado. Si escribes reglas interactivamente y olvidas persistirlas, “arreglarás” el problema hasta el próximo reinicio—escudo de papel clásico.
Si usas UFW: deja que UFW las persista. Si usas nftables directamente: persiste a través del método elegido y prueba en el reinicio como parte de la validación de cambios.
Segundo chiste corto, y luego volvemos al trabajo: la exposición IPv6 es el tipo de bug que se esconde tan bien que empiezas a pensar que es una característica—hasta que el auditor la encuentra y de repente es “comportamiento legacy”.
No es solo el firewall: servicios que escuchan en IPv6 por defecto
Incluso con un firewall perfecto, no dejes que los servicios escuchen ampliamente salvo que lo necesiten. La defensa en profundidad no es solo un eslogan; es cómo sobrevives a las malas configuraciones, cambios futuros y la ocasional regla “temporal” que alguien olvida quitar.
Enlaza a direcciones específicas cuando sea práctico
Algunos servicios pueden configurarse para enlazar solo a una interfaz de gestión, o a localhost detrás de un proxy inverso. Ejemplos:
- Los paneles de administración deberían enlazarse a
127.0.0.1y::1, y accederse vía tunel SSH o VPN. - APIs internas deberían enlazarse a una interfaz VLAN interna, no a la interfaz pública.
- Cualquier cosa “temporal” debería enlazarse a localhost por defecto.
Cuidado con los sockets de systemd
systemd puede mantener puertos abiertos incluso cuando piensas que un servicio está detenido. No es un bug; es una característica para servicios on-demand. También es una trampa si no revisas sockets.
Si encuentras un socket escuchando en IPv6 que no quieres, desactívalo:
cr0x@server:~$ sudo systemctl disable --now cups.socket
Removed "/etc/systemd/system/sockets.target.wants/cups.socket".
Qué significa: La unidad socket no escuchará después de esto y no se iniciará en el arranque.
Decisión: Haz esto para cualquier listener activado por socket no deseado, luego confirma con ss -lntup.
IPv6 y “localhost” no son lo mismo
Los ingenieros a veces enlazan una app a “localhost” y asumen que es local. Pero “localhost” puede significar solo IPv4 (127.0.0.1), solo IPv6 (::1) o ambos según la configuración. Sé explícito en configs de servicio. Escribe ambas direcciones si quieres ambas. Prueba ambas.
Cloud y borde: security groups, NACLs, load balancers y por qué los firewalls de host no bastan
En sistemas corporativos, la falla más común en IPv6 es la política con cerebro partido:
- El equipo de red cierra cuidadosamente los security groups IPv4.
- IPv6 se activa más tarde (“lo necesitamos por cumplimiento” / “modernización” / “porque existe la casilla del proveedor”).
- Las reglas entrantes IPv6 quedan permisivas o por defecto-allow en un lugar que nadie revisa a diario.
- Se asume que el firewall del host lo bloquea, pero UFW IPv6 está apagado o las reglas nft no están enganchadas.
El firewall de host es necesario, pero no suficiente. Si tienes la posibilidad de bloquear IPv6 en el borde, hazlo. Luego sigue aplicando la política en el host. Las dos capas capturan diferentes clases de errores:
- Controles en el borde protegen contra ruido directo de Internet y reducen la superficie de exposición.
- Firewall del host protege contra movimiento lateral, tráfico mal enrutado, amenazas internas y momentos en los que “alguien abrió un puerto en la instancia”.
Recuerda también los load balancers: podrías tener un listener IPv6 en el LB, que reenvía a instancias por IPv4. O al revés. Dual-stack en el borde no garantiza dual-stack en el host. Esa descoordinación es donde el monitoreo te miente.
Tres micro-historias corporativas desde la trinchera
1) Incidente causado por una suposición errónea: “UFW está activado, así que estamos seguros”
Una compañía mediana desplegó imágenes Ubuntu 24.04 para herramientas internas. Tenían un buen script de hardening consistente: instalar UFW, establecer deny por defecto inbound, permitir SSH desde rangos IPv4 corporativos, habilitar logging. Pasó su paso estándar de “escaneo de puertos”, que—predeciblemente—se ejecutó desde un runner solo IPv4 dentro de un job de CI.
Tres meses después, un ejercicio de red-team marcó múltiples hosts con SSH alcanzable por IPv6. La primera reacción del equipo fue negación, luego confusión, luego un torbellino de capturas “pero UFW está activo”. La pista estaba a simple vista: IPV6=no en /etc/default/ufw. Esa configuración se heredó de una baseline antigua creada cuando su entorno no tenía enrutamiento IPv6. Persistió como un fósil.
Los hosts habían adquirido direcciones IPv6 globales mediante un cambio de red que no se planteó como “esto cambia la exposición”. Los anuncios del router hicieron aparecer direcciones automáticamente. Nadie pensó en tratarlo como un cambio de límite de seguridad. Esa es la suposición equivocada: “la asignación de direcciones es un detalle de red.” En dual-stack, es un evento de política.
Arreglarlo no fue solo poner IPV6=yes. Tuvieron que añadir permisos adecuados de ICMPv6, o la conectividad se rompía de formas extrañas. También descubrieron que algunos hosts tenían servicios enlazados a :: “temporalmente”. Una vez forzaron el binding de servicios y aplicaron reglas dual-stack, los resultados del red-team se volvieron aburridos. Aburrido es bueno.
2) Optimización que se volvió en contra: “Drop total de ICMP para reducir ruido”
Otra organización ejecutaba una plataforma API de alto volumen y se enorgullecía de “superficie de ataque mínima”. Un ingeniero notó mucho tráfico ICMP en los logs y decidió que era “innecesario”. Apretaron reglas para dropear ICMP en general, incluyendo ICMPv6, porque “no necesitamos ping en producción”. Esto se empaquetó como optimización: menos paquetes, menos logs, menos distracciones.
En días, la plataforma desarrolló timeouts intermitentes. No fue un fallo limpio—peor. Algunos clientes experimentaron fallos en requests grandes, otros no. Las reintentos ayudaban. La latencia parecía bien hasta que no. El on-call empezó a coleccionar supersticiones. Alguien culpó al CDN. Alguien culpó a TLS.
La causa real fue el descubrimiento de MTU en IPv6 roto. Los mensajes ICMPv6 “Packet Too Big” no llegaban de vuelta. Las conexiones retrocedieron a un comportamiento de fragmentación que no encajaba, y ciertos caminos tiraban paquetes a un agujero negro. La optimización se volvió en contra y creó una pesadilla de fiabilidad y un coste de depuración.
Lo arreglaron permitiendo el ICMPv6 necesario y afinando el logging para que el ruido fuera manejable. La lección quedó: ICMPv6 no es “ping”. Es plomería. Apagarlo porque molesta es como quitar la luz de aceite porque es molesta.
3) La práctica aburrida pero correcta que salvó el día: puertas de validación dual-stack
Una compañía del ámbito financiero había sufrido antes por una exposición IPv6, así que institucionalizaron una práctica que nadie amaba: cada nuevo servicio y cada nueva imagen base tenía que pasar pruebas de reachability dual-stack. No “corrimos nmap una vez”, sino una pequeña suite automatizada: comprobar sockets en escucha, comprobar hooks de nftables, sondear desde un runner habilitado para IPv6 y afirmar “solo estos puertos son alcanzables”.
Fue aburrido. Los ingenieros se quejaron de que ralentizaba el aprovisionamiento. Seguridad lo aplaudió. SREs lo toleraron. El pago llegó silencioso: durante una migración cloud, un cambio de red añadió IPv6 a subredes por defecto. Muchos equipos ni se dieron cuenta, porque nada se rompió. Pero la puerta de validación sí: de pronto builds fallaban porque las pruebas de reachability IPv6 encontraban puertos abiertos.
En lugar de descubrir el problema por un incidente, lo descubrieron en CI. Las correcciones fueron pequeñas y repetibles: habilitar UFW IPv6 (o corregir reglas nftables inet), restringir SSH por prefijo v6 y deshabilitar sockets systemd sin usar. La migración siguió en calendario. Nadie escribió un postmortem. Ese es el sueño.
Errores comunes: síntomas → causa raíz → solución
1) “El escaneo IPv4 está limpio, pero los auditores dicen que hay puertos abiertos”
Síntoma: Un informe externo muestra SSH/HTTP alcanzable; tu escaneo interno muestra cerrado.
Causa raíz: El auditor escaneó IPv6; tú validaste solo IPv4.
Solución: Realiza escaneos dual-stack. Confirma ip -6 addr, luego sondea con nc -6vz o equivalente desde un host IPv6. Habilita reglas IPv6 en UFW o nftables.
2) “UFW está activo, pero conexiones IPv6 aún tienen éxito”
Síntoma: ufw status se ve correcto; conexiones IPv6 alcanzan servicios de todos modos.
Causa raíz: IPV6=no en /etc/default/ufw, o reglas UFW no enganchadas en nftables para rutas inet/ip6.
Solución: Pon IPV6=yes, recarga UFW, verifica con nft list ruleset y vuelve a probar desde una fuente IPv6 externa.
3) “IPv6 muere aleatoriamente tras endurecer”
Síntoma: Algunas conexiones se cuelgan, transferencias grandes fallan, caídas intermitentes extrañas.
Causa raíz: ICMPv6 bloqueado demasiado agresivamente (PMTUD y neighbor discovery afectados).
Solución: Permite ICMPv6 (ip6 nexthdr ipv6-icmp accept) o al menos los tipos requeridos. Prueba con cargas reales, no solo con ping.
4) “El servicio está detenido, pero el puerto sigue abierto”
Síntoma: Detienes un daemon; ss todavía muestra en escucha.
Causa raíz: La activación por socket de systemd mantiene el puerto abierto mediante una unidad .socket.
Solución: Desactiva la unidad socket (systemctl disable --now name.socket) y vuelve a comprobar listeners.
5) “Existen reglas, pero el tráfico IPv6 no golpea contadores”
Síntoma: Conexiones IPv6 tienen éxito; los contadores esperados de la cadena nft no se mueven.
Causa raíz: Tu cadena no está enganchada para input, o otra tabla/cadena tiene precedencia, o estás filtrando solo la familia ip.
Solución: Asegúrate de tener una cadena input inet o ip6 con un hook y prioridad correctos. Simplifica la propiedad: un solo gestor.
6) “Permitimos SSH desde IPv4 corporativo, pero IPv6 sigue permitiendo al mundo”
Síntoma: SSH está restringido por orígenes IPv4; IPv6 está abierto.
Causa raíz: La regla coincide solo con ip saddr, no con ip6 saddr.
Solución: Añade reglas con alcance por origen IPv6 (UFW allow from 2001:.../... o nft ip6 saddr).
7) “Deshabilitamos IPv6, pero vuelve / las apps se rompen”
Síntoma: Alguien cambia sysctl para deshabilitar IPv6; más tarde reaparece o los servicios fallan.
Causa raíz: Deshabilitar IPv6 es frágil en distintos entornos y puede romper dependencias; o la configuración se aplica inconsistente por interfaz.
Solución: Vuelve a habilitar IPv6 e implementa la política correcta de firewall y bindings. Si debes deshabilitarlo, hazlo de forma consistente y documentada, y programa una corrección posterior.
Listas de verificación / plan paso a paso
Paso a paso: cerrar el verdadero agujero en un único host Ubuntu 24.04
- Descubrir direcciones y rutas IPv6. Si tienes una dirección global, asume exposición y procede.
- Inventariar listeners. Identifica qué se enlaza a
::y qué no debería hacerlo. - Escoge un gestor de firewall. UFW o nftables. No ambos. Tampoco “scripts iptables más UFW”.
- Asegura deny inbound por defecto para ambas pilas. En UFW, configura
IPV6=yes. En nftables, usa una cadena inputinetconpolicy drop. - Permite ICMPv6 requerido. Mantén IPv6 funcional mientras lo aseguras.
- Añade permisos explícitos para servicios requeridos. Prefiere scoping por origen: prefijos de gestión para SSH, subredes de LB para puertos de app.
- Reduce listeners. Enlaza servicios internos/admin a localhost o interfaces internas. Desactiva sockets systemd no usados.
- Valida desde fuera por IPv6. Si no puedes probar desde fuera, tu cambio no está verificado.
- Revisa contadores/logs. Confirma que tus reglas se usan realmente.
- Persiste la configuración. Asegura que las reglas sobrevivan reinicios y la deriva de gestión de configuración.
Lista operativa: qué capturar en el ticket (hace auditorías sobrevivibles)
- Salida de
ip -6 addryip -6 route(prueba direccionamiento y ruta por defecto). - Salida de
ss -lntup(prueba qué podría ser alcanzado). - Salida de
nft list ruleset(prueba la aplicación real). - Estado de UFW + el toggle IPv6 en
/etc/default/ufw(prueba el alcance del gestor). - Resultados de sondas IPv6 externas antes/después (prueba el cierre).
Preguntas frecuentes
1) ¿Por qué apareció esto específicamente en Ubuntu 24.04?
Ubuntu 24.04 está firmemente en la era nftables y el networking dual-stack es común por defecto en muchos entornos. El “gotcha” no es exclusivo de 24.04, pero la mezcla de capas de herramientas y valores modernos hace más probable que se filtren configuraciones a medias.
2) Si mi servidor no tiene registro DNS AAAA, ¿estoy a salvo de la exposición IPv6?
No. DNS es un mecanismo de descubrimiento, no un control de acceso. Las direcciones IPv6 se filtran por logs, configuraciones, certificados, telemetría e inventarios cloud. Además, atacantes internos no necesitan DNS.
3) ¿“Escanear IPv6 es difícil” es una defensa significativa?
No realmente. Los atacantes no necesitan escanear todo un /64 si ya conocen tu dirección, lo que suele ocurrir. Y muchos servicios están en direcciones predecibles en entornos cloud.
4) ¿Puedo simplemente deshabilitar IPv6 para que esto desaparezca?
Puedes, pero normalmente es un parche temporal con daños colaterales. Rompe algunas redes, complica la resolución de problemas y suele revertirse más tarde. El cortafuegos dual-stack correcto es la solución duradera.
5) ¿Necesito reglas separadas para IPv4 e IPv6?
Necesitas cobertura para ambas. Con nftables, una tabla inet puede aplicar una cadena a las dos familias. Con UFW, debes habilitar IPv6 y confirmar que se generan reglas para v6.
6) ¿Qué ICMPv6 debo permitir?
Como mínimo, permite el tráfico ICMPv6 requerido para neighbor discovery y PMTUD. Si no tienes tiempo para ser ingenioso, permite ICMPv6 entrante y aprieta después con pruebas reales y una justificación.
7) ¿Por qué veo servicios escuchando en [::]:port cuando solo configuré IPv4?
Muchos demonios enlazan el wildcard IPv6 por defecto porque puede cubrir ambas pilas, o porque los valores de la distribución lo prefieren. Eso es normal; solo significa que debes aplicar política de firewall también para IPv6.
8) ¿Cómo sé si UFW está aplicando realmente IPv6?
Revisa /etc/default/ufw para IPV6=yes, luego inspecciona el ruleset de nftables y prueba desde un host IPv6 externo. El estado de la herramienta por sí solo no es prueba.
9) ¿Cuál es la forma más rápida de demostrar que lo arreglamos?
Desde un host fuera de tus rangos permitidos, intenta conectar por IPv6 al puerto previamente expuesto (por ejemplo nc -6vz). Debe fallar. Luego confirma que los contadores nft muestran drops para ese tráfico.
10) Si tenemos security groups en la nube, ¿por qué molestarnos con un firewall en el host?
Porque las políticas del borde derivan, existe tráfico interno y ocurre mal enrutamiento. El firewall del host te protege cuando el perímetro no es perfecto—y nunca lo es.
Conclusión: próximos pasos que realmente puedes agendar
Cierra el agujero como cierras cualquier brecha operativa real: prueba que existe, arréglalo en un lugar autoritativo, y luego añade una barrera para que no vuelva.
- Hoy: ejecuta
ip -6 addr,ss -lntupysudo nft list ruleseten un host representativo. Si ves IPv6 global + listeners abiertos + reglas débiles, has encontrado el problema. - Esta semana: elige un modelo de propiedad del firewall (UFW con IPv6 habilitado, o nftables nativo). Asegura deny inbound por defecto en ambas pilas. Permite ICMPv6 correctamente. Escopa los permisos entrantes por origen.
- Este mes: añade una puerta de validación dual-stack: sonda IPv6 externa + inventario de listeners + verificación de hooks nft. Hazlo aburrido. Hazlo automático.
Dual-stack ya no es opcional. La buena noticia: arreglarlo es sencillo una vez que dejas de fingir que IPv6 es “luego”.