UFW与IPv6:VPS上的IPv6防火墙漏洞
UFW规则和云防火墙可能只保护IPv4,让VPS服务通过公网IPv6直接暴露。了解Ubuntu 24.04中的成因、检查监听端口的方法,以及如何关闭IPv6暴露。
一句话说明 IPv6 防火墙陷阱
您的防火墙保护 IPv4。您的 VPS 几乎肯定也有一个公网 IPv6 地址,并且许多服务默认会监听该地址。如果防火墙只覆盖 IPv4,或者您依赖的云防火墙只过滤 IPv4,那么这些服务中的每一个都可以通过 IPv6 从整个互联网访问,而您的 IPv4 端看起来仍然受到严格限制。您使用 curl 测试端口,看到连接被拒绝,于是认为系统是安全的。攻击者通过 IPv6 连接到同一个端口,就能直接进入。
本指南说明在普通的 Ubuntu 24.04 VPS 上,这个漏洞是如何产生的、如何准确查看您暴露了哪些服务,以及如何关闭这些暴露面。问题不在于 UFW。现代 Ubuntu 安装中的 UFW 已经支持 IPv6。暴露来自周围的各个层,以及您不知道正在监听的服务。
为什么您的 VPS 一开始就启用了 IPv6
如今,几乎所有 VPS 都会在 IPv4 地址之外提供一个公网 IPv6 地址,通常是完整的 /64。请检查您的 VPS:
ip -6 addr show scope global2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> ...
inet6 2001:db8:2a::1/64 scope global这个 2001:db8:2a::1 可从互联网的任何位置路由访问,与您的 IPv4 地址完全相同。现在查看正在监听的端口:
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-proxy仔细查看 Local Address 列。0.0.0.0:22 表示“监听所有 IPv4 地址”。[::]:22 表示“监听所有 IPv6 地址”。127.0.0.1:5432 绑定到 loopback,完全不对公网开放,因此 Postgres 这一行是安全的。两行 [::] 会通过 IPv6 响应整个互联网,而 docker-proxy 则是您容易忘记曾经启动的那类服务。
大多数 daemon 默认绑定到 ::,因为在 Linux 中,:: socket 通常也会接受 IPv4 连接。因此,新服务器的默认状态是“在两个协议栈上对所有地址提供响应”。防火墙是唯一位于服务之前的防线,所以只检查其中一个协议栈的防火墙会带来实际问题。
IPv6 暴露的实际来源
通常有 4 个来源。在一台服务器上,可能只存在其中一个,也可能同时存在多个。
1. 仅过滤 IPv4 的云防火墙。 许多云服务商的防火墙和安全组产品都是围绕 IPv4 构建的,因此可能忽略 IPv6,或者需要您手动添加单独的 IPv6 规则。如果您唯一使用的防火墙是云服务商控制面板中的防火墙,而它不覆盖 IPv6,那么无论 IPv4 上关于端口 22 的设置显示什么,您的 [::] 服务都处于开放状态。请阅读云服务商的防火墙文档,并专门查找 IPv6。
2. 手动编写的 iptables 规则,但没有配置 ip6tables。 iptables 命令只处理 IPv4 规则表。IPv6 使用完全独立的命令 ip6tables,并拥有独立的规则。如果您编写了包含大量 iptables -A INPUT ... 行的防火墙脚本,却从未编写对应的 ip6tables 规则,那么 IPv6 防火墙就是空的。而默认策略为 ACCEPT 的空 INPUT 链会允许所有流量:
sudo ip6tables -L INPUT -nChain INPUT (policy ACCEPT)
target prot opt source destination这两段输出完整展示了问题:IPv4 已经过滤,IPv6 却接受来自任意位置的流量。
3. Docker 绕过防火墙直接发布端口。 运行 docker run -p 8080:80 时,Docker 会将自己的规则插入到 UFW 规则之前。因此,即使 ufw status 显示该端口被拒绝,已发布的端口仍然可以访问。在现代 Docker 中,IPv6 也存在同样的问题。Docker 为什么会绕过 UFW,以及如何正确过滤容器端口解释了其工作机制和解决方法。有关如何声明这些已发布端口,请参阅 VPS 上的 Docker Compose 基础。
4. 已关闭 IPv6 的 UFW。 UFW 支持 IPv6,但必须启用相应设置。检查该开关:
grep IPV6 /etc/default/ufw现代 Ubuntu 默认包含 IPV6=yes,因此 UFW 会将每条规则同时应用于 IPv4 和 IPv6。如果您看到的是来自旧镜像或旧教程的 IPV6=no,那么您编写的所有 UFW 规则都只适用于 IPv4,IPv6 将处于未管理状态。
准确查看对外暴露的内容
不要猜测。从外部进行测量。首先列出监听端口,并记录所有绑定到 :: 的端口:
sudo ss -tlnp | grep '::'然后从另一台计算机连接服务器的公网 IPv6 地址,并尝试访问一个你认为已关闭的端口:
curl -6 -v http://[2001:db8:2a::1]:8080/如果返回页面或 banner,说明该端口在 IPv6 上处于开放状态。关闭的端口会返回 Connection refused 或发生超时。这两种失败表示的信号不同;拒绝连接与连接超时的区别可以告诉你主机是否已响应并拒绝了连接,还是防火墙静默丢弃了数据包。要获得完整结果,请从服务器外部使用 nmap 扫描该 IPv6 地址:
nmap -6 2001:db8:2a::1nmap 报告为 IPv6 开放的每个端口,整个互联网都可以访问,无论你的 IPv4 扫描结果如何。将 IPv4 和 IPv6 扫描结果并排比较,是发现差异最快的方法:在 -6 上开放但在 IPv4 上关闭的任何端口,都是防火墙遗漏的服务。
缩小差距
让 UFW 同时覆盖 IPv4 和 IPv6,并将默认策略设为拒绝。 确认该开关,然后设置默认拒绝入站流量的策略,只放行必要的流量:
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如果启用 IPV6=yes 时 UFW 已处于活动状态,必须运行 sudo ufw reload 后更改才会生效。
ufw status 会将每条规则列出两次,一次是普通规则,另一次带有 (v6) 后缀。看到 (v6) 行时,说明 UFW 正在过滤 IPv6:
22/tcp ALLOW IN Anywhere
22/tcp (v6) ALLOW IN Anywhere (v6)如果手动管理 iptables,必须在 ip6tables 中复制每条规则,或者改用 nftables。nftables 的 inet 表可在同一处覆盖 IPv4 和 IPv6,从而避免这一类错误。手动编写规则时,使用单个 nftables inet filter 表是最简洁的解决方案。如果 VPS 运行的是 Rocky 或 AlmaLinux,而不是 Ubuntu,则无需配置 UFW;应改为管理 firewalld,它会同时将区域规则应用到 IPv4 和 IPv6。
将不应公开访问的服务绑定到回环地址。 数据库、管理面板或指标端点通常完全不需要公网地址。将其绑定到 127.0.0.1 和 ::1,使其从一开始就不会监听可路由地址。对于 Postgres,设置 listen_addresses = 'localhost'。对于应用服务器,将其绑定到 127.0.0.1,并在前面部署反向代理。关闭监听端口比使用防火墙阻止访问更可靠,因为这样根本没有可访问的监听端点。
不要依赖 UFW 保护 Docker 发布的端口。 将容器端口发布到指定地址,而不是所有网络接口。例如使用 -p 127.0.0.1:8080:80,这样该端口只能从主机以及你明确配置代理到该端口的来源访问。容器确实需要公开访问时,应将其置于 Traefik 反向代理之后,只发布代理端口,不要分别发布每个应用的端口。
在云服务商防火墙中添加 IPv6 规则,否则就应承认它无法充当 IPv6 防火墙,并改由主机上的 UFW 或 nftables 负责。
确认端口确实已关闭
完成修改后,在外部再次执行相同的测试:
curl -6 -v http://[2001:db8:2a::1]:8080/
nmap -6 2001:db8:2a::1之前响应的端口现在应拒绝连接或超时,nmap 应报告该端口为 filtered 或 closed。如果端口仍处于开放状态,请重新检查上文的四个来源:某个服务仍绑定在 ::,且前面没有防火墙规则;Docker 规则位于 UFW 规则之前;或者云服务商的防火墙根本没有处理 IPv6 流量。
完全不让敏感服务暴露在公网中更安全。通过 WireGuard VPN 保护 SSH 和管理面板,并使用防火墙限制其端口,使其只在隧道上响应。这样就不需要再考虑 IPv6 暴露问题。对于仍需公开的服务,可在默认拒绝的防火墙之上,再配置 在 SSH 前部署 Fail2ban,以减缓针对这些服务的暴力破解扫描。
如果你还不熟悉端口,请先阅读 什么是端口以及服务如何监听端口。
FAQ
UFW 默认会阻止 IPv6 吗?
在现代 Ubuntu 24.04 安装中,会。UFW 从 IPV6=yes 读取 /etc/default/ufw,并将每条规则同时应用于 IPv4 和 IPv6;ufw status 会显示带有 (v6) 后缀的 IPv6 规则。以下情况容易造成误判:IPV6=no(来自旧镜像或旧教程);依赖只过滤 IPv4 的云服务商防火墙;或者 Docker 绕过 UFW 发布端口。使用 grep IPV6 /etc/default/ufw 检查该开关。
如何检查 VPS 在 IPv6 上暴露了哪些服务?
运行 sudo ss -tlnp,记录所有本地地址以 [::] 开头的监听项。这表示服务会在所有 IPv6 接口上响应。然后从另一台机器直接使用 curl -6 -v http://[YOUR:IPV6::ADDR]:PORT/ 测试服务器的公网 IPv6 地址,或使用 nmap -6 YOUR:IPV6::ADDR 扫描该地址。IPv6 扫描中开放而 IPv4 中关闭的端口,就是暴露缺口。
为什么 UFW 显示 Docker 容器端口已阻止,我仍然可以访问它?
使用 -p 发布端口时,Docker 会将自己的防火墙规则插入到 UFW 规则之前。因此,即使 ufw status 将该端口列为拒绝,发布的端口仍然可以访问。IPv4 上会出现这种情况;启用 Docker IPv6 支持时,IPv6 上也会出现。将端口发布到特定地址,例如 -p 127.0.0.1:8080:80;或者将容器置于反向代理之后,只发布代理端口。
如果 IPv4 防火墙配置完善,仍然需要 IPv6 防火墙吗?
需要。IPv4 和 IPv6 是独立的网络协议栈,也使用独立的防火墙规则。完善的 IPv4 规则对 IPv6 流量不起作用。如果 VPS 具有公网 IPv6 地址,而绝大多数 VPS 都有,那么监听 :: 的任何服务都会持续通过 IPv6 访问,除非 IPv6 防火墙规则阻止了访问,或服务绑定到了回环地址。
如何让服务只监听 IPv4,或只监听 localhost?
在服务自身的配置中设置绑定地址。绑定到 127.0.0.1 可只监听 IPv4 回环接口;绑定到 0.0.0.0 可监听所有 IPv4 地址且不监听 IPv6。Postgres 使用 listen_addresses,SSH 使用 ListenAddress,大多数应用服务器则提供 host 或 bind 选项。使用 sudo ss -tlnp 确认结果,并检查 Local Address 中是否不再显示 [::]。