5月28日 02:18

什么是 DNSSEC,它如何保证 DNS 安全

DNSSEC(DNS Security Extensions) 是 DNS 协议的安全扩展,通过数字签名验证 DNS 响应的真实性和完整性,防止缓存投毒、DNS 欺骗等中间人攻击。需要明确的是,DNSSEC 只提供数据认证,不加密 DNS 查询——这是它和 DoH/DoT 的本质区别。

为什么需要 DNSSEC

传统 DNS 的安全缺陷

DNS 协议设计于 1980 年代,天生没有认证机制。解析器收到一条 DNS 响应后,无法判断这条响应是否来自真正的权威服务器,还是攻击者伪造的。

shell
用户查询 www.bank.com DNS 查询(明文,无认证) 攻击者在响应到达前注入伪造 IP 用户被引导至钓鱼站点

这种攻击之所以可行,是因为 DNS 响应只需要匹配查询的事务 ID(16 位,仅 65536 种可能),攻击者可以通过大量发送伪造响应来碰运气。2008 年的 Kaminsky 攻击把这个问题推到了极限——攻击者可以在数秒内投毒 DNS 缓存。

主要威胁类型

  • DNS 缓存投毒:向递归解析器的缓存中注入伪造记录,影响所有使用该解析器的用户
  • 中间人攻击:在 DNS 查询传输过程中截获并篡改响应
  • DNS 欺骗:伪造 DNS 响应,将用户导向恶意站点

DNSSEC 的解决思路

DNSSEC 不加密流量,而是在 DNS 数据上附加数字签名。解析器收到响应后,用预先建立的信任链验证签名——签名不通过就拒绝这条响应。

shell
用户查询 www.bank.com 返回 A 记录 + RRSIG 签名 用 DNSKEY 验证 RRSIG 验证失败 → 拒绝伪造响应(SERVFAIL) 验证通过 → 返回正确 IP

DNSSEC 的信任链

DNSSEC 的核心设计是一个从根域到目标域名的信任链(Chain of Trust),每一级为下一级做担保。

信任锚点

shell
根密钥(Root Trust Anchor) ↓ DS 记录担保 .com TLD 的 DNSKEY ↓ DS 记录担保 example.com 的 DNSKEY ↓ 用 DNSKEY 验证 example.com 的 A/AAAA/MX 等记录

根密钥是全球信任的起点,由 ICANN 管理。根密钥的公钥被硬编码在支持 DNSSEC 的解析器中(称为 trust anchor),不需要在线获取。2010 年根域完成签名,意味着整条信任链有了可靠的起点。

密钥双层架构:KSK 与 ZSK

DNSSEC 采用双密钥设计,将密钥签名和数据签名解耦:

KSK(Key Signing Key)

  • 仅用于签名 DNSKEY 记录集
  • 密钥较长(通常 2048-4096 位),长期使用(1-2 年轮换)
  • 变更时需要更新父域的 DS 记录,操作成本高
  • 私钥应离线保存,理想情况下存储在 HSM 中

ZSK(Zone Signing Key)

  • 用于签名区域内的所有其他记录(A、AAAA、MX 等)
  • 密钥较短(通常 1024-2048 位),频繁轮换(每 30-90 天)
  • 轮换不影响信任链,因为 KSK 没变

为什么分成两层?如果只用一把密钥,轮换时必须同时更新父域的 DS 记录,而 DS 记录的传播可能需要数小时甚至数天。双密钥设计让 ZSK 可以独立轮换,安全性和运维效率兼顾。

DNSSEC 记录类型详解

DNSKEY——存储公钥

dns
example.com. 3600 IN DNSKEY 256 3 8 ( AwEAAbX8qU... ) ; Base64 编码的公钥

三个关键字段:

  • Flags:256 = ZSK,257 = KSK。判断这是哪类密钥
  • Protocol:固定为 3,表示 DNSSEC
  • Algorithm:8 = RSA/SHA256,13 = ECDSA/P256,15 = Ed25519。算法号决定了验证时使用的具体算法

RRSIG——资源记录的签名

dns
www.example.com. 3600 IN RRSIG A 8 3 3600 ( 20240101000000 20240108000000 12345 example.com. oKx8j3... ) ; Base64 编码的签名

关键字段:

  • Type Covered:被签名的记录类型(这里是 A 记录)
  • Labels:域名层级数,用于通配符验证
  • Original TTL:签名时记录的 TTL,防止 TTL 被篡改
  • Signature Expiration / Inception:签名的有效时间窗口
  • Key Tag:指向用于签名的 DNSKEY
  • Signer's Name:签名者域名
  • Signature:数字签名本身

解析器验证时,用 Key Tag 找到对应的 DNSKEY,用公钥解密签名,与记录的哈希值比对。

DS——信任链的桥梁

dns
example.com. 3600 IN DS 12345 8 2 ( 2BB183AF5F22588179A53B0A98631FAD1A2DD3475 )

DS 记录存储在父域(.com)中,内容是子域(example.com)KSK 的哈希值。它告诉解析器:"如果子域的 DNSKEY 对应这个哈希,那就是可信的。"

DS 记录是信任链的关键环节——没有 DS 记录,信任链就断裂了,解析器无法从父域验证子域。

NSEC / NSEC3——证明不存在

dns
; NSEC:直接列出相邻域名 www.example.com. 3600 IN NSEC a.example.com. A AAAA ; NSEC3:对域名做哈希后再排列 2t7b...gpq.example.com. 3600 IN NSEC3 1 0 10 ABCDEF ( 2v91...kjm A AAAA )

DNS 的正常响应只有两种:有记录或没有。DNSSEC 需要对"没有"也做认证,否则攻击者可以谎称某个域名不存在。

  • NSEC:直接返回域名排序列表中的下一条记录。问题是暴露了区域内的域名列表(zone walking)
  • NSEC3:对域名做哈希后再排序,攻击者无法直接遍历域名。额外支持 opt-out 机制,让大量未签名的委托不需要单独签名

DNSSEC 验证流程

一个完整的 DNSSEC 验证过程:

shell
1. 递归解析器查询 www.example.com A 记录 2. 权威服务器返回 A 记录 + RRSIG(A) 3. 解析器查询 example.com 的 DNSKEY 4. 返回 DNSKEY(KSK) + DNSKEY(ZSK) + RRSIG(DNSKEY) 5. 用 ZSK 验证 RRSIG(A) → A 记录可信 6. 用 KSK 验证 RRSIG(DNSKEY) → ZSK 可信 7. 查询 .com 的 DS 记录,获取 example.com KSK 的哈希 8. 验证 KSK 的哈希与 DS 记录匹配 → KSK 可信 9. 重复步骤 7-8,沿 .com → 根 逐级向上验证 10. 到达根信任锚点(硬编码在解析器中) 所有验证通过 → 返回 A 记录给客户端(AD 标志位置 1任一环节失败 → 返回 SERVFAIL

注意第 10 步:解析器不需要在线查询根密钥,它已经在本地配置了根信任锚。这也是 DNSSEC 安全性的基础——只要根密钥不泄露,整条链就是可信的。

DNSSEC 的局限性与常见误解

DNSSEC 不做什么

  • 不加密 DNS 查询:DNSSEC 只认证响应数据,查询和响应仍然是明文传输。想加密需要用 DoH(DNS over HTTPS)或 DoT(DNS over TLS)
  • 不提供机密性:任何人都能看到你查询了什么域名
  • 不防 DDoS:DNSSEC 实际上增大了响应体积,反而可能放大 DDoS 攻击效果
  • 不防钓鱼:如果域名本身就是钓鱼域名(如 paypa1.com),DNSSEC 照样认证通过

DNSSEC vs DoH/DoT

维度DNSSECDoH/DoT
目的数据认证(防篡改)查询加密(防窃听)
防护对象响应内容的真实性传输过程的机密性
是否互相替代
理想组合两者配合使用两者配合使用

两者解决的是不同问题:DNSSEC 保证"收到的数据没被改过",DoH/DoT 保证"别人看不到你查了什么"。生产环境中应该同时启用。

DNSSEC 部署实践

生成密钥对

bash
# 生成 KSK(密钥签名密钥) dnssec-keygen -f KSK -a RSASHA256 -b 2048 -n ZONE example.com # 生成 ZSK(区域签名密钥) dnssec-keygen -a RSASHA256 -b 1024 -n ZONE example.com

现代实践建议使用 ECDSA(算法 13)或 Ed25519(算法 15)替代 RSA,密钥更短、签名更快、安全性相当。

签名区域文件

bash
# 对区域文件签名 dnssec-signzone -A -3 $(head -c 1000 /dev/urandom | sha1sum | cut -b 1-16) -N INCREMENT -o example.com -t example.com.db

-3 参数启用 NSEC3 并指定盐值,-N INCREMENT 自动递增序列号。

上传 DS 记录

bash
# 从 KSK 生成 DS 记录 dnssec-dsfromkey Kexample.com.+008+12345.key # 输出类似: # example.com. IN DS 12345 8 2 2BB183AF5F22588179A53B0A98631FAD1A2DD3475

将这条 DS 记录提交给域名注册商,注册商会将其推送到父域(.com)的区域文件中。DS 记录生效后,信任链才算建立完成。

配置递归解析器

bind
; BIND named.conf options { dnssec-validation auto; ; 自动验证,使用内置根信任锚 };
bash
# Unbound 配置 server: auto-trust-anchor-file: "/var/lib/unbound/root.key"

auto 模式下 BIND 会自动维护根信任锚,包括处理根密钥轮换(Root KSK Roll)。

密钥轮换注意事项

ZSK 轮换相对简单,发布新密钥、用新密钥签名、停止使用旧密钥即可。但要注意预发布(pre-publish)策略:先发布新 ZSK 但不立即用它签名,给解析器足够的缓存时间获取新密钥,再切换签名。

KSK 轮换则涉及 DS 记录的更新,流程更复杂:

  1. 生成新 KSK,加入 DNSKEY 记录集
  2. 用新旧 KSK 同时签名 DNSKEY 记录集
  3. 向注册商提交新 DS 记录
  4. 等待 DS 记录全球传播(TTL 到期)
  5. 移除旧 KSK

ICANN 在 2018 年进行了第一次根 KSK 轮换(KSK-2010 → KSK-2017),整个过程耗时数月,需要各解析器运营商配合更新信任锚。

DNSSEC 部署现状与排错

全球部署情况

层级DNSSEC 状态
根域2010 年完成签名
.com / .net / .org已签名
.cn已签名
二级域名(如 example.com)参差不齐,大型网站覆盖率仍低

根域和主流 TLD 已全部支持 DNSSEC,但二级域名的部署率仍然偏低。根据 APNIC 的统计,全球 DNSSEC 验证率大约在 30% 左右,说明很多解析器虽然支持 DNSSEC,但并没有严格验证。

常见排错命令

bash
# 查询 DNSSEC 记录 dig +dnssec www.example.com # 检查 AD 标志(Authenticated Data) dig +dnssec +adflag www.example.com # 追踪整条验证链 dig +dnssec +trace www.example.com # 在线可视化工具 # https://dnsviz.net/ # https://dnssec-debugger.verisignlabs.com/

常见故障模式:

  • DS 记录与 DNSKEY 不匹配:通常发生在 KSK 轮换后忘记更新 DS 记录
  • 签名过期:RRSIG 有有效期,忘记重新签名会导致验证失败
  • NSEC3 参数不一致:签名时和查询时的 NSEC3 参数必须一致

面试常见问题

DNSSEC 能防止 DNS 劫持吗?

DNSSEC 可以防止传输过程中的 DNS 劫持(伪造响应),但无法防止以下情况:客户端本地 DNS 配置被篡改、攻击者控制了权威 DNS 服务器本身、本地 hosts 文件被修改。DNSSEC 认证的是"数据来源的真实性",不是"数据本身是否安全"。

追问:如果权威服务器被攻破,DNSSEC 还能保护吗?——不能。攻击者拿到私钥后可以签发合法签名。所以 DNSSEC 的安全前提是私钥安全,KSK 私钥应存储在 HSM 中。

DNSSEC 和 DoH 是什么关系?

两者互补,不替代。DNSSEC 认证数据的真实性(防篡改),DoH 加密查询传输(防窃听)。即使使用 DoH,如果不用 DNSSEC,递归解析器到权威服务器之间仍可能被投毒。反之,DNSSEC 不加密查询,ISP 仍能看到你查了什么。

KSK 和 ZSK 为什么要分开?

单密钥方案下,每次轮换密钥都必须更新父域的 DS 记录,而 DS 记录的传播可能需要数小时到数天,期间信任链可能断裂。双密钥设计让 ZSK 可以频繁轮换(30-90 天)而不影响 DS 记录,只有 KSK 轮换时才需要更新 DS,而 KSK 的轮换周期通常是 1-2 年。

DNSSEC 对 DNS 性能有什么影响?

三个方面:响应体积增大(RRSIG 和 DNSKEY 附加数据可能让响应从几十字节膨胀到上千字节)、额外查询(需要获取 DNSKEY 和 DS 记录)、签名验证的 CPU 开销。现代硬件上验证耗时通常在微秒级,真正的瓶颈是额外的网络往返。DNS 缓存可以缓解大部分开销。

追问:为什么 DNSSEC 响应容易触发 TCP 回退?——传统 DNS 的 UDP 包限制为 512 字节,DNSSEC 签名后经常超出这个限制。虽然 EDNS0 可以扩展 UDP 包大小(通常到 4096 字节),但部分网络设备会截断大包或丢弃 EDNS0 选项,导致回退到 TCP 重试。

标签:DNS