IPv6 安全加固配置实战:内核参数与防火墙

IPv6 安全加固是很多服务器被忽视的短板。云主机与物理机默认开启 IPv6,但运维往往只在 iptables 里写了 v4 规则,IPv6 流量却能直接绕过全部访问控制直达业务端口。本文给出一套完整的 IPv6 安全加固配置,从内核参数、nftables/ip6tables 规则到 ICMPv6 与 NDP 防护,方案可直接复制执行,并在云安全组层面同步收口。

适用场景

  • 服务器使用了 v4 防火墙策略,但 ip -6 addr 显示网卡已自动获取到全球单播地址。
  • 云主机安全组只配置了 IPv4 规则,业务端口在 IPv6 侧实际处于完全开放状态。
  • 需要彻底关闭 IPv6,或需要在保留 IPv6 的前提下做最小化放行。
  • 遭到来自 IPv6 地址段的扫描、爆破或 CC 攻击,需要识别并封禁。

前置条件

  • 操作系统:CentOS/Rocky 8+、Ubuntu 20.04+ 或同代内核,内核版本 4.19 以上(nftables 原生链与 ICMPv6 类型匹配更完整)。
  • 权限:root。
  • 组件:nftables(推荐)或 iptables-services + ip6tables;iproute2、ndisc6(验证 NDP)。
  • 务必通过带外控制台(VNC/串口)操作。IPv6 规则写错会直接切断 SSH,只有带外通道才能恢复。
  • 建议先在测试节点验证,并提前准备好规则回滚命令。

原理说明

IPv6 与 IPv4 的四处关键差异

  • 地址配置自动化:IPv6 默认开启 SLAAC,主机收到路由器通告(Router Advertisement,RA)后自行生成地址,不需要 DHCP 服务器。这意味着同网段任何主机都可以伪造 RA 冒充网关,实施流量劫持。
  • NDP 取代 ARP:地址解析由邻居发现协议完成,依赖 ICMPv6 类型 133 至 137 的报文。一刀切封禁 ICMPv6 会导致 IPv6 网络直接不可用,这与 IPv4 上“禁 ping”的做法完全不同。
  • 扩展头带来绕过风险:IPv6 允许在固定头之后串联多个扩展头(路由头、分片头、逐跳选项头)。简化实现的 ACL 如果只解析固定头,就可能被扩展头绕过,其中路由头类型 0 已被 RFC 5095 明确废弃。
  • 隐私扩展改变源地址:开启 use_tempaddr 后,主机对外连接会周期性更换临时地址。基于源 IP 的封禁策略会因此失效,做限频与封禁前应关闭该特性。

加固目标

四个目标:明确 IPv6 是否必要;关闭不需要的地址自动配置与重定向接受;只放行必要端口并显式放行必需 ICMPv6 类型;在云安全组与主机防火墙两侧同时收口,避免“主机关了、云上还开”。

操作步骤

步骤一:现状盘点

先看清 IPv6 的真实暴露面,再决定策略:

# 网卡 IPv6 地址与作用域
ip -6 addr show scope global
# IPv6 监听端口(要重点看 0.0.0.0 之外的 [::] 监听)
ss -6 -lntup
# 当前 IPv6 防火墙规则
nft list table inet filter 2>/dev/null || ip6tables -L -n -v
# 是否存在默认路由与 RA 来源
ip -6 route show default
sysctl net.ipv6.conf.all.accept_ra net.ipv6.conf.all.forwarding

判读要点:ss -6 -lntup 中若出现业务端口的 [::]: 监听,而 v4 规则只放行了必要来源,则该端口在 IPv6 侧实际上处于无防护状态,这是最常见的风险点。

步骤二:内核参数加固

参数统一落在 /etc/sysctl.d/ 下,便于审计与回滚:

cat > /etc/sysctl.d/60-ipv6-hardening.conf <<'EOF'
# 服务器不接受路由器通告,地址必须静态配置
net.ipv6.conf.all.accept_ra = 0
net.ipv6.conf.default.accept_ra = 0
# 不接受 ICMPv6 重定向,防网关劫持
net.ipv6.conf.all.accept_redirects = 0
net.ipv6.conf.default.accept_redirects = 0
# 关闭源路由,防扩展头绕过
net.ipv6.conf.all.accept_source_route = 0
net.ipv6.conf.default.accept_source_route = 0
# 关闭隐私扩展,保证源地址稳定可封禁
net.ipv6.conf.all.use_tempaddr = 0
# ICMPv6 限速,缓解 NDP/RA 泛洪
net.ipv6.icmp.ratelimit = 100
# 反向路径校验(与业务代理共存时需评估)
net.ipv6.conf.all.rp_filter = 1
EOF
sysctl --system
sysctl net.ipv6.conf.all.accept_ra net.ipv6.conf.all.use_tempaddr

如果业务完全不需要 IPv6,可以整体关闭主机的 IPv6 协议栈。这种方式最彻底,但要确认所有依赖(部分 CDN 回源、容器网络、监控探针)都已经支持纯 v4:

cat > /etc/sysctl.d/61-disable-ipv6.conf <<'EOF'
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
net.ipv6.conf.lo.disable_ipv6 = 1
EOF
sysctl --system
ip -6 addr show | grep -c "inet6"   # 预期为 0

步骤三:nftables 规则(推荐方案)

使用 inet 家族可以把 v4 与 v6 写在同一张表里,避免两套规则漂移。必须显式放行 ICMPv6 的必要类型,否则邻居发现与路径 MTU 发现会中断:

cat > /etc/nftables.d/ipv6-hardening.nft <<'EOF'
table inet filter {
  set blackhole { type ipv6_addr; }
  chain input {
    type filter hook input priority 0; policy drop;
    iif lo accept
    ct state established,related accept
    # 必需 ICMPv6:不可达/包过大/超时/参数问题/回显/NDP 五类
    icmpv6 type { destination-unreachable, packet-too-big, time-exceeded, \
                  parameter-problem, echo-request, echo-reply, \
                  nd-router-solicit, nd-router-advert, \
                  nd-neighbor-solicit, nd-neighbor-advert } accept
    # 丢弃带路由头的包(含已废弃的 RH0)
    ip6 nexthdr 43 drop
    ip6 saddr @blackhole drop
    # NDP 与 RA 限速,缓解泛洪
    icmpv6 type nd-router-advert limit rate 5/second accept
    icmpv6 type nd-neighbor-solicit limit rate 20/second accept
    # 业务端口仅放行必要来源
    tcp dport { 80, 443 } accept
    tcp dport 22 ip6 saddr 2001:db8:100::/48 accept
    # 管理端口对外一律拒绝并记账
    tcp dport { 3306, 6379, 9200 } log prefix "v6-deny-db " drop
  }
  chain forward { type filter hook forward priority 0; policy drop; }
  chain output  { type filter hook output priority 0; policy accept; }
}
EOF
nft -f /etc/nftables.d/ipv6-hardening.nft
nft list table inet filter | head -40

对不需要 IPv6 的节点,最小化方案是直接丢弃所有入向 v6 流量(保留回环),一条规则即可:

nft add table inet v6drop 2>/dev/null
nft add chain inet v6drop input '{ type filter hook input priority -10; policy accept; }'
nft add rule inet v6drop input iifname != "lo" meta nfproto ipv6 drop
nft list table inet v6drop

步骤四:ip6tables 等价写法

仍在使用 iptables 体系的环境,用下面这套规则实现同样的收敛效果:

cat > /etc/sysconfig/ip6tables <<'EOF'
*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [0:0]
-A INPUT -i lo -j ACCEPT
-A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
-A INPUT -p ipv6-icmp -m icmp6 --icmpv6-type 1 -j ACCEPT
-A INPUT -p ipv6-icmp -m icmp6 --icmpv6-type 2 -j ACCEPT
-A INPUT -p ipv6-icmp -m icmp6 --icmpv6-type 3 -j ACCEPT
-A INPUT -p ipv6-icmp -m icmp6 --icmpv6-type 4 -j ACCEPT
-A INPUT -p ipv6-icmp -m icmp6 --icmpv6-type 128 -j ACCEPT
-A INPUT -p ipv6-icmp -m icmp6 --icmpv6-type 129 -j ACCEPT
-A INPUT -p ipv6-icmp -m icmp6 --icmpv6-type 133:136 -j ACCEPT
-A INPUT -m rt --rt-type 0 -j DROP
-A INPUT -p tcp -m multiport --dports 80,443 -j ACCEPT
-A INPUT -s 2001:db8:100::/48 -p tcp --dport 22 -j ACCEPT
COMMIT
EOF
ip6tables-restore < /etc/sysconfig/ip6tables
ip6tables -L INPUT -n -v --line-numbers

步骤五:关闭多余的 IPv6 监听

防火墙之外,还要从服务配置层面让监听退回 v4,减少暴露面。Nginx 与 MySQL 是两类最常见的“被动监听 v6”:

# Nginx:把监听收敛到明确地址(存于 /etc/nginx/conf.d/)
# listen [::]:80; 这类写法如无需要应删除
grep -rn "listen" /etc/nginx/conf.d/ | grep "::"
# MySQL:绑定到具体地址而不是 *
grep -n "^bind-address" /etc/my.cnf /etc/mysql/my.cnf 2>/dev/null
# Redis:确认是否监听 ::
grep -n "^bind" /etc/redis/redis.conf
systemctl reload nginx
ss -6 -lntup    # 复核,业务端口不应再出现 [::] 监听

步骤六:云安全组同步

主机防火墙只是第二道闸门,云平台安全组是第一道。只配 v4 不出网规则,等于给 IPv6 留了一扇后门。操作上需要:在安全组的 IPv6 入方向仅放行 80/443,并显式拒绝 22 以外的管理端口;IPv6 出方向按需收敛而非全放通;同时确认负载均衡与 CDN 侧是否已开启 IPv6 回源,避免加固后回源失败。

配置验证

加固效果必须从外部视角验证,仅看本机规则列表不充分:

# 1) 确认规则已生效
sysctl net.ipv6.conf.all.accept_ra net.ipv6.conf.all.accept_redirects
nft list table inet filter 2>/dev/null | grep -c "icmpv6 type"

# 2) 从外部做 IPv6 端口扫描(在另一台支持 v6 的主机上执行)
# nmap -6 -Pn -p 22,80,443,3306,6379 2001:db8::10
# 预期:仅 80/443(及白名单内的 22)为 open,数据库端口为 filtered

# 3) 检查是否还能收到 RA(服务器应为 0 条)
ip -6 route show | grep -c "ra"

# 4) 手动探测 NDP 是否正常(不应超时)
ndisc6 fe80::1%eth0 eth0 2>/dev/null | head -3

# 5) 复核监听面
ss -6 -lntup | awk '{print $5}' | sort -u

预期结果:内核参数返回加固后的值;规则表中 ICMPv6 相关规则数量大于 0;外部扫描显示非放行端口全部为 filtered;ip -6 route 中 RA 生成的路由条数为 0;NDP 探测正常返回。若外部扫描仍能看到数据库端口开放,说明泄露点在云安全组而非主机防火墙。

常见问题

FAQ 1:直接关闭 IPv6 会不会影响业务?

取决于业务的依赖情况。纯 v4 架构的 Web 业务,关闭 IPv6 通常没有影响,反而消除了一个常被忽略的暴露面。但三类场景必须谨慎:一是上游 CDN 或负载均衡已开启 IPv6 回源,源站关闭 v6 会导致回源失败;二是部分云服务(对象存储内网域名、托管数据库)优先解析到 v6 地址;三是自查脚本、监控探针或合规扫描要求 v6 可达。稳妥做法是先关闭 IPv6 并在灰度节点观察 24 小时,重点监测回源错误率与依赖服务连通性,确认无异常后再批量推广。如果不确定依赖关系,优先选择“保留 IPv6 但最小化放行”的方案,风险更低。

FAQ 2:只配置主机防火墙,不配置云安全组可以吗?

不可以,两者作用层不同。云安全组在虚拟化网络层生效,流量在到达网卡前就被过滤,攻击者无法通过主机内的漏洞利用关闭它;主机防火墙运行在操作系统内,一旦业务出现 RCE 或提权,规则就可能被篡改或清空。只配主机防火墙的风险是单点失效;只配云安全组的问题是内网横向流量与出向流量缺少管控。正确做法是两侧保持同一份放行清单:云安全组负责边界收敛与 IPv6 关停,主机侧负责进程级与协议级细化,任何一次策略变更都要同步两边,并在变更记录中标注生效时间,方便出现故障时快速定位是哪一层拦截。

总结

IPv6 安全加固的核心是三步收口:先盘点暴露面,用 ss -6 -lntup 和外部扫描找出真实开放的端口;再对齐两侧策略,内核参数关闭 RA 与重定向、nftables 显式放行必需 ICMPv6 类型并丢弃路由头,云安全组同步只放行 80/443;最后做外部验证,用 nmap -6 从外网确认非放行端口全部 filtered。只要 ICMPv6 类型放行清单写全,加固不会影响正常连通性,而暴露面会显著收窄。

需要特别提醒的是,IPv6 地址数量极大,基于单 IP 的封禁策略效率远低于 IPv4。面向 IPv6 的防护应更多依赖前缀级封禁(/48、/64)与行为特征识别,而不是逐地址拉黑。若站点同时承受 IPv4 与 IPv6 的攻击流量,建议在 CDN/WAF 层统一接入并开启 IPv6 回源校验,把两层流量的清洗策略合并管理。