UFW e IPv6: como evitar brechas de segurança
Verifique se seus serviços estão expostos via IPv6 mesmo com UFW ativo. Aprenda a identificar portas abertas em endereços públicos e como fechar essa brecha.
A armadilha do firewall IPv6 em uma frase
Seu firewall protege o IPv4. Seu VPS quase certamente também possui um endereço IPv6 público, e muitos serviços escutam nele por padrão. Se o seu firewall cobre apenas IPv4, ou se você utiliza um firewall de nuvem que filtra apenas IPv4, cada um desses serviços estará acessível de toda a internet via IPv6, enquanto o seu lado IPv4 parece protegido. Você testa uma porta com curl, recebe uma conexão recusada e se sente seguro. Um atacante se conecta à mesma porta via IPv6 e entra.
Este guia mostra de onde vem essa brecha em um VPS comum com Ubuntu 24.04, como ver exatamente o que você está expondo e como fechar essa brecha. O UFW não é o culpado aqui. Em uma instalação moderna do Ubuntu, o UFW já gerencia o IPv6. A exposição vem das camadas ao redor dele e de serviços que você não sabia que estavam escutando.
Por que seu VPS possui IPv6
Quase todo VPS atual vem com um endereço IPv6 público, geralmente um /64 completo, além do endereço IPv4. Verifique o seu:
ip -6 addr show scope global2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> ...
inet6 2001:db8:2a::1/64 scope globalEsse 2001:db8:2a::1 é roteável de qualquer lugar da internet, exatamente como seu endereço IPv4. Agora veja o que está escutando:
sudo ss -tlnpState 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-proxyLeia a coluna Local Address com atenção. 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á vinculado ao loopback e não é público, portanto a linha do Postgres é segura. As duas linhas [::] respondem a toda a internet via IPv6, e a linha docker-proxy é o tipo que você esquece que iniciou.
A maioria dos daemons faz o bind em :: por padrão, porque no Linux um socket :: geralmente aceita IPv4 também. Assim, a configuração padrão de um servidor novo é "responder em ambas as pilhas, em todos os lugares". Seu firewall é a única coisa que impede isso, e é por isso que um firewall que enxerga apenas uma pilha é um problema real.
De onde vem a lacuna de IPv6
Existem quatro fontes comuns. Em um servidor específico, você pode ter uma ou várias delas simultaneamente.
1. Um firewall de nuvem que filtra apenas IPv4. Muitos firewalls de provedores e produtos de security-group foram desenvolvidos para IPv4 e ignoram o IPv6 ou exigem regras de IPv6 separadas que devem ser adicionadas manualmente. Se o seu único firewall for o do painel do provedor e ele não cobrir IPv6, seus serviços [::] estarão abertos, independentemente do que o painel indique sobre a porta 22 em IPv4. Leia a documentação de firewall do seu provedor e procure especificamente pelo termo IPv6.
2. iptables configurado manualmente sem ip6tables. O comando iptables altera apenas as tabelas IPv4. O IPv6 possui um comando completamente separado, ip6tables, com suas próprias regras. Se você escreveu um script de firewall com linhas iptables -A INPUT ... e nunca escreveu as regras ip6tables correspondentes, seu firewall IPv6 estará vazio. Uma chain INPUT vazia com política padrão ACCEPT permite todo o tráfego:
sudo ip6tables -L INPUT -nChain INPUT (policy ACCEPT)
target prot opt source destinationEsse output resume a armadilha em uma tela. O IPv4 é filtrado, enquanto o IPv6 aceita tudo.
3. Docker publicando portas diretamente através do seu firewall. Quando você executa docker run -p 8080:80, o Docker insere regras próprias antes das regras do UFW. Assim, uma porta publicada torna-se acessível mesmo quando o ufw status nega essa porta; em versões modernas do Docker, o mesmo ocorre no IPv6. Por que o Docker ignora o UFW e como filtrar portas de containers corretamente explica o mecanismo e as correções. Veja o básico do Docker Compose em um VPS para entender como essas portas publicadas são declaradas.
4. UFW com IPv6 desativado. O UFW suporta IPv6, mas apenas quando configurado para isso. Verifique a configuração:
grep IPV6 /etc/default/ufwVersões modernas do Ubuntu utilizam IPV6=yes, então o UFW aplica cada regra a ambas as pilhas. Se você encontrar IPV6=no, proveniente de uma imagem antiga ou de um guia defasado, todas as regras do UFW que você escreveu aplicam-se apenas ao IPv4, deixando o IPv6 sem gerenciamento.
Veja exatamente o que você está expondo
Não presuma. Meça a partir de fora. Primeiro, liste seus listeners e anote todos os que estão vinculados ao :::
sudo ss -tlnp | grep '::'Depois, de uma máquina diferente, conecte-se ao endereço IPv6 público do servidor e tente uma porta que você acredita estar fechada:
curl -6 -v http://[2001:db8:2a::1]:8080/Se isso retornar uma página ou um banner, a porta está aberta no IPv6. Uma porta fechada retorna Connection refused ou um timeout. Para uma visão completa, escaneie o endereço IPv6 com nmap de fora do servidor:
nmap -6 2001:db8:2a::1Cada porta que o nmap reportar como aberta via IPv6 é uma porta que toda a internet pode acessar, independentemente do que o seu scan IPv4 mostrou. Comparar os scans IPv4 e IPv6 lado a lado é a maneira mais rápida de encontrar a lacuna: qualquer coisa aberta no -6 mas fechada no IPv4 é um serviço que seu firewall não está protegendo.
Feche as brechas
Configure o UFW para cobrir ambos os stacks e defina o padrão como deny. Confirme a alteração, defina uma política de deny padrão para tráfego inbound e permita apenas o 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 verboseSe o UFW já estava ativo quando você alterou IPV6=yes, a mudança só terá efeito após executar sudo ufw reload.
ufw status lista cada regra duas vezes: uma de forma simples e outra com o sufixo (v6). Quando você vir as linhas (v6), o UFW está filtrando IPv6:
22/tcp ALLOW IN Anywhere
22/tcp (v6) ALLOW IN Anywhere (v6)Se você gerencia o iptables manualmente, replique cada regra no ip6tables, ou migre para o nftables, cujas tabelas inet cobrem IPv4 e IPv6 em um único lugar, eliminando esse tipo de erro. Uma única tabela de filtro nftables inet é a solução mais limpa quando você escreve as regras manualmente.
Vincule serviços que não devem ser públicos ao loopback. Um banco de dados, um painel de administração ou um endpoint de métricas raramente precisa de um endereço público. Vincule-os a 127.0.0.1 e ::1 para que nunca escutem em um endereço roteável. Para o Postgres, configure listen_addresses = 'localhost'. Para um servidor de aplicação, vincule-o a 127.0.0.1 e utilize um reverse proxy à frente. Fechar o listener é mais eficiente que usar firewall, pois não haverá nada para ser alcançado.
Não confie no UFW para proteger as portas publicadas pelo Docker. Publique as portas dos containers em um endereço específico em vez de todas as interfaces, por exemplo -p 127.0.0.1:8080:80, para que a porta seja acessível apenas pelo host e pelo que você deliberadamente encaminhar para ela. Quando um container precisar ser público, coloque-o atrás de um reverse proxy Traefik e publique apenas o proxy, não cada aplicação.
Adicione regras de IPv6 no firewall do seu provedor, ou aceite que ele não é o seu firewall para IPv6 e deixe que o UFW ou o nftables no host realizem essa tarefa.
Verifique se você realmente fechou o acesso
Execute o mesmo teste externo novamente após as alterações:
curl -6 -v http://[2001:db8:2a::1]:8080/
nmap -6 2001:db8:2a::1A porta que respondia anteriormente deve agora recusar a conexão ou apresentar timeout, e o nmap deve reportá-la como filtered ou closed. Se uma porta ainda estiver aberta, revise as quatro causas acima: um serviço ainda vinculado ao :: sem uma regra à frente, uma regra do Docker posicionada antes do UFW, ou um firewall do provedor que não detectou o IPv6.
Manter serviços sensíveis totalmente fora da internet pública é ainda mais seguro. Coloque o SSH e painéis de administração atrás de uma VPN WireGuard e aplique firewall em suas portas para que respondam apenas pelo túnel; assim, a questão da exposição via IPv6 deixa de se aplicar a eles. Para mitigar varreduras de brute-force que atingem o que permanecer público, utilize Fail2ban à frente do SSH sobre um firewall com política default-deny.
Se você não tem familiaridade com portas, o que são portas e como serviços escutam é o guia de introdução para leitura inicial.
FAQ
O UFW bloqueia IPv6 por padrão?
Em uma instalação moderna do Ubuntu 24.04, sim. O UFW lê IPV6=yes de /etc/default/ufw e aplica cada regra tanto para IPv4 quanto para IPv6, e ufw status exibe as regras IPv6 com um sufixo (v6). O problema ocorre quando IPV6=no (de uma imagem antiga ou tutorial antigo), quando você depende de um firewall de provedor que filtra apenas IPv4, ou quando o Docker publica uma porta ignorando o UFW. Verifique a configuração com grep IPV6 /etc/default/ufw.
Como verifico o que meu VPS está expondo em IPv6?
Execute sudo ss -tlnp e observe cada listener cujo endereço local comece com [::], o que significa que ele responde em todas as interfaces IPv6. Em seguida, de outra máquina, teste o endereço IPv6 público do servidor diretamente com curl -6 -v http://[YOUR:IPV6::ADDR]:PORT/, ou faça um scan com nmap -6 YOUR:IPV6::ADDR. Qualquer porta aberta no scan IPv6, mas fechada no IPv4, é uma vulnerabilidade.
Por que consigo acessar a porta do meu container Docker mesmo com o UFW bloqueando?
O Docker insere suas próprias regras de firewall antes das regras do UFW quando você publica uma porta com -p, portanto a porta publicada fica acessível mesmo que o ufw status a liste como negada. Isso ocorre no IPv4 e também no IPv6 quando o suporte a IPv6 do Docker está ativo. Publique para um endereço específico como -p 127.0.0.1:8080:80, ou coloque o container atrás de um reverse proxy e publique apenas o proxy.
Ainda preciso de um firewall IPv6 se meu firewall IPv4 for seguro?
Sim. IPv4 e IPv6 são stacks de rede distintos com regras de firewall distintas. Um conjunto perfeito de regras IPv4 não protege o tráfego IPv6. Se o seu VPS possui um endereço IPv6 público, e quase todos possuem, qualquer serviço escutando em :: permanece acessível via IPv6 até que uma regra de firewall IPv6 ou um binding de loopback o interrompa.
Como faço para um serviço escutar apenas em IPv4 ou apenas em localhost?
Configure o endereço de bind do serviço em seu próprio arquivo de configuração. Use bind em 127.0.0.1 para apenas loopback IPv4, ou 0.0.0.0 para todos os endereços IPv4 sem listener IPv6. O Postgres usa listen_addresses, o SSH usa ListenAddress, e a maioria dos servidores de aplicação possui uma flag de host ou bind. Confirme o resultado com sudo ss -tlnp e verifique se o Local Address não mostra mais [::].