你的域名,可能正在被别人"无声地"劫持
先说一个让很多人不舒服的事实:DNS 协议设计于 1983 年,它从来没有考虑过安全问题。当你的用户在浏览器里敲下你的域名,操作系统向递归 DNS 服务器发起查询,递归服务器再去问根、问顶级域、问你的权威 DNS,最后把 IP 返回。整个链条上,没有任何一步在验证"这个回答到底是不是真的来自域名主人"。
这意味着什么?意味着任何一个能在这条链路上插一脚的人,都可以伪造一个回答,告诉用户"zz1984.com 的 IP 是 1.2.3.4",而用户的浏览器会毫不犹豫地连过去——因为 DNS 的回答没有签名,无法分辨真伪。这个攻击叫 DNS 缓存投毒(Cache Poisoning),历史上著名的 Kaminsky 攻击就是靠它,在几秒钟内就能污染一台递归服务器上任意域名的缓存。
劫持的后果不只是"网站打不开"。对站长来说,最可怕的是:你的域名可以指向一个长得和你一模一样的钓鱼站,而用户看不出来;或者你的邮箱 MX 记录被改,所有发往你域名的邮件都进了攻击者的服务器——包括那些"点击重置密码"的邮件。你的 SSL 证书对此毫无帮助,因为攻击者会用他自己的证书去申请一个合法签发(他有域名控制权,DV 证书几分钟就能拿到)。
DNSSEC(DNS Security Extensions)就是为了解决这个问题而生的。它给 DNS 记录加上密码学签名,让递归服务器可以验证"这个回答确实由域名的权威服务器签发,且内容没有被篡改"。验证失败的响应会被直接丢弃,攻击者哪怕污染了缓存也没用——因为伪造的回答签不出来。
DNSSEC 的工作原理:一条信任链,从根一直签到你
DNSSEC 最核心的设计思想是信任链(Chain of Trust)。它不依赖任何一个中心机构的"我说了算",而是靠层层签名,让验证者可以从根一直验证到你的记录。
链条是这样的:
根区(.)
│ 用根区的 KSK 签名,公钥是公开的、被所有解析器内置(信任锚)
│ 在根区里放置了 .com 的 DS 记录(.com 的公钥哈希)
▼
.com 顶级域
│ 用 .com 的 ZSK 签名
│ 在 .com 里放置了 zz1984.com 的 DS 记录(你的公钥哈希)
▼
zz1984.com 权威区(你的域名)
│ 用你的 ZSK 签名所有记录
└─ 在区里放置 DNSKEY 记录(你的公钥)
验证的流程是自下而上发起、自上而下验证:递归服务器拿到你的 A 记录回答时,会要求你同时提供 RRSIG(签名);它用你区里的 DNSKEY(公钥)验签;然后它要确认这个 DNSKEY 是真的——于是去问 .com 要你的 DS 记录,用自己的公钥验 DS 的签名;再确认 .com 的公钥是真的——去问根区要 .com 的 DS……一直验到根,而根的 KSK 公钥是解析器软件里内置的(信任锚)。
链条上任何一环断掉,验证就失败。这也是 DNSSEC 最常见的两个故障原因:DS 记录和 DNSKEY 不匹配(漏配或换了密钥忘了更新 DS),以及签名过期(忘了续期)。
顺便解释四个会反复遇到的缩写:
- KSK(Key Signing Key):密钥签名密钥。只用来签名 DNSKEY 记录,也就是给其他公钥"背书"。它变动很少,是信任链的锚点,所以它的公钥哈希会被登记为 DS 记录放到上级域。
- ZSK(Zone Signing Key):区域签名密钥。用来签名区里所有普通记录(A、AAAA、MX、CNAME、TXT……)。它变动相对频繁,因为签名有效期短。
- RRSIG:签名记录类型。每一条被签名的记录旁边都有一条对应的 RRSIG,里面含签名值和有效期。
- DS(Delegation Signer):委派签名者记录。它存在于上级域,内容是下级域 KSK 的哈希。这是把上下两级串起来的唯一纽带,也是 DNSSEC 配置里最容易出错的地方。
启用前的准备:先摸清你的 DNS 在哪
DNSSEC 的启用方式完全取决于你的 DNS 托管在哪里,因为签名这件事本质上是权威 DNS 服务器干的活。分三种情况:
情况一:域名商自带的 DNS(最常见)
Cloudflare、阿里云 DNS、DNSPod、华为云 DNS、Namecheap 等都已经支持一键开启 DNSSEC。你只需要在控制台点一下"启用 DNSSEC",它会生成 DNSKEY 和 DS 记录,你把 DS 记录填到域名注册商(注意:是注册商,不是 DNS 服务商)那边,就完成了。这是最省事的路径,强烈推荐。
情况二:自建权威 DNS(BIND / PowerDNS / Knot)
如果你像我一样把 DNS 抓在自己手里,那就得自己管密钥。签名的活儿由 dnssec-keygen 生成密钥、dnssec-signzone 签名、BIND 加载签名后的区文件。这套流程复杂且极易出错(密钥轮换、签名有效期、区文件重签的自动化),下面会专门讲。
情况三:CDN 或第三方提供 DNS
部分 CDN 商不开放 DNSSEC。这时候要么接受没有 DNSSEC,要么把 DNS 迁到支持的服务商(很多免费服务商支持),二者可以并存——DNS 服务和 CDN 服务本来就是分离的,你可以用 A 或 CNAME 指向 CDN。
在动手之前,先确认当前状态:
# 查看你域名当前是否有 DNSKEY 和 DS
dig +dnssec DNSKEY zz1984.com @1.1.1.1
dig DS zz1984.com @1.1.1.1
# 用 Google/Cloudflare 的判断工具(最直观)
# https://dnssec-debugger.verisignlabs.com/ 输入你的域名
# https://dnsviz.net/ 会画出完整的信任链图
# 命令行验证:如果 +dnssec 查询返回结果里带 "ad" flag,说明验证通过
dig +dnssec www.zz1984.com @1.1.1.1 | grep -E "flags:"最后一条命令里的 ad 标志位(Authenticated Data)是关键:它表示解析器已经完成了 DNSSEC 验证且通过。如果查得到结果但没有 ad,说明这个域名没有启用 DNSSEC(或者解析器没开启验证)。
实战:给自建 BIND 启用 DNSSEC(完整流程)
这套流程我配过好几次,坑非常多,逐步讲清楚。假设你的域名是 example.com,BIND 的区文件目录是 /var/named/。
第一步:生成 KSK 和 ZSK
cd /var/named
# 生成 KSK(只签 DNSKEY,用 RSA 2048 或 ECDSA P-256)
dnssec-keygen -a ECDSAP256SHA256 -f KSK -n ZONE example.com
# 输出:Kexample.com.+013+12345
# 生成 ZSK(签所有记录)
dnssec-keygen -a ECDSAP256SHA256 -n ZONE example.com
# 输出:Kexample.com.+013+67890
# 查看生成的文件(每个密钥有 .key 公钥 和 .private 私钥两个文件)
ls -l Kexample.com.*关于算法选择:ECDSAP256SHA256(算法 13)是当前的最优解——签名短(对 DNS 报文大小友好)、验证快、性能好。RSA 2048(算法 8)兼容性天花板最高但报文大,容易触发 TCP 回退。Ed25519(算法 15)很优雅但部分老解析器不支持。中小站点选 ECDSAP256SHA256 即可。
第二步:把公钥写进区文件
# 把两个 .key 文件里的公钥追加到区文件末尾
cat Kexample.com.+013+12345.key >> example.com.zone
cat Kexample.com.+013+67890.key >> example.com.zone
# 重要:把区的 SOA 行加上这些配置(在 $ORIGIN 附近)
# 对于 BIND 9.16+,可以用 dnssec-policy 自动管理(推荐)
# 见下面的 named.conf 配置第三步:配置 named.conf 启用自动签名
BIND 9.16 之后引入了 dnssec-policy,可以自动处理签名、重签和密钥轮换——这是最省心的方式,强烈建议用这个而不是手动 dnssec-signzone。
// /etc/named.conf
dnssec-policy default {
keys {
ksk lifetime unlimited algorithm ecdsap256sha256;
zsk lifetime 90d algorithm ecdsap256sha256;
};
// 签名有效期相关
signatures-refresh 5d;
signatures-validity 14d;
signatures-validity-dnskey 14d;
};
zone "example.com" {
type master;
file "/var/named/example.com.zone";
// 关键:让 BIND 自动签名并维护密钥
dnssec-policy default;
inline-signing yes;
// 自动重签,不需要手动跑 dnssec-signzone
auto-dnssec maintain;
};改完配置,named-checkconf 检查语法,然后 rndc reload。BIND 会在后台完成签名,并把签名后的内容维护在 .signed 文件里。日志会显示 zone example.com/IN: signing with policy default。
第四步:验证签名是否生效
# 在权威服务器上直接查(绕过递归,看原始回答)
dig @localhost DNSKEY example.com +dnssec +short
# 应该看到 DNSKEY 记录和对应的 RRSIG 记录,形如:
# 257 3 13 xxxxxx... (KSK,flags 是 257)
# 256 3 13 yyyyyy... (ZSK,flags 是 256)
# DNSKEY 13 2 604800 20261001000000 ... (RRSIG)
# 查一条普通记录,看是否带 RRSIG
dig @localhost www.example.com A +dnssec +multiline注意 DNSKEY 的 flags:257 = KSK,256 = ZSK。如果只有 256 没有 257,说明 KSK 没配进去,DS 就没法建。
第五步:在注册商处添加 DS 记录(最关键的一步)
这一步是把你的域名接入全球信任链的唯一动作。在你注册域名的那个地方(不是 DNS 托管商,是注册商,比如 GoDaddy、Namecheap、阿里云域名、腾讯云域名),找到"DNSSEC"或"DS 记录"设置,添加一条:
# 生成 DS 记录(在权威服务器上执行)
dnssec-dsfromkey -2 Kexample.com.+013+12345.key
# 输出格式:example.com. IN DS 12345 13 2 <64位十六进制哈希>
# 拆解含义:
# 12345 = Key Tag(KSK 的标识)
# 13 = 算法(ECDSAP256SHA256)
# 2 = 摘要类型(SHA-256,必须是 2,1 是 SHA-1 已废弃)
# <hash> = KSK 公钥的 SHA-256 摘要在注册商控制台填入这四个值。有的平台让你填一行完整记录,有的让你分字段填,都行。注意 Key Tag 和哈希一定要从上面这条命令的输出里原样复制,手抄极容易错位。
填完之后,DS 记录要通过上级域(.com)发布出去,这通常需要几分钟到几小时(取决于 TLD 的刷新周期)。在这个窗口期内,你的域名处于最危险的中间状态:DS 已经发布但验证者如果缓存了旧数据会不一致。
第六步:全链路验证
# 1. 确认 DS 已经发布到 .com
dig DS example.com @8.8.8.8 +short
# 应该返回你刚才填的 DS 记录
# 2. 确认递归解析器验证通过(关键:看 ad 标志)
dig +dnssec www.example.com @1.1.1.1 | grep -A1 "flags:"
# 期望看到: flags: qr rd ra ad;
# 3. 用 dnsviz 看完整信任链(会画出每条记录的验证状态)
# https://dnsviz.net/d/example.com/dnssec/看到 ad 标志才算成功。如果 dig 返回 SERVFAIL 或者没有 ad,说明链条某处断了。
故障排查:DNSSEC 配错会让整站"全球不可解析"
这是 DNSSEC 最需要警惕的地方,也是它比 HTTPS 危险得多的原因:HTTPS 配错了,只是浏览器报警;DNSSEC 配错了,是所有启用验证的解析器直接返回 SERVFAIL——你的域名在全球范围内"消失"。用户连"证书错误"都看不到,就是打不开。
下面是按出现频率排序的坑位清单:
坑一:DS 与 DNSKEY 不匹配(最高频)
症状:dig +dnssec 返回 SERVFAIL,dnsviz 上你的域名显示红色 DS 不匹配。
原因通常是:换了密钥之后忘了同步更新注册商那边的 DS 记录。权威服务器上已经是新 KSK,但上级域还挂着旧 KSK 的哈希,验证者一比对就失败。
# 对比当前 DNSKEY 生成的 DS 和实际发布的 DS
dnssec-dsfromkey -2 Kexample.com.+013+<新的tag>.key
dig DS example.com @8.8.8.8 +short
# 两者必须完全一致解决:立刻到注册商更新 DS。如果密钥轮换是计划内的,正确做法是先发布新 DS、等 TTL 过期、再撤旧 KSK(双签名过渡期),绝对不能"先撤旧再上新"。顺序错了就是一段时间的全球不可解析。
坑二:签名过期
症状:原本正常,某天突然 SERVFAIL。dig 看 RRSIG 的 expiration 字段已经过了当前时间。
如果用的是手动 dnssec-signzone,这就是必然会发生的事——签名有效期默认 30 天,你忘了重签,30 天后域名就趴了。所以别用手动签名,用 dnssec-policy + inline-signing 让 BIND 自动重签,或者用 PowerDNS 的 pdnsutil secure-zone(它也是自动的)。
# 查看当前签名的有效期
dig example.com SOA +dnssec | grep -A1 RRSIG
# RRSIG 的第二个字段是 expiration(YYYYMMDDHHMMSS 格式)坑三:报文过大导致 TCP 回退失败
症状:部分用户能访问、部分不能,特别是通过某些中间设备(老的防火墙、运营商设备)的用户。
原因:DNSSEC 的签名让响应报文显著变大。RSA 2048 签名约 256 字节,加上 DNSKEY 经常让响应超过 1232 字节,超过 UDP 的安全上限就会触发 EDNS0 分片或 TCP 回退,而很多网络路径会丢弃分片或阻断 53 端口的 TCP。
# 检查响应大小
dig example.com DNSKEY +dnssec | grep "MSG SIZE"
# 如果超过 1232,就该优化
# 优化手段一:换更短的算法(ECDSA P-256 的签名只有 ~64 字节)
# 优化手段二:减小 TTL、精简 DNSKEY(只保留一个 KSK 一个 ZSK)
# 优化手段三:确保权威服务器响应设置了正确的 EDNS0 UDP size这是选 ECDSAP256SHA256 而不是 RSA 的核心理由——不只是性能,更是为了让报文留在 UDP 范围内,避免回退到 TCP 带来的各种中间设备问题。
坑四:CDN 和 DNSSEC 的一条与多条 CNAME 冲突
症状:主域名能用,但某个走 CDN 的子域(比如 img.example.com)验证不通过。
核心规则:DNSSEC 下,CNAME 只能有唯一一条记录,不能有多条 CNAME 做负载均衡。而且 CDN 的 CNAME 指向的域名本身也必须支持 DNSSEC(否则签名链断在 CNAME 那里)。Cloudflare 的 CNAME flattening 会帮你把 CDN 的 CNAME 解析成 A/AAAA 并一起签名,是可以的;但如果是普通的多 CNAME,就会出问题。
坑五:只签了主域,忘了所有子域
DNSSEC 的签名是逐区(zone)的。如果你的 DNS 里同时托管了 example.com 和 blog.example.com(后者作为独立 zone 存在,带自己的 SOA 和 NS),那 DNSSEC 需要分别对两个 zone 配置。只配了主域,子域就没保护(但也不会 SERVFAIL,因为验证者发现没有 DS 就退回"不验证"模式)。
DNSSEC 能防什么,不能防什么
理解边界比理解原理更重要,否则会对它产生错误的依赖。
能防:
- 缓存投毒:伪造的回答签不出来,验证者直接丢弃。这是它的核心价值。
- 链路中间人篡改:运营商 DNS 劫持、公共 Wi-Fi 的 DNS 篡改、透明代理插播广告,全部无效。
- DNS 记录被未授权修改后不被察觉:如果攻击者拿到了你 DNS 管理后台的权限改了 A 记录,他同时也有权限改签名(因为签名的密钥在同一个后台)——所以这种情况 DNSSEC 防不住。但如果你把密钥存在本地(自建 BIND),控制台被黑也改不了签名,那么在攻击者拿到 DNS 控制台权限后,签名会立即失效并在
dnsviz上报警——这反而成了一个强力的入侵检测信号。
不能防:
- 域名注册商账户被攻破。攻击者可以改 NS 记录把整个域名指向他自己的服务器,也可以同时改 DS。DNSSEC 在这里帮不了你,唯一解法是给注册商账户开双因素认证、开启注册局锁(Registrar Lock)。
- 你的权威 DNS 服务器被攻破。密钥在服务器上,攻击者拿到就能重新签名一切。这时 DNSSEC 反而"帮着"攻击者把伪造内容签得合法。
- 隐私。DNSSEC 不加密任何东西,只是签名验证。DNS 查询内容依然明文可见(要隐私得靠 DoH/DoT)。
- 可用性。它只保证"你收到的是真的",不保证"你收得到"。DNSSEC 甚至会降低可用性——因为它把原本"出错也能凑合用"的情况变成了硬失败。
对 SEO 到底有没有影响
直接回答:DNSSEC 不是排名因素。Google、百度都不会因为你的域名开了 DNSSEC 而给你加权。
但它通过可用性间接影响你的流量,而且路径很明确:
- 防劫持 = 防流量被偷。没有 DNSSEC 的域名一旦被投毒劫持到钓鱼站,用户在搜索结果的点击会落在别人的站点上,你的排名和流量会凭空消失,而且你查自己的站都查不出问题。
- 爬虫可达性。搜索引擎的爬虫用的是自己的递归解析器,如果它遇到了投毒或者解析不一致,抓取会失败,直接表现为"已发现未编入索引"或抓取异常。
- 邮件送达率。这个跟 SEO 没直接关系,但跟站长关系极大——你域名的 MX 被劫持,注册/找回密码/收录通知的邮件就全进了别人邮箱。DNSSEC 加 DMARC 的组合,能把邮件投递的信任度提上去。
所以正确的态度是:别为了 SEO 去开 DNSSEC,要为了"域名主权"去开它。它的收益不在于多几个排名,而在于没人能假装是你。
一份可以直接执行的启用清单
- 先评估:
dig +dnssec 你的域名 @1.1.1.1,看有没有ad。有就说明已经开了,没有就继续。 - 选路线:能一键开就用托管商(Cloudflare / 阿里云 / DNSPod),自建 BIND 就走
dnssec-policy自动签名。绝对不要用手动dnssec-signzone——签名过期无人重签是必然故障。 - 算法选 ECDSAP256SHA256(算法 13),报文小、性能好、兼容性够。
- 先在权威服务器上生成并验证 DNSKEY 和 RRSIG,确认
dig @localhost +dnssec能看到签名(257 和 256 都在),再动 DS。 - 挑低峰时段发布 DS(比如凌晨),因为 DS 发布后的几分钟到几小时是风险窗口。
- 发布后立刻三重验证:
dig DS(上级域有记录)、dig +dnssec(有ad标志)、dnsviz.net(链条无红点)。 - 记录密钥指纹和 DS 内容到密码管理器。密钥轮换时你需要它们,找不到就得从头来一遍。
- 设置监控:用一个脚本每天检查
dig +dnssec是否仍有ad标志,以及 RRSIG 的有效期剩余天数,低于 7 天就告警。这是唯一能救你的自动化。 - 密钥轮换要留过渡期:先上新的,等双份 TTL 过期,再撤旧的。永远不要"先撤后上"。
- 同时把注册商账户的双因素和 Registrar Lock 打开——DNSSEC 防不了注册商被黑,这两件事才是防那个的。
最后说一个我自己心态上的转变。刚做站的时候我觉得 DNSSEC 是"大公司才需要的东西",我的小站没人会花力气去劫持。后来想明白了:DNS 劫持是自动化的、批量的,攻击者不针对谁,他只是跑一个脚本扫全网,把所有能投毒的域名都指向自己的钓鱼集群。你的站小,恰恰意味着你可能连被劫持了都不会发现。开 DNSSEC 花不了多少时间,而且现在大部分平台都是一键——这是投入产出比最高的一项域名保护,没有之一。