SSD Nodes Learn Hosting plans →
Guias Matt ConnorPor Matt Connor

UFW no IPv6: como fechar portas abertas no VPS

No Ubuntu 24.04, o UFW já trata IPv6. Veja como uma firewall cloud ou regras iptables só para IPv4 deixam serviços expostos no seu VPS.

A armadilha da firewall IPv6 numa frase

A sua firewall protege o IPv4. O seu VPS quase certamente também tem um endereço IPv6 público, e muitos serviços escutam nele por predefinição. Se a sua firewall abranger apenas o IPv4, ou se depender de uma firewall cloud que filtra apenas IPv4, todos esses serviços ficam acessíveis a partir de toda a Internet através de IPv6, enquanto o lado IPv4 parece bloqueado. Testa uma porta com curl, vê uma ligação recusada e sente-se protegido. Um atacante liga-se à mesma porta através de IPv6 e entra.

Este guia mostra de onde vem essa lacuna num VPS Ubuntu 24.04 normal, como ver exatamente o que está exposto e como fechar essa exposição. O UFW não é o problema aqui. Numa instalação moderna do Ubuntu, o UFW já trata o IPv6. A exposição vem das camadas à volta dele e dos serviços que não sabia que estavam a escutar.

Por que o seu VPS usa IPv6 desde o início

Atualmente, quase todos os VPS são fornecidos com um endereço IPv6 público, muitas vezes uma /64 inteira, além do endereço IPv4. Verifique o seu:

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

O 2001:db8:2a::1 neste guia é um marcador de posição de 2001:db8::/32, um prefixo reservado para documentação que nunca é encaminhado na internet. O endereço real que o servidor mostra nessa linha pode ser alcançado a partir de qualquer ponto da internet, exatamente como o seu endereço IPv4. Agora veja o que está a escutar:

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

Leia atentamente a coluna Local Address. 0.0.0.0:22 significa "escutar em todos os endereços IPv4". [::]:22 significa "escutar em todos os endereços IPv6". 127.0.0.1:5432 está associado ao loopback e não é público, portanto a linha do Postgres é segura. As duas linhas [::] aceitam ligações de toda a internet através de IPv6, e a linha docker-proxy é o tipo de serviço que é fácil esquecer que foi iniciado.

A maioria dos daemons associa-se a :: por predefinição, porque no Linux um socket :: normalmente também aceita IPv4. Por isso, a configuração predefinida de um servidor acabado de instalar é "responder através das duas pilhas, em qualquer endereço". A firewall é a única barreira entre esses serviços e a internet. Por isso, uma firewall que analisa apenas uma das pilhas é um problema real.

De onde vem realmente a lacuna do IPv6

Existem três origens comuns. Num determinado servidor, pode ter uma delas ou várias ao mesmo tempo.

1. Uma firewall na cloud que filtra apenas IPv4. Muitas firewalls de fornecedores e produtos de grupos de segurança foram desenvolvidos à volta do IPv4 e ignoram o IPv6 ou exigem regras IPv6 separadas, que tem de adicionar manualmente. Se a sua única firewall for a do painel do fornecedor e não abranger IPv6, os seus serviços [::] ficam expostos, independentemente do que o painel indicar sobre a porta 22 em IPv4. Consulte a documentação da firewall do fornecedor e procure especificamente a palavra IPv6.

2. iptables configurado manualmente sem ip6tables. O comando iptables atua apenas nas tabelas IPv4. O IPv6 tem um comando completamente separado, ip6tables, com regras próprias. Se escreveu um script de firewall cheio de linhas iptables -A INPUT ... e nunca escreveu as regras ip6tables correspondentes, a firewall IPv6 está vazia. Uma cadeia INPUT vazia com uma política predefinida ACCEPT permite tudo:

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

Esse resultado mostra toda a armadilha num único ecrã. O IPv4 é filtrado; o IPv6 aceita ligações de qualquer origem.

3. O Docker publica portas diretamente, contornando a firewall. Quando executa docker run -p 8080:80, o Docker insere as próprias regras antes das regras do UFW. Por isso, uma porta publicada fica acessível mesmo quando ufw status indica que essa porta foi negada. As versões atuais do Docker gerem regras ip6tables por predefinição. Assim, o mesmo se aplica ao IPv6 quando a rede do contentor tem IPv6 ativado. Por que motivo o Docker contorna o UFW e como filtrar corretamente as portas dos contentores explica o mecanismo e as correções. Consulte os conceitos básicos do Docker Compose numa VPS para saber como declarar essas portas publicadas.

Uma nota sobre a opção IPv6 do UFW. O UFW suporta IPv6, e a opção está num único ficheiro. Verifique-a:

grep IPV6 /etc/default/ufw

O Ubuntu atual inclui IPV6=yes, pelo que o UFW aplica cada regra às duas pilhas. Se vir IPV6=no, devido a uma imagem antiga ou a um guia antigo, o IPv6 não fica desprotegido. Com essa definição, um UFW ativo elimina todo o tráfego IPv6, exceto na interface de loopback. Nesse caso, o problema ocorre no sentido inverso: tudo o que pretendia disponibilizar por IPv6 deixa de responder. Defina-a como yes se quiser que o UFW filtre o IPv6 com as suas regras, em vez de o bloquear completamente.

Veja exatamente o que está a expor

Não adivinhe. Meça a partir do exterior. Primeiro, liste os sockets em escuta e registe todos os que estão associados a :::

sudo ss -tlnp | grep '::'

Depois, a partir de outra máquina, ligue-se ao endereço IPv6 público do servidor e tente uma porta que considere fechada:

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

Se isso devolver uma página ou um banner, a porta está aberta em IPv6. Uma porta fechada devolve Connection refused ou resulta num timeout. Estas duas falhas não são o mesmo sinal, e a diferença entre ligação recusada e timeout mostra se o host respondeu e rejeitou a ligação ou se uma firewall descartou o pacote silenciosamente. Para obter uma visão completa, faça um scan do endereço IPv6 com nmap a partir de fora do servidor:

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

Todas as portas que o nmap indicar como abertas em IPv6 podem ser alcançadas por toda a Internet, independentemente do resultado do scan IPv4. Compare os scans IPv4 e IPv6 lado a lado. Esta é a forma mais rápida de encontrar a falha: qualquer porta aberta em -6 mas fechada em IPv4 corresponde a um serviço que a sua firewall não está a filtrar.

Elimine a lacuna

Faça o UFW abranger as duas pilhas e defina a política padrão como negar. Confirme a alteração e, em seguida, defina uma política de entrada que negue tudo por padrão e permita apenas o que for necessário:

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

Se o UFW já estava ativo quando ativou IPV6=yes, a alteração só entra em vigor depois de executar sudo ufw reload.

ufw status apresenta cada regra duas vezes: uma vez sem sufixo e outra com o sufixo (v6). Quando vir as linhas (v6), o UFW está a filtrar IPv6:

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

Se gerir o iptables manualmente, replique todas as regras no ip6tables ou mude para nftables. As tabelas inet do nftables abrangem IPv4 e IPv6 num único local e eliminam esta classe de erro. Uma única tabela de filtragem inet do nftables é a correção mais simples quando escreve as regras manualmente. Se o seu VPS executar Rocky ou AlmaLinux em vez de Ubuntu, não há UFW para configurar. Nesse caso, o firewalld é o frontend que deve gerir, e aplica as respetivas regras de zona às duas pilhas em simultâneo.

Associe os serviços que não devem ser públicos à interface de loopback. Uma base de dados, um painel de administração ou um endpoint de métricas raramente precisa de um endereço público. Associe-o a 127.0.0.1 e ::1 para que nunca escute num endereço encaminhável. No Postgres, defina listen_addresses = 'localhost'. Num servidor de aplicações, associe-o a 127.0.0.1 e coloque um reverse proxy à frente. Fechar o listener é melhor do que filtrá-lo na firewall, porque deixa de existir algo a que ligar.

Não confie no UFW para proteger as portas publicadas pelo Docker. Publique as portas dos contentores num endereço específico, em vez de todas as interfaces. Por exemplo, use -p 127.0.0.1:8080:80, para que a porta só seja acessível a partir do host e do proxy que configurar explicitamente. Quando um contentor tiver realmente de ser público, coloque-o atrás de um reverse proxy Traefik e publique apenas o proxy, não cada aplicação.

Adicione regras IPv6 à firewall do seu fornecedor ou aceite que ela não protege o IPv6 e deixe o UFW ou o nftables no host assumir essa função.

Verifique se a porta está realmente fechada

Execute novamente o mesmo teste externo depois das alterações:

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

A porta que respondeu antes deve agora recusar a ligação ou atingir o tempo limite. O nmap deve indicá-la como filtrada ou fechada. Se uma porta continuar aberta, reveja as três fontes acima: um serviço ainda associado a :: sem nenhuma regra à frente, uma regra do Docker colocada antes do UFW ou uma firewall do fornecedor que nunca processou IPv6.

Manter os serviços sensíveis totalmente fora da Internet pública é ainda mais seguro. Coloque o SSH e os painéis de administração atrás de uma VPN WireGuard e filtre as respetivas portas para que respondam apenas através do túnel. Assim, a questão da exposição por IPv6 deixa de se aplicar a esses serviços. Para abrandar os scans de força bruta que atingem tudo o que permanece público, adicione o Fail2ban à frente do SSH sobre uma firewall com política de bloqueio por omissão.

Se as portas ainda são um conceito novo para si, leia primeiro o que são portas e como os serviços escutam.

FAQ

O UFW bloqueia IPv6 por predefinição?

Numa instalação moderna do Ubuntu 24.04, sim. O UFW lê IPV6=yes de /etc/default/ufw e aplica cada regra a IPv4 e IPv6. O ufw status mostra as regras IPv6 com o sufixo (v6). O problema surge quando depende de uma firewall do fornecedor que filtra apenas IPv4, quando as regras escritas manualmente para iptables não têm uma regra equivalente em ip6tables ou quando o Docker publica uma porta sem passar pelo UFW. IPV6=no (de uma imagem antiga ou de um tutorial antigo) não abre IPv6. Com essa opção, um UFW ativo bloqueia todo o tráfego IPv6, exceto o tráfego de loopback. Verifique esta opção com grep IPV6 /etc/default/ufw.

Como verifico o que o meu VPS está a expor em IPv6?

Execute sudo ss -tlnp e anote cada listener cujo endereço local comece por [::]. Isso significa que responde em todas as interfaces IPv6. Depois, a partir de outra máquina, teste diretamente o endereço IPv6 público do servidor com curl -6 -v http://[YOUR:IPV6::ADDR]:PORT/ ou faça uma verificação com nmap -6 YOUR:IPV6::ADDR. Qualquer porta aberta na verificação IPv6, mas fechada em IPv4, é uma falha de configuração.

Porque consigo aceder à porta do meu contentor Docker quando o UFW indica que está bloqueada?

Quando publica uma porta com -p, o Docker insere as suas próprias regras de firewall antes das regras do UFW. Por isso, a porta publicada fica acessível, mesmo que ufw status a mostre como negada. Isto acontece em IPv4 e também em IPv6 quando o suporte IPv6 do Docker está ativo. Publique a porta num endereço específico, como -p 127.0.0.1:8080:80, ou coloque o contentor atrás de um reverse proxy e publique apenas o proxy.

Ainda preciso de uma firewall IPv6 se a minha firewall IPv4 estiver bem configurada?

Sim. IPv4 e IPv6 são pilhas de rede separadas, com regras de firewall separadas. Um conjunto perfeito de regras IPv4 não tem efeito sobre o tráfego IPv6. Se o seu VPS tiver um endereço IPv6 público, como acontece com quase todos, qualquer serviço que escute em :: continuará acessível por IPv6 até que uma regra de firewall IPv6 ou uma ligação a loopback o impeça.

Como faço um serviço escutar apenas em IPv4 ou apenas em localhost?

Defina o endereço de bind do serviço na respetiva configuração. Use 127.0.0.1 para aceitar ligações apenas no loopback IPv4 ou 0.0.0.0 para aceitar ligações em todos os endereços IPv4 sem um listener IPv6. O Postgres usa listen_addresses, o SSH usa ListenAddress e a maioria dos servidores de aplicações disponibiliza uma opção de host ou de bind. Confirme o resultado com sudo ss -tlnp e verifique se Local Address já não mostra [::].