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 在原有记录之外引入四种记录,构成从根到叶的信任链。
| 记录 | 全称 | 作用 |
|---|---|---|
| DNSKEY | DNS Key | 区域公钥(KSK 签 DNSKEY,ZSK 签数据) |
| RRSIG | Resource Record Signature | 对某个 RRset 的签名 |
| DS | Delegation Signer | 父区域对子区域 KSK 的哈希,建立信任传递 |
| NSEC/NSEC3 | Next 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 对比
| 维度 | DoT | DoH |
|---|---|---|
| 端口 | 853(专用) | 443(复用 HTTPS) |
| 可识别性 | 高,易做策略 | 低,与网页流量混淆 |
| 企业管控 | 相对容易 | 困难,常需拦截已知 DoH 域名 |
| 实现复杂度 | 低 | 需 HTTP 栈 |
| 典型用途 | 系统级解析、内网 | 浏览器、客户端隐私 |
| 标准 | RFC 7858 | RFC 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 位始终不置位 | 上游剥离 DNSSEC | dig +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 提供传输层保护;排查时则可复用 网络故障排查实战 里的分层诊断方法。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。