DNS 安全与加密解析

系统讲解 DNS 的安全威胁与加密解析方案:从缓存投毒、Kaminsky 攻击与源端口随机化,到 DNSSEC 的签名链、DS 委派与 NSEC/NSEC3 否定证明,再到 DoT/DoH 的报文封装、ECS 隐私权衡与 Unbound、dnsdist 的落地配置。

DNS 诞生于一个没有攻击者的年代:查询与响应都是明文 UDP,解析器无条件信任权威服务器的回答,连「这条记录是否真的由该域名的管理者签发」都无法证明。攻击者只要抢在真正的响应之前伪造一个应答,就能把用户导向任意 IP——这就是缓存投毒,而 Kaminsky 攻击曾一度能在几秒内投毒主流解析器。

本文按「威胁 → 密码学防护 → 传输加密」的顺序展开:先建立 DNS 的威胁模型,再拆解 DNSSEC 如何用签名链提供数据来源认证,然后对比 DoT 与 DoH 两种加密传输的取舍,最后落到 ECS 隐私权衡与 Unbound、dnsdist 的实际配置。

一、DNS 的威胁模型

1.1 明文 DNS 的四类风险

传统 DNS 只有 UDP 明文一条路,这决定了它同时暴露在四类攻击下。理解每类攻击利用的字段,是选择防护手段的前提。

攻击类型利用点后果
缓存投毒解析器信任首个应答,Transaction ID/端口可猜全域被劫持到恶意 IP
中间人篡改明文无完整性保护按域名定向劫持
隐私泄露查询明文可被链路监听暴露访问行为
DNS 隧道任意子域名可携带数据数据外泄、C2 通道

1.2 缓存投毒与 Kaminsky 攻击

早期解析器的查询源端口固定、Transaction ID 只有 16 位,攻击者只需猜中 65536 分之一的组合就能抢先应答。Kaminsky 攻击进一步放大了成功率:它让解析器去查询大量不存在的随机子域名,逼解析器反复向权威服务器发问,攻击者在每次发问的窗口内高频伪造应答,命中一次即可污染整个域的 NS 记录。

Kaminsky 攻击流程:
  1. 攻击者向解析器查询 a1.example.com(不存在)
  2. 解析器向 example.com 权威发问,等待应答
  3. 攻击者在该窗口内伪造海量应答,猜测 TxID + 源端口
  4. 命中则伪造 example.com 的 NS 记录(而非单条 A)
  5. 整个 example.com 的解析被劫持

1.3 基础加固:随机化与 0x20

在 DNSSEC 普及前,最有效的缓解是扩大猜测空间:源端口随机化(16 位)叠加 Transaction ID(16 位),把命中概率降到 2 的 32 次方分之一。0x20 编码进一步随机化查询名的大小写,权威必须原样回显,攻击者还要猜中大小写模式。

缓解手段叠加:
  TxID 随机化        : 16 位
  源端口随机化        : 16 位(需解析器支持)
  0x20 大小写编码     : 每个字母 1 位,如 example → eXaMpLe
  合计猜测空间          : 32 位 + 大小写位数

二、DNSSEC 的签名链与验证

2.1 DNSSEC 解决什么问题

DNSSEC(DNS Security Extensions)不加密,只做数据来源认证与完整性:它用公钥签名证明「这条记录确实由该区域的密钥签发,且未被篡改」。它防的是投毒与篡改,不防隐私泄露——隐私要靠 DoT/DoH。

2.2 新增的记录类型

DNSSEC 在原有记录之外引入四种记录,构成从根到叶的信任链。

记录全称作用
DNSKEYDNS Key区域公钥(KSK 签 DNSKEY,ZSK 签数据)
RRSIGResource Record Signature对某个 RRset 的签名
DSDelegation Signer父区域对子区域 KSK 的哈希,建立信任传递
NSEC/NSEC3Next Secure证明「某记录不存在」的否定应答

2.3 签名链与验证过程

验证是自顶向下的:根区域的 KSK 作为信任锚预置在解析器里,逐级验证下一级的 DNSKEY,直到叶记录的 RRSIG。

验证链(. → com. → example.com.):
  1. 信任锚:根区 KSK(解析器内置)
  2. 用根 KSK 验根 DNSKEY 的 RRSIG
  3. 用根 ZSK 验 com. 的 DS,得到 com. KSK 哈希
  4. 用 com. KSK 验 com. DNSKEY,再用 ZSK 验 example.com. DS
  5. 用 example.com. ZSK 验 www.example.com. 的 A 记录 RRSIG
  6. 全部通过 → 应答置 AD(Authentic Data)位
# 验证解析器是否做了 DNSSEC 验证(看 AD 标志)
dig +dnssec example.com @1.1.1.1
# flags: qr rd ra ad;  ← ad 表示已验证

# 逐级查看 DS 委派链
dig +trace +dnssec example.com

# 查看某区域的 DNSKEY(KSK/ZSK)
dig DNSKEY example.com +dnssec

# 强制不信任,观察是否 SERVFAIL
dig +cd example.com   # +cd = 关闭验证,用于对比

2.4 否定证明与 NSEC/NSEC3

「记录不存在」也需要签名证明,否则攻击者可以伪造 NXDOMAIN。NSEC 通过「本条记录 → 下一条记录」的链表证明区间内无此名,但它会泄露区域里存在哪些名字(可被枚举)。NSEC3 用哈希替代明文名,牺牲一点性能换取防枚举。

NSEC(明文枚举风险):
  a.example.com NSEC c.example.com
  → 若查询 b.example.com,落在 a 与 c 之间即证明不存在
  → 攻击者遍历整条链即可枚举全部子域

NSEC3(哈希防枚举):
  存 H(a).example.com 而非 a.example.com
  → 需额外计算,无法直接读出子域明文

2.5 部署现实与陷阱

DNSSEC 的难点不在密码学,而在运维:密钥轮换、时钟偏移、UDP 分片。签名过期或时钟不同步会导致全区域 SERVFAIL,而 DNSKEY 变大后 UDP 响应超 512 字节,需要 EDNS0 与 TCP 回退。

陷阱症状规避
RRSIG 过期全区域 SERVFAIL提前轮换,监控有效期
时钟偏移签名被判定无效NTP 严格同步
UDP 分片丢失大响应超时启用 EDNS0,支持 TCP 回退
密钥轮换失误链断裂先加新 DNSKEY 再换 DS

三、DoT 与 DoH:加密传输

3.1 DoT:专用端口的 TLS

DoT(DNS over TLS,RFC 7858)把 DNS 报文直接跑在 TLS 之上,使用专用端口 853。它对网络管理员友好——端口固定、流量特征明显、易于做策略与限速,但也正因如此,审查者可以直接封 853。

DoT 报文封装:
  [ TLS Record ]
    [ 2 字节长度前缀 ][ DNS 报文 ]
  - 长度前缀解决 TCP 流式传输的报文边界
  - 端口:853
  - 可复用 TLS 会话恢复与 ALPN(dot)

3.2 DoH:混在 HTTPS 里

DoH(DNS over HTTPS,RFC 8484)把 DNS 查询封装成 HTTP 请求,走 443 端口,与普通网页流量无法区分。它隐蔽性最强、最难拦截,但也让企业网络里的 DNS 策略失效——内网无法区分「用户在解析恶意域名」还是「在刷网页」。

DoH 请求形态:
  GET  /dns-query?dns=<base64url(报文)>   Accept: application/dns-message
  POST /dns-query                          body: 原始 DNS 报文(二进制)

响应:
  Content-Type: application/dns-message
  body: 原始 DNS 响应报文
# 用 curl 手工发一个 DoH 查询
curl -s -H 'accept: application/dns-message' \
  'https://cloudflare-dns.com/dns-query?dns=AAABAAABAAAAAAAAB2V4YW1wbGUDY29tAAABAAE' \
  --output - | xxd | head

# kdig 支持 DoT 与 DoH 两种方式
kdig -d @1.1.1.1 +tls-ca +tls-hostname=cloudflare-dns.com example.com   # DoT
kdig -d @https://cloudflare-dns.com/dns-query example.com               # DoH

3.3 DoT 与 DoH 对比

维度DoTDoH
端口853(专用)443(复用 HTTPS)
可识别性高,易做策略低,与网页流量混淆
企业管控相对容易困难,常需拦截已知 DoH 域名
实现复杂度低需 HTTP 栈
典型用途系统级解析、内网浏览器、客户端隐私
标准RFC 7858RFC 8484

四、ECS 与隐私权衡

4.1 EDNS Client Subnet 的作用

ECS(EDNS Client Subnet)让解析器把客户端的部分 IP 前缀(通常是 /24)附在查询里转发给权威,使 CDN 能返回就近节点。代价是这个前缀暴露了用户的大致位置,若递归解析器是公共的,等于把位置信息交给了权威。

ECS 工作方式:
  Stub → 递归解析器:正常查询
  递归 → 权威:附加 OPT 记录,含 CLIENT-SUBNET = 203.0.113.0/24
  权威:按该网段返回就近节点 IP
  递归:缓存时按网段隔离,避免跨网段串味

4.2 隐私与调度的取舍

策略调度精度隐私
无 ECS按递归解析器位置调度最好
全量 ECS(/24)精确到网段暴露网段
截断 ECS(/16 或 /8)城市级折中
仅权威侧支持依赖上游是否发送视上游而定
# Unbound 中控制 ECS:默认不发送,或截断为 /24
# 见 unbound.conf 的 send-client-subnet / max-client-subnet-ipv4

五、落地:解析器与网关配置

5.1 Unbound 做验证型递归解析

Unbound 是主流的验证型递归解析器:本地做 DNSSEC 验证,对外可开 DoT。关键是把信任锚配好,并确保系统时间准确。

# /etc/unbound/unbound.conf
server:
    interface: 0.0.0.0
    access-control: 10.0.0.0/8 allow
    auto-trust-anchor-file: "/var/lib/unbound/root.key"   # 根信任锚
    val-log-level: 2                                      # 记录验证失败
    harden-dnssec-stripped: yes                           # 剥离 DNSSEC 的应答降级
    qname-minimisation: yes                               # 最小化查询名,减少泄露

    tls-cert-bundle: "/etc/ssl/certs/ca-certificates.crt"
    # 向上游用 DoT(可选)
    forward-zone:
        name: "."
        forward-tls-upstream: yes
        forward-addr: 1.1.1.1@853#cloudflare-dns.com

5.2 dnsdist 做前置策略与限速

dnsdist 常部署在 Unbound 前面,负责负载均衡、限速、拒绝放大攻击与按源打标签。它可以按查询类型、响应大小做规则,也能强制某些查询走 TCP。

-- dnsdist 关键规则示例
addAction({"attacker.example."}, DropAction())
addAction(MaxQPSIPRule(50), DropAction())          -- 单 IP 限速
addAction(RespSizeRule(1232), TCOnlyAction())       -- 大响应强制 TCP
addAction(AllRule(), PoolAction("recursors"))       -- 分流到后端池

5.3 排错速查

现象可能原因排查命令
全区域 SERVFAIL签名过期/时钟偏移dig +dnssec 看 RRSIG 时间
偶发解析失败UDP 分片丢失dig +bufsize=1232、强制 TCP
AD 位始终不置位上游剥离 DNSSECdig +cd 对比,查 harden-dnssec-stripped
DoT 不通853 被阻断kdig +tls 测连通性
解析被劫持明文 DNS 被篡改切换 DoT/DoH,核对多个解析器

5.4 DNS 放大攻击与响应率限制

DNS 基于无连接 UDP,且请求远小于响应,天然适合反射放大:攻击者伪造受害者源 IP 发送小查询,权威服务器把大响应打向受害者。DNSSEC 引入的签名会让响应更大,客观上放大了这一风险,因此必须配合响应率限制(RRL)。

反射放大流程:
  1. 攻击者伪造源 IP = 受害者,向开放解析器发查询
     (如 ANY 查询、DNSSEC 签名的 TXT)
  2. 解析器把放大后的响应发往受害者
  3. 放大倍数 = 响应大小 / 请求大小(可达数十倍)

防护:
  - 关闭开放递归(只服务授权网段)
  - 启用 RRL(Response Rate Limiting)按 /24 限速
  - 拒绝 ANY 查询(RFC 8482 建议返回 HINFO)
# Unbound 开启 RRL
ratelimit:
    enable: yes
    responses-per-second: 10
    slip: 2                 # 每 N 个被限的查询回一个截断响应
    ipv4-prefix-length: 24
    ipv6-prefix-length: 64

5.5 监控与告警

DNSSEC 故障往往是「静默失败」:签名过期后解析开始 SERVFAIL,但若无监控,直到用户投诉才被发现。关键指标要盯住签名有效期、验证失败率与响应延迟。

指标含义告警阈值建议
RRSIG 剩余有效期距签名过期时间< 7 天预警
验证失败率SERVFAIL 占比> 0.1%
查询延迟 P99解析耗时突增 50%
缓存命中率本地命中比例骤降提示上游异常
放大/限速丢弃RRL 丢弃数异常升高提示被攻击
# 检查某域名签名还有多久过期
dig +dnssec example.com RRSIG 2>/dev/null | grep RRSIG

# 用 delv 做本地验证(BIND 自带,会明确报验证结果)
delv @1.1.1.1 example.com A

5.6 从明文到加密的迁移路径

加密解析不是一步到位,而是按影响面逐步推进。企业内网通常先在内网解析器上开 DoT 对上游,再把客户端配置指向内网解析器,最后按需评估是否允许浏览器直连公共 DoH。

迁移顺序(由内向外):
  1. 内网递归解析器启用 DNSSEC 验证(保证答案可信)
  2. 解析器对上游使用 DoT(保证上游链路加密)
  3. 客户端指向内网解析器(统一出口、便于审计)
  4. 按策略决定是否放行公共 DoH(权衡隐私与管控)
阶段目标关键动作
加固防投毒端口随机化 + 0x20 + RRL
可信答案真实开 DNSSEC 验证,监控有效期
加密链路保密内网 DoT,客户端指向内网解析器
管控可审计限制公共 DoH,保留日志

六、小结

主题核心要点落地建议
威胁投毒、篡改、隐私、隧道端口随机化 + 0x20 起步
DNSSEC签名链提供来源认证开验证,监控 RRSIG 有效期
否定证明NSEC 可枚举,NSEC3 防枚举优先 NSEC3
DoT/DoH专用端口 vs 复用 443内网用 DoT,客户端隐私用 DoH
ECS精度与隐私权衡按需截断网段

DNS 是几乎每个请求的「第一跳」,它的安全直接决定后续所有连接是否可信。DNSSEC 保证「拿到的答案没被改」,DoT/DoH 保证「问问题的过程没被看」,两者互补而非替代。想把这条链路放到整体威胁模型里看,可以对照 DNS 系统与智能解析 的解析流程,以及 网络安全与加密通信 中 TLS 如何为 DoT/DoH 提供传输层保护;排查时则可复用 网络故障排查实战 里的分层诊断方法。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「network」更多文章

  1. MPTCP 与多路径传输
  2. 流量整形与 TC/qdisc
  3. 网络命名空间与容器网络底层