DNS 这个名字,几乎所有做过网站、配过路由器、抓过包的人都听过,但真正能把它从头到尾讲清楚的人并不多。我在整理网安学习笔记到第 9 天的时候,特意把 DNS 服务器单独拎出来做了一整篇,原因是这个东西表面上只是"把域名翻译成 IP",实际上它是整个网络访问链路里最容易被忽视、也最容易被做手脚的一环。你在浏览器里敲下一个地址,从输入到页面出来,中间那几十毫秒里 DNS 干了什么、谁在回答你、答案从哪来、可不可信,这一整套东西决定了你后面做流量分析、日志排查、甚至应急响应时的判断质量。这篇笔记适合三类人看:刚入门网安、还在补网络基础的;做运维或者开发、被 DNS 解析问题折腾过的;以及想把实验环境搭起来、亲手摸一遍递归查询和权威应答的。我会从"这东西到底在干什么"讲起,一路讲到怎么自己搭一台、怎么验证、怎么从安全角度看它的弱点,中间所有实验都是在本机虚拟环境里跑的,参数和命令都能直接抄。
1. 先把 DNS 到底在干什么说透
1.1 一次网页打开背后,DNS 到底做了几件事
很多人对 DNS 的理解停在一句话:"域名换 IP"。这句话没错,但太薄了。你敲下www.example.com回车,浏览器先查自己的缓存,再查系统缓存,再问本机配置的 DNS 服务器,如果这台服务器不知道,它还要替你去问根、问顶级域、问权威服务器,最后把答案带回来,同时把答案缓存一段时间。整个链条上有四个不同角色在参与,任何一环出问题,你看到的现象都是"打不开网页",但根因可能完全不一样。
我踩过的第一个坑就在这里:早年遇到网站打不开,第一反应是"服务器挂了",ping 一下 IP 通的,就懵了。后来才明白,ping 通 IP 说明网络层没问题,打不开是因为域名没解析出来,问题出在 DNS 这一层,跟服务器本身毫无关系。从那以后我排障的顺序就固定下来了:先确认 IP 层通不通,再确认域名能不能解析,最后才看应用层。这个顺序能把排查范围一下子砍掉一大半。
DNS 干的事情,用生活化的说法就是"查电话簿"。但和电话簿不同的是,这本电话簿不是一本,而是几百万本,分散在全世界,每本只负责自己那一小片区域,而且每本都会被人不断修改。你要查一个号码,得先知道从哪本开始查、怎么顺着线索找到最终那本、找到之后答案能信多久。这一整套协调机制,就是 DNS 协议真正的内容。
1.2 为什么学网安绕不开 DNS
我在笔记里把 DNS 排在第 9 天,不是随便排的。从攻防视角看,DNS 有几个别的协议不具备的特点,让它天然成为一个高价值目标。
第一,它几乎无处不在。只要设备联网、要访问服务,就必然发生域名解析。很多内网环境里,员工电脑可以不出网,但一定要能解析内部域名,这就意味着 DNS 流量在绝大多数网络里都是"必须放行"的,它天然具备穿透性。
第二,它承载的是"信任入口"。用户认为www.example.com就是官网,这个判断完全建立在 DNS 答案正确的前提上。一旦解析结果被篡改,用户看到的是完全一样的页面外观,但背后连的是另一台机器,这种攻击的隐蔽性极高。
第三,它的日志价值被严重低估。一台 DNS 服务器的查询日志,本质上记录了这个网段里每台主机想访问什么。做应急响应的时候,我从 DNS 日志里定位到异常主机的次数,比从防火墙日志里找到的还多。因为防火墙只记录连接,DNS 记录的是意图——主机在解析什么域名,往往比它连了什么更能说明问题。
第四,DNS 是很多隐蔽信道的基础。这一点我在第 5 节会从检测角度展开,这里先记一句:正常的 DNS 查询有很明显的形态特征,一旦偏离这些特征,就是值得追的信号。
所以说,网安学习里跳过 DNS,等于放弃了观察整个网络行为最好的一扇窗。
1.3 三个新手最容易搞混的概念
学 DNS 的时候有几个概念特别容易混,我一开始也栽过,这里先摆清楚,后面就好理解了。
第一个是hosts 文件和 DNS 的关系。hosts 文件是本地静态映射,优先级高于 DNS 查询。我做过一个实验:在 hosts 里把某个测试域名指到一个错误 IP,然后在浏览器访问,抓包发现根本没有 DNS 查询报文发出去,因为系统在 hosts 里就拿到答案了。这个特性在做实验隔离的时候非常好用,但反过来,如果你排查解析问题,忘了 hosts 里可能有一条历史遗留记录,就会白折腾半天。我有一次就是这样,dig 查出来是对的,浏览器访问却是错的,最后发现是半年前测试时往 hosts 里加的一行没删。
第二个是DNS 缓存的多层性。浏览器缓存、操作系统缓存、本地 DNS 服务器缓存、上游递归服务器缓存,四层甚至更多。你改了域名的解析记录,发现有的机器立刻生效、有的机器要等很久,就是因为各层缓存的过期时间不一样。理解 TTL 这个概念,是理解这类"改了解析不生效"问题的唯一钥匙。
第三个是"DNS 服务器"这个词的两个含义。它既可以指你电脑上配置的那个"上游服务器",也就是替你干活的递归解析器;也可以指存放某个域名真实记录的"权威服务器"。这两个角色的职责、部署位置、安全考量完全不同,混着说就会把自己绕晕。下一节我就专门把这几类角色拆开。
2. DNS 体系里的四类角色与查询链路
2.1 根、顶级域、权威、递归:各管一段
要理解 DNS,先得知道这个体系是分层的,每一层只管自己那一小段,谁都不掌握全量数据。这个设计思路其实挺聪明——如果全世界只有一台服务器存所有域名,它第一天就会被压垮,而且一旦被改,全网沦陷。
从结构上看,从上到下是这样几层:
| 层级 | 职责 | 数量级 | 特点 |
|---|---|---|---|
| 根服务器 | 只知道各顶级域(.com、.cn 等)的权威服务器在哪 | 全球千余个实例 | 只回答"去问谁",不回答具体 IP |
| 顶级域服务器 | 知道该顶级域下各注册域名指向的权威服务器 | 每个顶级域一批 | 同样只做"指路" |
| 权威服务器 | 存放某个具体域名的真实记录 | 每个域名至少两台 | 答案的最终来源,只有它说了算 |
| 递归解析器 | 替客户端跑完整个查询链路并缓存结果 | 每个网络环境至少一台 | 客户端唯一直接打交道的角色 |
这里的关键认知是:客户端的电脑从来不会直接去问根服务器,它只问自己的递归解析器,后面所有的"跑腿"都由递归服务器完成。这个设计是为了降低整个体系的负载——如果全世界几十亿台设备都直接去问根,根早就挂了。
我做过一个很直观的实验:把本机的 DNS 配置改成一个不存在的地址,然后访问任意域名,抓包会看到客户端反复向那个地址发查询、超时、重试,但绝不会自动"退而求其次"去找根服务器。客户端就是这么一个"只认配置、不做推理"的角色。
2.2 递归查询和迭代查询,一字之差差很多
这是我当初学 DNS 时最绕的一个点,绕了很久才想通,所以单独拿出来讲。
递归查询是"你帮我查到底,给我最终答案"。客户端对递归解析器发出的就是这种请求。客户端的态度是:我不管你怎么查,最后给我一个 IP 或者一个"查不到"。
迭代查询是"你知道多少告诉我多少,我自己接着问"。递归解析器去问根服务器的时候用的就是这种。它问根"www.example.com在哪",根不会替它去问,只会回答".com的事情你去问这几个服务器"。递归解析器拿着这个线索,再去问.com的服务器,拿到"example.com去找那两台权威"的回复,再问权威,最后拿到 IP。
这个区别用快递打个比方就很清楚了。递归查询是你把收货地址给快递员,他负责一路送到;迭代查询是你去问路,每个人只告诉你"往前走到路口再问下一个人",你得自己一段一段走完。
理解这一点之后,很多现象就能解释了。比如为什么递归解析器压力这么大——它得替所有客户端跑完全程;比如为什么缓存对递归解析器这么重要——跑一次那么费劲,答案当然要留着复用;也比如为什么递归解析器是最值得做安全加固的一环——它站在"承上启下"的位置,一旦被污染,影响的是一整片客户端。
2.3 TTL:性能和安全的双刃剑
每条 DNS 记录都带一个 TTL(Time To Live),单位是秒,告诉缓存方"这个答案你可以留多久"。这个数字看着不起眼,但它直接决定了两件事:解析变更的生效速度,和缓存被污染后的影响时长。
TTL 设得长,比如 86400 秒(一天),好处是缓存命中率高、递归服务器压力小、解析速度快;坏处是万一你改了记录,全世界要等一天才陆续生效,中间这段时间不同用户看到的结果不一致。TTL 设得短,比如 60 秒,改记录几乎立刻生效,适合做灰度切换、故障转移;坏处是查询量暴增,递归服务器和权威服务器都要承受更高的压力。
我的经验是:稳定不变的主站记录可以设 3600 到 86400;做迁移、做灾备切换的域名提前一周把 TTL 降到 300 甚至 60,切换完再调回去。这个操作叫"降 TTL 预热",很多人不知道,直接在切换当天改记录,然后被用户投诉说"改了没用",其实就是旧缓存还没过期。
从安全角度看,TTL 也有另一面。缓存投毒类攻击之所以有效果,很大程度依赖 TTL 窗口——投毒成功后,在那段 TTL 时间里,所有用这台递归服务器的客户端都会拿到错误答案。所以缩短关键域名的 TTL,某种意义上也缩短了被投毒后的影响窗口。当然这只是缓解思路之一,真正的防护还是靠协议层的加固,第 5 节细说。
3. 常见记录类型,逐个拆开看
3.1 A、AAAA、CNAME:最常用的三个
A 记录是 DNS 里最基础的记录,把域名映射到一个 IPv4 地址。一条 A 记录可以有多条,形成多 IP 轮询,这就是最简单的负载均衡。我在实验里给同一个域名配了两条 A 记录,指向两台不同的测试机,然后连续 dig 十次,能看到返回顺序在轮换——当然这只是轮询,不感知后端是否存活,真要做健康检查得上更复杂的方案。
AAAA 记录对应 IPv6,格式一样,只是地址长。这里有个小坑:不少环境 IPv6 链路其实没通,但域名配了 AAAA 记录,客户端会优先尝试 IPv6,超时之后才回落到 IPv4,表现就是"网页打开慢好几秒"。我遇到过好几次这种"慢但能开"的诡异现象,最后都是用抓包看出来客户端在死磕 IPv6。
CNAME 记录是别名,把一个域名指向另一个域名,由后者继续解析。它的好处是解耦:你有一个通用名www.example.com,CNAME 到cdn.example.net,将来换服务商只要改 CNAME 的目标,业务侧完全不用动。但 CNAME 有几条硬性限制必须记住:根域名(也就是裸域,如example.com)通常不能直接配 CNAME,因为根域名必须同时存在 SOA 和 NS 记录,而 CNAME 要求该名字下不能有其他任何记录,两者冲突。这个问题在配置托管解析的时候经常撞上,绕法是给裸域配 A 记录或者用别名记录类型。
3.2 NS、SOA、MX:管理类记录
NS 记录标明这个域名由哪几台服务器负责解析,它是委派关系的体现。你在注册商那里把域名的 NS 改成某组服务器,就等于把这个域名的解析权交给了那组服务器。查 NS 记录是排查"我的解析为什么改不动"的第一步——如果 NS 还指着老服务商,你在新平台怎么配都没用。
SOA 记录是每个区域文件里必须有的第一条记录,里面包含了主服务器、管理员邮箱、序列号、刷新间隔、重试间隔、过期时间、否定缓存 TTL 等字段。最值得注意的是那个序列号:从服务器靠比较序列号判断主服务器上的数据有没有更新,序列号没变大,从服务器就不会同步。我见过的最典型的翻车场景是——手动改完区域文件,忘了把序列号加一,然后纳闷为什么从服务器上的记录一直是旧的。这个坑非常经典,几乎每个手写区域文件的人都踩过。
MX 记录管邮件路由,带优先级,数字越小优先级越高。这块跟网安关系挺大:邮件伪造检测里有好几项机制都依赖 DNS 查询结果,SPF 靠 TXT 记录、DKIM 靠 TXT 里的公钥、DMARC 也是 TXT。所以做邮件安全的排查,本质上有一半是在查 DNS 记录。
3.3 TXT、PTR、SRV、CAA:容易被忽略但很有用
TXT 记录本来是用来放任意文本的,现在被各种验证机制征用了:域名所有权验证、SPF、DKIM、DMARC 全靠它。它也常被滥用来做数据外带,因为 TXT 可以放比较长的内容,这一点在第 5 节检测部分会提。
PTR 记录是反向解析,用 IP 查域名。它的区域名是倒着写的,192.0.2.10的反向区域是10.2.0.192.in-addr.arpa。反向解析在很多场景会被校验,比如邮件服务器会检查发信 IP 有没有对应的 PTR,没有就可能被判为可疑。这也是为什么自建邮件服务特别麻烦——不光要配正向,还得找 IP 提供方配反向。
SRV 记录用来声明某类服务在哪台主机、哪个端口,带优先级和权重。内部服务发现用得多。
CAA 记录用来限制哪些证书颁发机构可以给这个域名签证书。这个记录很多人不知道,但它是防误签发的一个不错的手段——配了 CAA 之后,没在列表里的机构理论上不应该给这个域名签证书。
3.4 用 dig 把记录一条条查出来
理论讲完,得上手。dig是我最常用的工具,输出结构清晰,比nslookup好用太多。基本用法是这样:
# 查 A 记录 dig example.com A # 查指定类型的记录 dig example.com MX dig example.com TXT dig example.com NS # 指定向某台 DNS 服务器查询,绕过本机配置 dig @192.0.2.53 example.com A # 只看答案部分,去掉冗余信息 dig +short example.com A # 追踪完整解析链路,看每一跳的回答 dig +trace example.com # 查反向解析 dig -x 192.0.2.10+trace这个参数特别值得多说两句。它会从根服务器开始,把迭代查询的每一步都打出来,你 literally 能看到"问根得到.com的服务器列表、问.com得到权威服务器、问权威得到最终答案"的完整过程。我一开始对递归和迭代的理解一直很虚,直到跑了一次+trace,看到那一串输出,才真正建立起直观印象。如果你只看一个命令,我建议就是这个。
还有一个参数+norecurse,作用是告诉服务器"别替我递归,你有缓存就给我,没有就拉倒"。用它去问一台递归服务器,如果返回的答案标志位里递归可用但没给你结果,就说明这台服务器缓存里没有,需要真正跑一次查询。这个技巧在判断"答案是从缓存来的还是现查的"时很有用。
4. 动手搭一台自己的 DNS 服务器
4.1 选型:三种方案各自的适用场景
自己搭 DNS,工具上有几个常见选择,我按使用场景分一下:
| 方案 | 定位 | 配置复杂度 | 适合什么场景 |
|---|---|---|---|
| BIND | 老牌全能型,功能最全 | 较高,配置文件和区域文件都要手写 | 学习协议细节、需要完整权威服务能力 |
| dnsmasq | 轻量,配置极简 | 低,一个配置文件几十行 | 内网小规模、实验环境、本地开发 |
| CoreDNS | 插件化,配置用语法块 | 中等,配置文件是插件链 | 容器环境、服务发现、需要灵活扩展 |
我自己的学习路径是先用 dnsmasq 快速跑通"能解析"这件事,建立信心;然后换 BIND 手写区域文件,把 SOA、NS、A、CNAME 一个个敲进去,逼自己理解每个字段的含义;最后在容器环境里试了 CoreDNS,感受插件化的思路。这个顺序我觉得挺合理,从易到难,每一步都有明确收获。
下面以 BIND 为例写具体配置,因为它的区域文件格式是最"标准"的,理解了它,其他工具的配置一看就懂。实验环境我用一台隔离的 Linux 虚拟机,网段用文档保留地址192.0.2.0/24,服务器地址192.0.2.53,测试域名用lab.example.com。
4.2 装包和主配置文件
装包命令按发行版不同:
# Debian 系 apt install -y bind9 bind9utils dnsutils # RedHat 系 dnf install -y bind bind-utils装完之后主配置文件一般在/etc/bind/named.conf(Debian 系)或/etc/named.conf(RedHat 系)。这个文件里通常会用 include 引入几个子文件,包括named.conf.options(全局选项)和named.conf.local(自定义区域)。我习惯把自定义的东西全放在named.conf.local里,不碰默认文件,将来要清理也好找。
全局选项里我改的主要是这几项:
options { directory "/var/cache/bind"; listen-on port 53 { 127.0.0.1; 192.0.2.53; }; allow-query { 192.0.2.0/24; 127.0.0.1; }; recursion yes; allow-recursion { 192.0.2.0/24; 127.0.0.1; }; forwarders { 192.0.2.1; }; dnssec-validation auto; };每一行都有用意,我解释一下为什么这么写:
listen-on限定了监听地址。默认可能监听所有网卡,实验环境里最好明确写出来,避免把服务暴露到不该暴露的接口上。这是个很基础但很容易忽略的安全习惯。
allow-query限定谁能来查。这一条极其重要。默认配置如果允许任何来源查询,你的服务器就成了一个开放解析器,会被别人拿去当反射放大的跳板。我在做实验的时候故意把这条放开过一次,然后在日志里看到大量来自陌生地址的查询请求,明显不是我自己发的。这个教训很值钱:只要是对外可访问的 DNS 服务,必须做来源限制。
allow-recursion单独控制谁能用递归功能,比allow-query更精细。通常只对内网开放,对外只提供权威解析。
forwarders指定上游。如果这台机器主要做内网权威解析,转发的部分只用于它自己查外部域名,配一个可信的上游就行。
dnssec-validation auto是开启 DNSSEC 验证,后面第 5 节会讲它防的是什么。
4.3 手写区域文件,把每条记录搞明白
定义区域,在named.conf.local里加:
zone "lab.example.com" { type master; file "/etc/bind/db.lab.example.com"; };然后写区域文件。我从一个最小可用的骨架开始:
$TTL 3600 @ IN SOA ns1.lab.example.com. admin.lab.example.com. ( 2024010101 ; Serial 序列号,每次改必须加一 3600 ; Refresh 从服务器多久检查一次 900 ; Retry 检查失败后多久重试 604800 ; Expire 过期时间 300 ; Negative TTL 否定答案缓存时间 ) @ IN NS ns1.lab.example.com. @ IN NS ns2.lab.example.com. ns1 IN A 192.0.2.53 ns2 IN A 192.0.2.54 @ IN A 192.0.2.53 www IN A 192.0.2.80 mail IN A 192.0.2.25 mail IN MX 10 mail.lab.example.com. ftp IN CNAME www.lab.example.com.几个必须注意的点,都是我自己踩出来的:
@符号代表当前区域名,也就是lab.example.com,写@ IN A就等于给裸域配 A 记录。
每一行末尾的点不能漏。ns1.lab.example.com.后面的那个点表示"这是完整域名,不要再给我补后缀"。如果漏掉,服务器会把它当相对名字,自动补上区域后缀,结果变成ns1.lab.example.com.lab.example.com,查的时候一脸问号。这个坑极其常见,我第一次配的时候就在这上面耗了一个小时。
序列号必须手动递增。改完文件不加序列号,主服务器自己会重新加载,但如果有从服务器,它不会同步。养成习惯:每改一次,把序列号按日期格式加一。
SOA 里那个admin.lab.example.com.是管理员邮箱,但@在区域文件里有特殊含义,所以邮箱里的@要用点代替,实际含义是admin@lab.example.com。
改完先做语法检查,别急着重启:
named-checkconf named-checkzone lab.example.com /etc/bind/db.lab.example.comnamed-checkzone会告诉你区域文件有没有语法错误、序列号是否合理、有没有缺少必需的 NS 记录。养成"改完先 check 再 reload"的习惯,能省掉大量服务起不来的排查时间。
重载配置:
rndc reload # 或者 systemctl reload bind9最后别忘了防火墙放行 53 端口的 TCP 和 UDP。DNS 主要走 UDP,但响应超过 512 字节或者做区域传送的时候会走 TCP,两个都得开。很多人只开 UDP,结果大响应查不到,或者区域传送失败,排查起来特别费劲。
4.4 验证与客户端指向
验证分三步走,一步比一步严格。
第一步,在服务器本机查:
dig @127.0.0.1 www.lab.example.com A如果返回的ANSWER SECTION里有你配的 IP,说明服务本身正常。
第二步,从同网段的另一台机器上查:
dig @192.0.2.53 www.lab.example.com A这一步验证的是网络连通性和allow-query配置。如果本机能查、别的机器查不到,八成是allow-query没放行,或者防火墙挡住了。
第三步,把测试机的 DNS 指向这台服务器,用系统解析器验证。改/etc/resolv.conf:
nameserver 192.0.2.53然后ping www.lab.example.com,看能不能解析出来。注意resolv.conf在很多系统上会被网络管理服务覆盖,改完过一会儿又变回去了。稳妥的做法是在网络配置里改,而不是直接编辑这个文件。
还有一个验证细节:查一下反向解析。如果你配了反向区域,dig -x 192.0.2.80应该返回www.lab.example.com.。没配反向区域的话这条会失败,属于正常,但知道这个区别很重要,因为日志分析的时候经常需要靠反向解析把 IP 还原成主机名。
4.5 把日志打开,这是最有价值的部分
搭服务器最大的收获其实不在"能解析",而在日志。BIND 默认的查询日志是关的,需要单独开一个通道:
logging { channel query_log { file "/var/log/named/query.log" versions 5 size 20m; severity info; print-time yes; print-category yes; }; category queries { query_log; }; };开了之后,每次查询都会落一行,包含时间、客户端 IP、查询的域名、查询类型、结果。这份日志的价值我说三点:
一是排障。用户说"解析不了",你直接 grep 他的 IP,看他到底查了什么、服务器回了什么,一目了然,不用猜。
二是发现异常。正常的查询有很明显的分布特征:集中在少数几个常用域名、查询类型以 A 和 AAAA 为主、单机查询频率稳定。一旦出现某个客户端高频查询大量随机字符串域名、或者大量 TXT 查询,那就是值得追的信号。
三是留存证据。做安全事件复盘的时候,域名解析记录能还原出主机在某个时间点想访问什么,这个信息量比单纯的连接记录大得多。
日志要注意轮转,versions 5 size 20m就是保留 5 个历史文件、每个最大 20M 的意思。不加限制的话,日志能把磁盘吃满,这个我在早期实验环境里真的遇到过——一台测试机跑了两周,日志把根分区塞满了,服务直接写不进去。所以日志轮转配置一定要在开日志的同时就配上。
5. 从安全角度看 DNS 的弱点
5.1 缓存投毒:原理和现在的防线
缓存投毒的核心思路是:让递归服务器把一个伪造的答案存进缓存,之后所有查询这个域名的客户端都会拿到这个错误答案。
早期的脆弱点在于递归服务器发出去的查询报文里,有一个事务 ID(通常 16 位)和源端口号。攻击者如果能把这两个值猜对,并且抢在真正的权威服务器回复之前把伪造回复塞过去,就能投毒成功。16 位事务 ID 加固定源端口,可猜的空间很小,理论上可以靠大量尝试撞上。
现在的标准防线是源端口随机化。除了事务 ID 随机,源端口也从固定值改成随机分配,两者组合起来可猜空间大了几个数量级,靠暴力撞成功的概率被压到极低。这条在现代解析软件里基本默认开启,但如果你用的是很老的版本或者自己写的解析逻辑,务必确认这一点。
另一条防线是0x20 编码,也就是在查询域名里随机改变字母大小写,要求回复必须原样带回同样的大小写。这相当于又加了一层随机校验,因为攻击者看不到真实查询报文,很难猜中大小写模式。
从运维角度,我给的建议很实际:别自建对外递归服务。自建权威服务没问题,但对外提供递归解析这件事,风险收益比很差——你要处理各种放大攻击、要被扫描、要被投毒尝试,而收益只是"我自己的解析器"。内网用当然可以,但一定要做来源限制。
5.2 解析劫持:从链路层到配置层
"劫持"这个词范围更大,凡是让客户端拿到非预期答案的手段都能算。按层次分:
本地配置层:直接改客户端的 DNS 配置,或者改 hosts 文件。这类最简单粗暴,也是最容易被发现的,因为查一下配置就露馅了。
链路中间层:在客户端和服务器之间的链路上拦截并伪造响应。这类隐蔽性高一些,但因为 DNS 走的是明文 UDP,拦截本身并不困难。
上游服务层:上游递归服务器本身返回了错误结果。这类最难查,因为客户端看起来一切正常,只是答案不对。
检测这几类问题的通用思路是交叉验证:用不同的 DNS 服务器查同一个域名,比对结果。如果本机配置的服务器返回 A,换一个可信的公共解析服务返回 B,那就值得怀疑。我在实验环境里模拟过一次中间人伪造响应,用dig @服务器直接查是正常结果,但走系统默认解析就是错的,两边一对比立刻就定位到了问题所在。
还有一个细节:检查响应报文里的标志位是否合理。正常递归查询的响应,RA(递归可用)标志应该有,AA(权威回答)标志在递归服务器返回时通常没有。如果一台递归服务器给你返回的答案带AA标志,这个组合本身就挺奇怪,值得多看一眼。
5.3 隐蔽信道:从检测视角看异常特征
DNS 因为"必须放行"的特性,历史上被用来做隐蔽数据传输。我这里不讲怎么构造,只讲怎么发现,因为这才是防御方真正需要的。
异常特征主要有这么几类,我在自己的实验环境里造过流量做对比,差异相当明显:
| 特征维度 | 正常查询 | 可疑表现 |
|---|---|---|
| 域名长度 | 通常较短,结构规整 | 单标签超长、整体长度接近上限 |
| 子域名随机性 | 有一定语义,重复率高 | 大量随机字符串,几乎不重复 |
| 查询类型 | A、AAAA 占绝对多数 | TXT、NULL、CNAME 比例异常升高 |
| 域名多样性 | 集中在少数热门域名 | 单个父域下出现海量不同子域 |
| 查询频率 | 平稳,有作息规律 | 恒定高频,无波动,24 小时不停 |
| 响应大小 | 通常几十到几百字节 | 响应明显偏大且稳定 |
把这些特征落到实操上,最直接的办法就是把查询日志按父域聚合统计。比如统计每小时每个二级域下的唯一子域数量,正常业务这个数字很低,一旦某个域下出现成千上万个不同子域,基本可以定性了。我在实验里用脚本跑过这个统计,正常流量的曲线是一条几乎贴地的线,构造出来的异常流量直接冲上天,肉眼可辨。
另一个思路是统计响应字节数的分布。正常解析的响应大小集中在某个区间,如果出现一批响应大小异常一致且偏大的查询,也值得留意。
5.4 DNSSEC 和加密传输,各自解决什么问题
这两个东西经常被混为一谈,其实解决的是完全不同的问题,必须分清。
DNSSEC 解决的是"答案有没有被篡改"。它用数字签名给 DNS 记录签名,权威服务器返回记录时带上签名,递归服务器用公钥验证。验证通过说明这个答案确实来自权威服务器且未被修改。它不解决保密性问题——查询和响应内容依然是明文,中间人照样能看见你在查什么,只是改不了。
加密传输解决的是"内容别被看见"。把 DNS 查询包在加密通道里,链路上看不到查询内容,自然也改不了。但它不解决"答案可不可信"——如果对端服务器本身返回错误答案,加密通道会忠实地把错误答案送给你。
所以这俩是互补的,不是替代关系。实践中,客户端侧加密传输在个人场景用得多,服务端侧的 DNSSEC 验证在递归服务器上配置更普遍。
配置 DNSSEC 验证,BIND 里就是那行dnssec-validation auto;。开了之后,如果某个域名的签名验证失败,查询会直接返回失败而不是返回未验证的答案——这个行为很重要,叫"验证失败即拒绝",避免降级攻击。我做过实验:故意配一个签名对不上的测试区域,开启验证后查询直接报 SERVFAIL,日志里能看到具体的验证失败原因。SERVFAIL 是 DNSSEC 排查里最常见的现象,看到它先别怀疑网络,去看验证日志。
6. 排障实录:那些把我卡住的问题
6.1 工具链和排查顺序
我现在的排查顺序基本固定,效率比一开始瞎试高太多:
第一步,确认目标域名在权威侧到底配了什么。用dig @权威服务器 域名 类型,直接问权威,绕开所有缓存。这一步能确定"源头是对的"。
第二步,确认递归服务器返回什么。用dig @递归服务器 域名。如果和第一步不一致,问题在缓存或链路上。
第三步,确认客户端拿到什么。在本机用dig不带@参数,走系统配置。
这三步做完,问题定位在哪个环节就清楚了。大部分人卡住是因为跳过了第一步和第二步,直接在客户端反复试,试半天也不知道是源头错了还是缓存错了。
常用工具对照:
| 工具 | 适用场景 | 关键参数 |
|---|---|---|
| dig | 精细查询,看完整报文结构 | +trace、+short、@server、+norecurse |
| nslookup | 快速查一条,交互式调试 | 直接nslookup 域名 服务器 |
| host | 极简查询,适合脚本 | host -t MX 域名 |
| tcpdump | 抓包确认报文到底发没发出去 | port 53 -nn -vv |
| 日志分析 | 看历史查询、找异常 | grep 客户端 IP 或域名 |
抓包这一步特别值得强调。很多时候"解析失败"根本不是 DNS 的问题,而是客户端压根没发出查询——比如 hosts 里有记录、或者浏览器缓存命中、或者系统级缓存还没过期。抓一次包,看到底有没有 UDP 53 的出向报文,能立刻把范围砍一半。
6.2 常见问题速查表
下面这张表是我自己整理的,几乎覆盖了我遇到过的所有情况:
| 现象 | 可能原因 | 排查手段 |
|---|---|---|
| 本机能解析,其他机器不能 | allow-query未放行或防火墙拦截 | 从客户端dig @服务器,看是否超时 |
| 改了记录但不生效 | 多层缓存未过期 | 查 TTL,直接dig @权威对比 |
| 主从数据不一致 | 序列号未递增 | 对比两侧 SOA 记录里的序列号 |
| 服务启动失败 | 区域文件语法错误或序列号问题 | named-checkconf+named-checkzone |
| 大响应查询失败 | 只放行了 UDP,未放行 TCP | 检查防火墙 53 端口 TCP 规则 |
| 部分域名返回 SERVFAIL | DNSSEC 验证失败 | 看验证日志,确认签名链 |
| 解析慢但能成功 | IPv6 优先尝试超时回落 | 抓包看 AAAA 查询是否超时 |
| 日志磁盘占满 | 未配置轮转 | 检查 logging 配置的 versions 和 size |
| 服务器被陌生来源刷查询 | 开放解析 | 检查allow-query和allow-recursion |
6.3 几个真正让我涨经验的坑
第一个坑是**"能 ping 通但打不开网页"的误判**。前面提过,本质是把网络层和应用层混了。现在我排障第一步永远是dig,不是ping。
第二个坑是区域文件的相对域名。漏写末尾的点导致名字被自动补后缀,报错信息又不直观,第一次遇到的时候完全没想到是这个原因。后来我养成了一个笨办法:写区域文件时,所有完整域名一律写全并带点,不用任何简写,虽然啰嗦但不会错。
第三个坑是防火墙只开 UDP。DNS 的主要流量走 UDP,但响应超过 512 字节时(比如 DNSSEC 签名、大 TXT 记录)会切到 TCP。只开 UDP 的表现是"小查询正常、特定查询失败",特别迷惑。这个坑我在配 DNSSEC 的时候撞过,明明是签名配置的问题,实际是 TCP 被挡了。
第四个坑是hosts 文件的历史遗留。前面说过一次,这里再强调,因为它太隐蔽了。排查解析问题,第一件事应该是看一眼 hosts 里有没有相关条目,五秒钟的事,能省半小时。
第五个坑是测试环境用真实域名做实验。这个不是技术坑,是流程坑。早期我在实验里直接拿线上在用的域名改解析来测试,结果影响了真实访问。后来所有实验一律用example.com下的子域或者自建的lab.example.com这类隔离区域,跟线上完全分开。这个习惯我觉得比任何技术细节都重要。
6.4 后续可以继续深挖的方向
这一篇写完之后,我给自己列了几个后续要补的点,也分享出来,供参考。
一是区域传送的安全配置。allow-transfer不限制的话,任何人都能把你的整个区域文件拉走,等于把内网拓扑全暴露了。这个配置必须限制到从服务器 IP。
二是DNS 流量的行为基线。我在实验里做过简单的统计分析,但真实环境里要建基线,得积累至少两三周的正常数据,才能定义"什么是正常"。这件事没有捷径,只能靠时间堆。
三是响应策略与过滤。在递归服务器上做域名黑白名单、响应策略区域,是内网防护里比较实用的手段,配起来不难,但策略设计需要结合业务实际,做不好会误伤正常业务。
四是和流量分析的联动。DNS 日志单独看价值有限,和连接日志、进程信息关联起来才有意思。比如某个主机解析了一个陌生域名,紧接着向那个 IP 发起了连接,这条链路串起来就是一个完整的可疑行为。
我在实际做实验的过程中最深的体会是:DNS 这东西看着简单,是因为平时它不出问题;一旦出问题,排查需要的是对整个链路的完整理解,而不是某一个命令的记忆。把递归和迭代的区别想透、把缓存层次想透、把权威和递归的角色分清,这三件事想明白了,后面所有工具和配置都只是形式。我自己是跑到dig +trace看到那一串完整链路输出的时候,才觉得真的把这块摸清楚了,建议你也去跑一次,比看十篇文章都管用。