UFW e IPv6: il VPS ha una porta aperta?
Le regole UFW e il firewall cloud possono filtrare solo IPv4: su Ubuntu 24.04 i servizi restano esposti via IPv6. Ecco come verificarlo e chiudere la falla.
La trappola del firewall IPv6 in una frase
Il firewall protegge IPv4. Il tuo VPS dispone quasi certamente anche di un indirizzo IPv6 pubblico e molti servizi sono in ascolto su IPv6 per impostazione predefinita. Se il firewall copre solo IPv4, oppure se ti affidi a un firewall cloud che filtra soltanto IPv4, tutti questi servizi sono raggiungibili da Internet tramite IPv6, mentre il lato IPv4 sembra protetto. Verifichi una porta con curl, ricevi una connessione rifiutata e ti senti al sicuro. Un attaccante si connette alla stessa porta tramite IPv6 e accede al sistema.
Questa guida mostra da dove deriva questa lacuna su un normale VPS Ubuntu 24.04, come vedere esattamente cosa stai esponendo e come chiudere l'esposizione. UFW non è il problema. In una installazione moderna di Ubuntu, UFW gestisce già IPv6. L'esposizione deriva dai livelli che lo circondano e dai servizi che non sapevi essere in ascolto.
Perché il tuo VPS usa IPv6
Oggi quasi tutti i VPS vengono forniti con un indirizzo IPv6 pubblico, spesso un’intera /64, oltre al relativo indirizzo IPv4. Verifica il tuo:
ip -6 addr show scope global2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> ...
inet6 2001:db8:2a::1/64 scope globalQuel 2001:db8:2a::1 è instradabile da qualsiasi punto di Internet, esattamente come il tuo indirizzo IPv4. Ora controlla quali servizi sono in ascolto:
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-proxyLeggi con attenzione la colonna Local Address. 0.0.0.0:22 significa "in ascolto su ogni indirizzo IPv4". [::]:22 significa "in ascolto su ogni indirizzo IPv6". 127.0.0.1:5432 è associato all’interfaccia di loopback e non è pubblico, quindi la riga di Postgres è sicura. Le due righe [::] rispondono a tutto Internet tramite IPv6, mentre la riga docker-proxy corrisponde al tipo di servizio che si dimentica di aver avviato.
La maggior parte dei demoni si associa a :: per impostazione predefinita, perché su Linux un socket :: di solito accetta anche connessioni IPv4. Di conseguenza, la configurazione predefinita di un server appena installato è: "rispondere su entrambi gli stack, ovunque". Il firewall è l’unico elemento che impedisce l’accesso diretto a questi servizi. Per questo un firewall che controlla un solo stack è un problema concreto.
Da dove deriva realmente la lacuna IPv6
Le cause più comuni sono quattro. Su un determinato server può essercene una oppure più di una contemporaneamente.
1. Un firewall cloud che filtra solo IPv4. Molti firewall dei provider e prodotti basati su security group sono stati progettati inizialmente per IPv4 e ignorano IPv6 oppure richiedono regole IPv6 separate, da aggiungere manualmente. Se l'unico firewall è quello del pannello del provider e non copre IPv6, i servizi [::] sono esposti indipendentemente da quanto indicato per la porta 22 su IPv4. Consulta la documentazione del firewall del provider e cerca esplicitamente il termine IPv6.
2. Regole iptables create manualmente senza ip6tables. Il comando iptables modifica soltanto le tabelle IPv4. IPv6 usa un comando completamente separato, ip6tables, con regole proprie. Se hai scritto uno script firewall pieno di righe iptables -A INPUT ... senza creare le corrispondenti regole ip6tables, il firewall IPv6 è vuoto e una catena INPUT vuota con policy predefinita ACCEPT consente tutto:
sudo ip6tables -L INPUT -nChain INPUT (policy ACCEPT)
target prot opt source destinationQuesto output mostra l'intero problema in una sola schermata. IPv4 è filtrato, mentre IPv6 accetta connessioni da chiunque.
3. Docker pubblica le porte aggirando direttamente il firewall. Quando esegui docker run -p 8080:80, Docker inserisce le proprie regole prima di quelle di UFW. Di conseguenza, una porta pubblicata è raggiungibile anche quando ufw status indica che la porta è negata; nelle versioni moderne di Docker lo stesso vale per IPv6. Perché Docker aggira UFW e come filtrare correttamente le porte dei container spiega il meccanismo e le correzioni. Consulta le nozioni di base di Docker Compose su un VPS per vedere come vengono dichiarate queste porte pubblicate.
4. UFW con IPv6 disabilitato. UFW gestisce anche IPv6, ma solo se è configurato per farlo. Controlla l'impostazione:
grep IPV6 /etc/default/ufwLe versioni moderne di Ubuntu includono IPV6=yes, quindi UFW applica ogni regola a entrambi gli stack. Se visualizzi IPV6=no, proveniente da un'immagine precedente o da una guida datata, tutte le regole UFW che hai scritto valgono solo per IPv4 e IPv6 rimane senza gestione.
Verifica esattamente cosa stai esponendo
Non fare supposizioni. Misura dall'esterno. Per prima cosa, elenca i socket in ascolto e annota tutti quelli associati a :::
sudo ss -tlnp | grep '::'Poi, da un'altra macchina, connettiti all'indirizzo IPv6 pubblico del server e prova una porta che ritieni chiusa:
curl -6 -v http://[2001:db8:2a::1]:8080/Se restituisce una pagina o un banner, la porta è aperta su IPv6. Una porta chiusa restituisce Connection refused oppure va in timeout. Questi due errori non indicano la stessa situazione: la differenza tra connessione rifiutata e timeout mostra se l'host ha risposto rifiutando la connessione oppure se un firewall ha eliminato il pacchetto senza risposta. Per una visione completa, esegui la scansione dell'indirizzo IPv6 con nmap da una macchina esterna al server:
nmap -6 2001:db8:2a::1Ogni porta che nmap segnala come aperta su IPv6 è raggiungibile da Internet, indipendentemente dal risultato della scansione IPv4. Confrontare affiancate le scansioni IPv4 e IPv6 è il modo più rapido per individuare la differenza: qualsiasi porta aperta su -6 ma chiusa su IPv4 indica un servizio non coperto dal firewall.
Chiudere la falla
Configurare UFW per entrambe le famiglie di indirizzi e impostare il criterio predefinito su deny. Verificare il cambio, quindi impostare una policy predefinita che neghi il traffico in ingresso e consentire soltanto ciò che serve:
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 UFW era già attivo quando è stato modificato IPV6=yes, il cambio diventa effettivo soltanto dopo l'esecuzione di sudo ufw reload.
ufw status elenca ogni regola due volte: una volta senza suffissi e una volta con il suffisso (v6). Quando sono presenti le righe (v6), UFW sta filtrando IPv6:
22/tcp ALLOW IN Anywhere
22/tcp (v6) ALLOW IN Anywhere (v6)Se si gestisce iptables manualmente, replicare ogni regola in ip6tables oppure passare a nftables. Le relative tabelle inet coprono IPv4 e IPv6 in un unico punto ed eliminano interamente questo tipo di errore. Se si scrivono le regole manualmente, una singola tabella di filtro nftables inet è la soluzione più pulita. Se il VPS esegue Rocky o AlmaLinux invece di Ubuntu, non c'è UFW da configurare: si gestisce invece firewalld come front end, e le relative regole di zona si applicano contemporaneamente a entrambe le famiglie di indirizzi.
Associare al loopback i servizi che non devono essere pubblici. Un database, un pannello di amministrazione o un endpoint per le metriche raramente hanno bisogno di un indirizzo pubblico. Associarli a 127.0.0.1 e ::1, in modo che non restino mai in ascolto su un indirizzo instradabile. Per Postgres, impostare listen_addresses = 'localhost'. Per un application server, associarlo a 127.0.0.1 e anteporre un reverse proxy. Chiudere il listener è più efficace che filtrarlo con il firewall, perché in questo modo non c'è nulla da raggiungere.
Non affidarsi a UFW per proteggere le porte pubblicate da Docker. Pubblicare le porte dei container su un indirizzo specifico anziché su tutte le interfacce, ad esempio -p 127.0.0.1:8080:80, così la porta è raggiungibile soltanto dall'host e da ciò a cui si inoltra esplicitamente il traffico tramite proxy. Quando un container deve essere realmente pubblico, collocarlo dietro un reverse proxy Traefik e pubblicare soltanto il proxy, non ogni applicazione.
Aggiungere le regole IPv6 al firewall del provider oppure riconoscere che il firewall del provider non protegge IPv6 e affidare questo compito a UFW o nftables sull'host.
Verifica che le porte siano effettivamente chiuse
Ripeti lo stesso test dall'esterno dopo aver apportato le modifiche:
curl -6 -v http://[2001:db8:2a::1]:8080/
nmap -6 2001:db8:2a::1La porta che prima rispondeva ora dovrebbe rifiutare la connessione o andare in timeout, e nmap dovrebbe indicarla come filtrata o chiusa. Se una porta è ancora aperta, ripercorri le quattro fonti precedenti: un servizio ancora in ascolto su :: senza alcuna regola davanti, una regola Docker applicata prima di UFW oppure un firewall del provider che non ha mai gestito il traffico IPv6.
È ancora più sicuro mantenere completamente fuori da Internet i servizi sensibili. Metti SSH e i pannelli di amministrazione dietro una VPN WireGuard e configura il firewall in modo che le relative porte rispondano soltanto attraverso il tunnel; in questo modo il problema dell'esposizione IPv6 non si applica più a tali servizi. Per rallentare le scansioni brute-force che colpiscono i servizi ancora pubblici, aggiungi Fail2ban davanti a SSH a un firewall con criterio predefinito di negazione.
Se le porte sono un concetto nuovo, cosa sono le porte e come i servizi restano in ascolto è la guida introduttiva da leggere per prima.
FAQ
UFW blocca IPv6 per impostazione predefinita?
Su una moderna installazione di Ubuntu 24.04, sì. UFW legge IPV6=yes da /etc/default/ufw e applica ogni regola sia a IPv4 sia a IPv6; ufw status mostra le regole IPv6 con il suffisso (v6). Il problema si presenta quando IPV6=no (proveniente da un'immagine obsoleta o da un vecchio tutorial), quando si utilizza un firewall del provider che filtra soltanto IPv4 oppure quando Docker pubblica una porta oltre le regole di UFW. Verificare l'impostazione con grep IPV6 /etc/default/ufw.
Come posso verificare cosa espone il mio VPS su IPv6?
Eseguire sudo ss -tlnp e annotare ogni listener il cui indirizzo locale inizia con [::], perché indica che il servizio risponde su tutte le interfacce IPv6. Quindi, da un'altra macchina, testare direttamente l'indirizzo IPv6 pubblico del server con curl -6 -v http://[YOUR:IPV6::ADDR]:PORT/ oppure eseguire una scansione con nmap -6 YOUR:IPV6::ADDR. Qualsiasi porta aperta nella scansione IPv6 ma chiusa su IPv4 rappresenta una falla nella configurazione.
Perché posso raggiungere la porta del mio container Docker quando UFW indica che è bloccata?
Quando si pubblica una porta con -p, Docker inserisce le proprie regole firewall prima di quelle di UFW. Di conseguenza, la porta pubblicata è raggiungibile anche se ufw status la mostra come negata. Questo avviene su IPv4 e anche su IPv6 quando il supporto IPv6 di Docker è attivo. Pubblicare la porta su un indirizzo specifico, ad esempio -p 127.0.0.1:8080:80, oppure mettere il container dietro un reverse proxy e pubblicare soltanto il proxy.
Ho ancora bisogno di un firewall IPv6 se il mio firewall IPv4 è configurato correttamente?
Sì. IPv4 e IPv6 sono stack di rete separati, con regole firewall separate. Un insieme di regole IPv4 perfetto non protegge il traffico IPv6. Se il VPS dispone di un indirizzo IPv6 pubblico, come accade nella maggior parte dei casi, qualsiasi servizio in ascolto su :: resta raggiungibile tramite IPv6 finché una regola firewall IPv6 o un binding su loopback non lo impedisce.
Come posso fare in modo che un servizio ascolti solo su IPv4 o solo su localhost?
Impostare l'indirizzo di binding del servizio nella relativa configurazione. Usare 127.0.0.1 per il solo loopback IPv4 oppure 0.0.0.0 per tutti gli indirizzi IPv4 senza un listener IPv6. Postgres usa listen_addresses, SSH usa ListenAddress e la maggior parte dei server applicativi espone un'opzione host o bind. Verificare il risultato con sudo ss -tlnp e controllare che Local Address non mostri più [::].