SSD Nodes Learn Hosting plans →
Guías Matt ConnorPor Matt Connor

UFW e IPv6: la puerta abierta de tu VPS

En Ubuntu 24.04, UFW ya gestiona IPv6, pero otra capa puede dejar puertos expuestos. Comprueba qué escucha en tu VPS y cierra la brecha.

La trampa del firewall IPv6 en una frase

Tu firewall protege IPv4. Tu VPS casi seguro también tiene una dirección IPv6 pública, y muchos servicios escuchan en ella de forma predeterminada. Si tu firewall sólo cubre IPv4, o si dependes de un firewall del proveedor cloud que sólo filtra IPv4, todos esos servicios son accesibles desde Internet mediante IPv6, aunque el lado IPv4 parezca protegido. Compruebas un puerto con curl, ves que la conexión es rechazada y te sientes seguro. Un atacante se conecta al mismo puerto mediante IPv6 y entra.

Esta guía muestra de dónde procede esa brecha en un VPS normal con Ubuntu 24.04, cómo ver exactamente qué estás exponiendo y cómo cerrarlo. UFW no es el problema aquí. En una instalación moderna de Ubuntu, UFW ya gestiona IPv6. La exposición procede de las capas que lo rodean y de servicios que no sabías que estaban escuchando.

Por qué tu VPS usa IPv6 desde el principio

Hoy, casi todos los VPS se entregan con una dirección IPv6 pública, a menudo un /64 completo, además de su dirección IPv4. Comprueba el tuyo:

ip -6 addr show scope global
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> ...
    inet6 2001:db8:2a::1/64 scope global

El 2001:db8:2a::1 de esta guía es un marcador de posición de 2001:db8::/32, un prefijo reservado para documentación que nunca se anuncia en Internet. La dirección real que muestra tu servidor en esa línea es enrutable desde cualquier punto de Internet, igual que tu dirección IPv4. Ahora comprueba qué servicios están escuchando:

sudo ss -tlnp
State   Recv-Q  Local Address:Port   Process
LISTEN  0       0.0.0.0:22           sshd
LISTEN  0       [::]:22              sshd
LISTEN  0       127.0.0.1:5432       postgres
LISTEN  0       [::]:8080            docker-proxy

Lee con atención la columna Local Address. 0.0.0.0:22 significa «escuchar en todas las direcciones IPv4». [::]:22 significa «escuchar en todas las direcciones IPv6». 127.0.0.1:5432 está vinculado a la interfaz de loopback y no es público, por lo que la línea de Postgres es segura. Las dos líneas [::] responden a todo Internet mediante IPv6, y la línea docker-proxy corresponde al tipo de servicio que puedes olvidar que has iniciado.

La mayoría de los demonios se vinculan a :: de forma predeterminada, porque en Linux un socket :: normalmente también acepta IPv4. Por tanto, la configuración predeterminada de un servidor recién instalado es «responder en ambas pilas y en todas partes». Tu firewall es lo único que se interpone, por eso un firewall que sólo ve una de las pilas es un problema real.

De dónde procede realmente la brecha de IPv6

Hay tres fuentes habituales. En un servidor concreto puede haber una de ellas o varias a la vez.

1. Un firewall de la nube que filtra sólo IPv4. Muchos firewalls de proveedores y productos de grupos de seguridad se diseñaron originalmente para IPv4 y omiten IPv6 o requieren reglas de IPv6 independientes que debe añadir manualmente. Si el único firewall que tiene es el del panel del proveedor y no cubre IPv6, sus servicios [::] quedan expuestos independientemente de lo que indique sobre el puerto 22 en IPv4. Consulte la documentación del firewall del proveedor y busque específicamente la palabra IPv6.

2. iptables configurado manualmente sin ip6tables. El comando iptables sólo modifica las tablas de IPv4. IPv6 tiene un comando completamente independiente, ip6tables, con sus propias reglas. Si escribió un script de firewall lleno de líneas iptables -A INPUT ... y nunca escribió las reglas ip6tables equivalentes, el firewall de IPv6 está vacío y una cadena INPUT vacía con una política predeterminada ACCEPT permite todo:

sudo ip6tables -L INPUT -n
Chain INPUT (policy ACCEPT)
target     prot opt source               destination

Esa salida muestra todo el problema en una sola pantalla. IPv4 está filtrado, pero IPv6 acepta conexiones desde cualquier origen.

3. Docker publica puertos directamente, sin pasar por el firewall. Cuando ejecuta docker run -p 8080:80, Docker inserta sus propias reglas por delante de las de UFW, por lo que se puede acceder a un puerto publicado aunque ufw status indique que ese puerto está denegado. Las versiones actuales de Docker administran las reglas de ip6tables de forma predeterminada. Por tanto, cuando IPv6 está habilitado en la red del contenedor, ocurre lo mismo con IPv6. Por qué Docker omite UFW y cómo filtrar correctamente los puertos de los contenedores explica el mecanismo y las soluciones. Consulte los conceptos básicos de Docker Compose en un VPS para saber cómo se declaran estos puertos publicados.

Nota sobre el interruptor de IPv6 de UFW. UFW admite IPv6 y la opción se encuentra en un solo archivo. Compruébela:

grep IPV6 /etc/default/ufw

Las versiones modernas de Ubuntu incluyen IPV6=yes, por lo que UFW aplica cada regla a ambas pilas. Si ve IPV6=no, debido a una imagen antigua o a una guía antigua, IPv6 no queda abierto. Con ese valor, un UFW activo descarta todo el tráfico IPv6 excepto el de la interfaz de bucle invertido. En ese caso, el problema se produce en sentido contrario: todo lo que pretendía ofrecer mediante IPv6 deja de responder. Establézcalo en yes si quiere que UFW filtre IPv6 con sus reglas en lugar de bloquearlo por completo.

Vea exactamente qué está exponiendo

No haga suposiciones. Mídalo desde el exterior. Primero, enumere los procesos que escuchan y anote todos los que estén vinculados a :::

sudo ss -tlnp | grep '::'

Después, desde otra máquina, conéctese a la dirección IPv6 pública del servidor e intente acceder a un puerto que crea que está cerrado:

curl -6 -v http://[2001:db8:2a::1]:8080/

Si devuelve una página o un banner, el puerto está abierto en IPv6. Un puerto cerrado devuelve Connection refused o agota el tiempo de espera. Esos dos fallos no indican lo mismo, y la diferencia entre una conexión rechazada y una que agota el tiempo de espera indica si el host respondió y rechazó la conexión o si un firewall descartó el paquete en silencio. Para obtener una visión completa, analice la dirección IPv6 con nmap desde fuera del servidor:

nmap -6 2001:db8:2a::1

Cada puerto que nmap indique como abierto mediante IPv6 es un puerto accesible desde todo Internet, independientemente de lo que mostrara el análisis de IPv4. Compare los análisis de IPv4 e IPv6 uno al lado del otro. Es la forma más rápida de encontrar la diferencia: cualquier puerto abierto en -6 pero cerrado en IPv4 corresponde a un servicio que su firewall no está filtrando.

Cierre la brecha

Haga que UFW cubra ambas pilas y establezca la denegación predeterminada. Confirme el cambio. Después, establezca una política predeterminada de denegación de conexiones entrantes y permita sólo lo necesario:

sudo sed -i 's/^IPV6=no/IPV6=yes/' /etc/default/ufw
sudo ufw default deny incoming
sudo ufw allow 22/tcp
sudo ufw enable
sudo ufw status verbose

Si UFW ya estaba activo cuando cambió IPV6=yes, el cambio no se aplica hasta ejecutar sudo ufw reload.

ufw status muestra cada regla dos veces: una vez sin cambios y otra con el sufijo (v6). Cuando vea las líneas (v6), UFW está filtrando IPv6:

22/tcp                     ALLOW IN    Anywhere
22/tcp (v6)                ALLOW IN    Anywhere (v6)

Si administra iptables manualmente, replique cada regla en ip6tables o cambie a nftables. Sus tablas inet cubren IPv4 e IPv6 en un solo lugar y eliminan por completo este tipo de error. Una sola tabla de filtrado inet de nftables es la solución más limpia cuando escribe las reglas manualmente. Si su VPS ejecuta Rocky o AlmaLinux en lugar de Ubuntu, no hay UFW que configurar. firewalld es la interfaz que debe administrar en su lugar y aplica sus reglas de zona a ambas pilas a la vez.

Asigne los servicios que no quiera exponer públicamente a la interfaz de loopback. Una base de datos, un panel de administración o un endpoint de métricas rara vez necesitan una dirección pública. Asígnelos a 127.0.0.1 y ::1 para que nunca escuchen en una dirección enrutable. En Postgres, establezca listen_addresses = 'localhost'. En un servidor de aplicaciones, asígnelo a 127.0.0.1 y coloque un reverse proxy delante. Cerrar el listener es mejor que filtrarlo con el firewall, porque así no hay ningún servicio accesible.

No confíe en UFW para proteger los puertos publicados por Docker. Publique los puertos de los contenedores en una dirección específica, no en todas las interfaces. Por ejemplo, use -p 127.0.0.1:8080:80 para que el puerto sólo sea accesible desde el host y desde aquello a lo que lo exponga deliberadamente mediante un proxy. Cuando un contenedor deba ser realmente público, colóquelo detrás de un reverse proxy de Traefik y publique sólo el proxy, no cada aplicación.

Añada reglas de IPv6 al firewall de su proveedor o acepte que no es su firewall para IPv6 y deje que UFW o nftables en el host se encarguen de esa función.

Verifique que realmente estén cerrados

Vuelva a ejecutar la misma prueba externa después de aplicar los cambios:

curl -6 -v http://[2001:db8:2a::1]:8080/
nmap -6 2001:db8:2a::1

El puerto que respondía antes ahora debería rechazar la conexión o agotar el tiempo de espera, y nmap debería mostrarlo como filtrado o cerrado. Si un puerto sigue abierto, revise de nuevo las tres fuentes anteriores: un servicio que sigue enlazado a :: sin ninguna regla delante, una regla de Docker situada antes que UFW o un firewall del proveedor que nunca procesó IPv6.

Mantener los servicios sensibles completamente fuera de Internet pública es aún más seguro. Coloque SSH y los paneles de administración detrás de una VPN WireGuard y configure el firewall para que sus puertos sólo respondan a través del túnel. Así, la cuestión de la exposición mediante IPv6 deja de aplicarse a esos servicios. Para ralentizar los escaneos de fuerza bruta que llegan a lo que permanezca público, añada Fail2ban delante de SSH sobre un firewall con denegación predeterminada.

Si los puertos son un concepto nuevo para usted, lea primero qué son los puertos y cómo escuchan los servicios.

FAQ

¿UFW bloquea IPv6 de forma predeterminada?

En una instalación moderna de Ubuntu 24.04, sí. UFW lee IPV6=yes de /etc/default/ufw y aplica cada regla tanto a IPv4 como a IPv6. ufw status muestra las reglas de IPv6 con el sufijo (v6). El problema aparece cuando depende de un firewall del proveedor que sólo filtra IPv4, cuando las reglas escritas manualmente para iptables no tienen su equivalente en ip6tables o cuando Docker publica un puerto por fuera de UFW. IPV6=no (de una imagen antigua o de un tutorial antiguo) no habilita IPv6: con ese valor, un UFW activo descarta todo el tráfico IPv6 excepto el de loopback. Compruebe el estado con grep IPV6 /etc/default/ufw.

¿Cómo compruebo qué expone mi VPS mediante IPv6?

Ejecute sudo ss -tlnp y observe cada listener cuya dirección local empiece por [::]. Esto significa que responde en todas las interfaces IPv6. Después, desde otra máquina, pruebe directamente la dirección IPv6 pública del servidor con curl -6 -v http://[YOUR:IPV6::ADDR]:PORT/ o escanéela con nmap -6 YOUR:IPV6::ADDR. Cualquier puerto abierto en el escaneo IPv6 pero cerrado en IPv4 constituye una brecha de configuración.

¿Por qué puedo acceder al puerto de mi contenedor Docker si UFW indica que está bloqueado?

Docker inserta sus propias reglas de firewall antes que las de UFW cuando publica un puerto con -p. Por eso, el puerto publicado es accesible aunque ufw status lo muestre como denegado. Esto ocurre en IPv4 y también en IPv6 cuando la compatibilidad con IPv6 de Docker está habilitada. Publique el puerto en una dirección específica, como -p 127.0.0.1:8080:80, o coloque el contenedor detrás de un reverse proxy y publique sólo el proxy.

¿Necesito un firewall IPv6 si mi firewall IPv4 está bien configurado?

Sí. IPv4 e IPv6 son pilas de red independientes, con reglas de firewall independientes. Un conjunto perfecto de reglas IPv4 no hace nada con el tráfico IPv6. Si su VPS tiene una dirección IPv6 pública, como ocurre con casi todos, cualquier servicio que escuche en :: seguirá siendo accesible mediante IPv6 hasta que una regla de firewall IPv6 o una vinculación a loopback lo impida.

¿Cómo hago que un servicio escuche sólo en IPv4 o sólo en localhost?

Configure la dirección de vinculación del servicio en su propia configuración. Vincúlelo a 127.0.0.1 para usar sólo el loopback IPv4, o a 0.0.0.0 para usar todas las direcciones IPv4 sin un listener IPv6. Postgres usa listen_addresses, SSH usa ListenAddress y la mayoría de los servidores de aplicaciones ofrece una opción de host o de vinculación. Confirme el resultado con sudo ss -tlnp y compruebe que Local Address ya no muestre [::].