
网安学习路线走到第9天进度上其实刚过打地基的阶段。前面几天折腾环境、摸协议、抓包看流量到了DNS这一块很多人的第一反应是这不就是个域名转IP的翻译器吗配好就不用管了。我一开始也这么想直到第一次在真实流量里看到一批长度超过200字节、子域名全是随机串的DNS查询才意识到DNS服务器在安全视野里的分量远比能解析重得多它是几乎所有网络行为的第一个动作也是极少数既能被滥用做隐蔽信道、又能被当成放大器打人的协议。这篇笔记我把DNS服务器这条线从原理到动手全部走一遍包括我在实验机上搭一套能用的解析服务、以及从日志里读出异常的过程。适合刚入门安全、想补DNS这块短板的朋友也适合做运维但没系统梳理过解析链路的人。看完你至少能做到三件事说清楚一次解析到底经过了谁、自己搭一台可控的DNS服务器、以及看懂大部分DNS相关的异常现象。1. 为什么安全路线图里要专门留一天给DNS1.1 一个被严重低估的第一跳网络安全里有个挺普遍的错觉越是底层的协议越安全因为它简单。DNS就吃了这个亏。它设计于上世纪八十年代核心目标只有两个——好记、好扩展安全完全不在考虑范围内。这就带来一个结构性问题客户端拿到什么IP几乎完全相信DNS的答复。浏览器不会去验证这个IP是不是真的属于这个域名操作系统也不会。信任链条的起点就在解析结果上谁能在这一步动手脚后面所有的加密、鉴权、访问控制全部建立在一个错误的地址上。从攻击者的成本收益看这也是最划算的位置。不用撬开任何一台主机不用找漏洞只要污染一次解析结果就能让一整片区域的用户访问到错误的目的地。从防守方角度看DNS又是唯一一个每个终端每天都会主动发起几百上千次请求的协议日志体量巨大异常藏在水面下很难被发现。这种高价值、低门槛、低可见度的组合注定它必须单独拿出来学一天而不是在讲HTTP的时候附带提一句。我在实际排查里遇到的情况更直观很多内网事件复盘到最后第一个异常信号往往不是流量告警而是某台机器开始高频查询一堆不存在的域名。这条线索如果没人看得懂整个事件的时间线就断了。1.2 我踩过的第一个认知坑把它当成配好就不动的基础设施刚接触这块时我跟着教程把/etc/resolv.conf填上地址ping通了就以为结束了。后来做实验才发现光能通这个标准会掩盖掉大量信息。举几个我当时完全没意识到的点同一个域名连续查两次返回的IP可能不一样来源是轮询或者CDN调度不是故障查询一个有CNAME的域名dig默认只显示最终结果中间的别名链被折叠了排查时极容易误判缓冲区里已经有一条记录时你改权威服务器的配置本地看到的还是旧的因为TTL没到权限被拒绝REFUSED和域名不存在NXDOMAIN在某些工具输出里看起来很相似但排查方向完全相反。这些坑的共同点是它们都不影响能不能上网所以不会有人提醒你。但一旦进入安全场景——比如你要判断某次解析结果是正常的CDN调度还是被投毒了——这些细节就是全部。1.3 学DNS到底要学到什么颗粒度我给自己的标准是三层。第一层是能描述链路从应用发起请求到拿到地址中间经过了哪些角色各自做什么决策。第二层是能动手控制自己架一台服务器改一条记录、看它生效、抓包确认。第三层是能读证据拿到一份查询日志或一段流量判断哪些是正常行为哪些是异常。只做到第一层看文章都懂上手就懵做到第二层基本能应付日常运维第三层才是安全视角真正需要的。后面的内容就按这三层展开先讲链路再讲角色和记录然后动手搭最后落到攻防和日志分析上。2. 一次解析请求的完整链路从那行命令到拿到地址2.1 请求发出之前系统已经查过三轮了很多人以为ping 某域名就是直接问DNS服务器其实在真正发出网络请求之前操作系统内部已经走完了一部分流程。以常见桌面系统为例顺序大致是应用自身的缓存。浏览器有自己的DNS缓存某些语言的标准库也会缓存解析结果。系统缓存服务。不少发行版跑着一个本地缓存守护进程监听127.0.0.1:53专门缓存最近的解析结果。hosts文件。这个优先级很靠前一条静态映射就能覆盖掉整个DNS体系。只有这三轮都没命中才会真正向resolv.conf里配置的服务器发包。这个顺序在安全上有直接意义排查为什么解析到奇怪的地址时先看hosts和本地缓存别一上来就怀疑上游被污染。我见过不止一次问题出在某次调试时手工往hosts里加的一行事后忘得干干净净。2.2 递归查询和迭代查询用问路来类比最好懂这两个词经常被混着用其实差异很清晰。递归查询是客户端对本地DNS服务器说的你帮我把这件事查到底我只等最终答案。客户端只发一次请求剩下所有跑腿的活儿都由服务器完成。这也是我们平时上网最常见的模式。迭代查询是本地DNS服务器对上游服务器说的你告诉我下一步该问谁就行。根服务器不会直接给你答案它回一句这个域名归某台服务器管你去问它然后本地服务器再去问下一级一层层往下走直到拿到答案。用问路类比递归是我要去某个地方你直接带我过去迭代是你告诉我往哪个方向走我自己走一段再问下一个人。理解了这一点就能明白为什么本地DNS服务器的负担那么重——它要替成千上万个客户端跑完全程还要把结果缓存下来复用。2.3 根、顶级域、权威三层往下走的顺序迭代过程走的顺序是固定的。本地服务器先问根根只知道各个顶级域如.com、.cn、.org的服务器地址拿到顶级域服务器地址后再问它它只知道该域下各个权威服务器的地址最后由权威服务器给出真正的记录答案。整个过程通常三到四次往返加上缓存之后大部分请求在第一跳就结束了。这里有个常被误解的点所谓根服务器只有十几台说的是逻辑地址的数量实际物理节点在全球有上千个靠任播技术让同一个地址在不同地区对应不同节点。所以打掉根服务器在物理上并不现实但这也意味着根这一层并不是攻击者的优先目标他们更愿意去打某个具体域名的权威服务器或者中间的缓存。2.4 国内环境里经常被忽略的一层转发器很多内网环境里本地服务器并不自己跑完整迭代而是配置了转发器所有查询先丢给上游上游查完了再返回。这种模式部署简单但也带来两个后果。一是你的解析结果完全依赖上游的行为上游如果做了调度或者有缓存污染你会直接继承二是排错时链条变长了你既看不到迭代过程也判断不出是哪一跳出的问题。我一般会在实验环境里同时准备两种配置一种纯递归一种走转发对比着看。纯递归模式下用trace能看到完整路径出问题时定位非常清楚转发模式下就只能靠上游的返回码和响应时间来推测。3. DNS服务器的三种角色与各自的安全含义3.1 递归解析器面向客户端的那一张脸递归解析器也叫缓存服务器是客户端直接对话的对象。它的职责是接受查询、代为完成解析、把结果缓存一段时间。绝大多数企业出口的DNS服务器都是这个角色。从安全角度看这个角色的风险集中在三点。第一是开放性如果配置不当允许任意来源查询它就变成了一个公开的解析服务被人拿去做各种滥用轻则带宽被打满重则被牵连进事件。第二是缓存内容缓存里的数据是攻击者的高价值目标投毒成功一次就能影响一批用户。第三是它记录的日志这既是最有价值的排查证据也是隐私敏感数据保存策略要提前想清楚。配置上最关键的一行永远是访问控制。下面这段是我实验环境里的写法思路就是只允许内网段查询只允许内网段递归options { listen-on port 53 { 127.0.0.1; 192.168.56.10; }; listen-on-v6 { none; }; directory /var/cache/bind; recursion yes; allow-query { 192.168.56.0/24; }; allow-recursion { 192.168.56.0/24; }; forwarders { 192.168.56.1; }; dnssec-validation auto; version not-a-clue; };最后那行version看着像玩笑其实是刻意为之。服务器默认会在版本查询时返回软件版本号等于主动告诉别人你跑的是什么、大概有什么已知问题。改掉它能减少一点信息暴露成本几乎为零属于必做的收尾动作。3.2 权威服务器数据的真正持有者权威服务器是某个域名记录的最终来源。它不缓存别人的数据只对自己管理的区域负责。我们平时改域名解析本质上就是在权威服务器上改区域文件。这个角色在攻防里的位置很微妙。它本身通常是加固良好的直接打进去不容易所以攻击者更常做的是绕开它——去改注册信息、去劫持中间链路、或者直接污染缓存让用户根本走不到权威服务器这一步。我个人的判断习惯是如果多个不同网络位置的客户端同时解析出错误结果而权威服务器自己查是正常的那问题大概率出在缓存层或者链路层不在权威。这个判断能省掉大量无效排查。3.3 缓存服务器性能和安全在这里打架缓存是DNS能撑起整个互联网的根本原因没有缓存每次访问都要跑完整迭代根和顶级域早就崩了。但缓存同时也是最脆弱的一环因为它存的是别人告诉它的答案而且要在有效期内无条件相信。缓存带来的安全矛盾很典型TTL越长性能越好但错误数据留存越久。一个被污染的记录如果TTL是86400那就意味着整整一天里所有经过这台缓存的客户端都会拿到错误地址。反过来TTL设得很短缓存命中率下降上游压力暴增。这块的实践建议是分场景核心业务域名用较短的TTL比如300到600秒保证出问题能快速切换静态资源域名可以放长到几小时甚至一天减少查询量。别为了省事所有记录统一设成一天切换的时候你会非常难受——我吃过这个亏改完记录盯着屏幕等了四十分钟才看到生效。3.4 主从同步一致性问题比想象中麻烦生产环境的权威服务器一般至少两台一主一从靠区域传送同步数据。同步的判断依据是区域文件里SOA记录的序列号从服务器发现主服务器的序列号比自己大就发起传送。问题就出在这个手工维护序列号上。改记录忘了加序列号从服务器就永远不同步表现出来就是有的人能访问有的人不能而且这种故障极难复现——因为两台服务器返回的结果不一样客户端命中哪台全看运气。我的习惯是把序列号写成日期加两位序号的形式比如2024060101改一次加一肉眼可读也方便对比。4. 资源记录类型盘点安全人视角的记录面4.1 一张表看清每种记录暴露了什么查域名不只是查IP。不同记录类型会暴露不同层面的信息做资产梳理或者威胁情报的时候把这些记录面都过一遍往往能翻出不少东西。记录类型作用安全视角的关注点A域名到IPv4地址直接暴露资产所在网段是资产测绘的起点AAAA域名到IPv6地址常被忽略可能绕过只针对IPv4的访问控制CNAME别名指向另一个域名解析链变长可能指向第三方托管服务MX邮件交换服务器暴露邮件服务提供商是钓鱼防护配置的输入NS该区域的权威服务器直接告诉别人你的解析托管在哪TXT任意文本承载各类验证信息也常被滥用做数据通道SOA区域起始授权信息暴露管理员联系方式和序列号能推断变更频率PTR反向解析IP到域名可用于推断主机用途和所处环境SRV服务定位暴露内部服务发现结构CAA规定哪些机构可为该域签发证书缺失时证书签发缺乏约束这张表里我认为最值得多看一眼的是TXT。它本身设计得很宽松可以塞任意内容于是被各种场景借用邮件发信策略、域名归属验证、甚至某些服务的心跳。好处是方便坏处是它也是隐蔽信道最常见的载体——把数据编码后放进TXT查询里从流量上看就是一堆正常的DNS报文。4.2 别名链被折叠时该怎么看dig默认输出ANSWER段里可能有两条记录一条CNAME一条A看起来是两行实际上是一条链。如果链中间断了你会看到CNAME有了但A没有这时候的判断是别名配置存在但目标域名解析不了问题出在目标那一侧而不是你查的这个域名。排查时我一般加noall answer把多余的段砍掉只看答案部分链条关系一目了然dig 192.168.56.10 www.lab.local noall answer反查也值得养成习惯。给一个IP查它指向哪个域名经常能发现一些没在资产清单里的东西尤其是那些只做了反向解析、没做正向解析的半拉子配置。dig -x 192.168.56.20 short4.3 TTL这个字段不只是性能参数TTL在安全分析里有两个用处。一是判断记录的稳定程度一个正常业务域名的TTL通常是一个规整的数字300、600、3600这类如果看到TTL是1或者极小的奇怪数字往往说明有人在频繁切换可能是做负载均衡也可能是做快速切换规避追踪。二是判断缓存状态连续查同一个域名如果第二次的TTL明显比第一次小说明命中了缓存剩下的时间是剩余有效期。还有个细节不是所有服务器都会老实返回剩余TTL有些会直接返回原始值或者归零。所以拿TTL做精确判断不可靠只能当辅助信号。5. 动手在实验机上从零搭一台能用的DNS服务5.1 选型完整实现还是轻量转发实验环境里我一般两个都装用途不同。**完整实现如BIND系列**适合需要自己管理区域、做区域传送、验证记录配置的场景。它功能全配置稍重区域文件的语法必须严格遵守写错一个点号都可能解析失败。**轻量转发型如dnsmasq**适合做内网缓存、把一批域名固定到某个地址、快速验证客户端行为配置文件短到十几行就能跑起来。选型的判断标准其实就一句你要不要自己当权威。要就上完整实现不要只是想让内网查得快一点或者做点定向映射轻量的足够。我第一次搭的时候直接上了完整实现结果被区域文件里的点号坑了两个小时——ns1.lab.local写成ns1.lab.local.少一个尾点它就被当成相对名称拼接上区域后缀变成了ns1.lab.local.lab.local。注意区域文件里所有完全限定域名末尾必须带点。这是最容易出错、报错信息又最不直观的地方。5.2 区域文件的逐字段说明下面这份区域文件是我实验环境实际在用的简化版字段含义我逐条标注$TTL 600 IN SOA ns1.lab.local. admin.lab.local. ( 2024060101 ; serial 序列号每次改动必须递增 3600 ; refresh 从服务器多久检查一次 900 ; retry 检查失败后多久重试 604800 ; expire 从服务器多久没联系上就停止服务 300 ) ; minimum 否定答案的缓存时长 IN NS ns1.lab.local. ns1 IN A 192.168.56.10 www IN A 192.168.56.20 mail IN A 192.168.56.30 IN MX 10 mail.lab.local. IN TXT vspf1 ip4:192.168.56.30 -all代表区域本身的名称IN是网络类别基本固定。SOA里那个minimum字段值得单独说一句它控制的是记录不存在这种否定答案能缓存多久。这个值设大了你新增一条记录后客户端可能长时间都还认为它不存在这是很多人改完记录不生效的真实原因。改完之后不要直接重启先做语法检查能省掉大量启动失败后的瞎猜named-checkconf /etc/bind/named.conf named-checkzone lab.local /etc/bind/db.lab.local第二条命令会明确告诉你哪一行有问题。跑通了再加载rndc reload5.3 验证三种查询方式各查什么搭完之后要验证的点有三个正向解析对不对、反向解析通不通、传递链条完不完整。# 正向指定服务器查 A 记录只看答案段 dig 192.168.56.10 www.lab.local A noall answer # 只看 SOA确认区域加载成功 dig 192.168.56.10 lab.local SOA short # 反向验证 PTR 配置 dig 192.168.56.10 -x 192.168.56.20 short # 走完整迭代路径观察每一跳 dig trace www.lab.localtrace这条命令在纯递归环境下特别有用它会把根、顶级域、权威的每一跳都打出来。如果某一步卡住了故障点立刻暴露。不过在配置了转发器的环境里trace会绕过转发直接走根看到的路径跟实际客户端走的不是一条这点要注意区分。5.4 常见返回码与它们真正的含义排查时先看返回码能省一半时间。下面这几个我遇到过太多次返回码含义常见原因NOERROR查询成功注意不代表有记录答案可能为空NXDOMAIN该域名不存在名字写错或记录确实没配SERVFAIL服务器处理失败上游不可达、区域文件语法错、DNSSEC验证失败REFUSED拒绝服务访问控制没放行来源地址FORMERR报文格式错误客户端构造异常或者中间设备改包最容易误判的是NOERROR但答案为空。这种情况下域名是存在的只是查询的那个类型没有记录。比如域名只配了A记录你查MX就会得到这个结果。很多人看到NOERROR就以为一切正常其实啥也没查到。SERVFAIL是最需要经验的一个。它是个笼统的失败可能来自任何一环。我的排查顺序固定是先确认区域文件语法named-checkzone再看上游是否可达最后检查DNSSEC验证——签名过期或者链断了同样会返回这个码。6. DNS在攻防对抗里的几个典型落点6.1 缓存投毒为什么现在还值得了解经典的缓存投毒思路是抢答。客户端发出查询后攻击者抢在真正的权威服务器之前伪造一个响应发过去如果事务ID和源端口都对得上缓存就会把这条件假记录存下来。现代实现做了两件事来对抗随机化源端口和随机化事务ID。两者的组合把盲猜成功的概率压到了很低的水平这也是那次著名的漏洞修复的核心思路。但注意这只解决了盲猜如果攻击者能嗅探到查询报文这两个随机值就直接暴露了抢答依然可行。所以判断一个环境是否安全不能只看软件版本还要看查询链路是否可被监听。识别投毒的信号不复杂同一域名在不同时间、不同网络位置解析出不同结果而权威服务器自己查是正常的就要怀疑缓存层了。这时候清缓存再查一次如果恢复正常但过一阵又变基本可以确认有问题。6.2 域名劫持的四个层次劫持这个词被用得很泛实际上发生在不同层面处理方式完全不同终端层hosts文件被改、本地代理设置被改。特征是只有这一台机器有问题。本地网络层网关或者中间设备拦截了53端口的查询返回自己构造的答案。特征是同一网段内所有设备表现一致。解析链路层上游递归服务器被污染或者被控制。特征是多地同时异常切换解析服务器就好了。注册信息层域名的权威服务器地址被改到了别处。特征是全网异常且你自己在权威上的配置没动过。排查的基本方法是分层验证先在出问题的机器上直接查权威服务器正常则排除第4层再换一个网络环境查同样的域名正常则排除第3层最后在同网段另一台机器上验证正常则锁定在终端层。这个顺序能把范围一步步收窄比漫无目的地试要高效得多。6.3 DNS隧道一个被过度神话但确实存在的通道隧道的基本原理不复杂把数据编码后放进域名的子域名部分或者放进TXT查询的内容里通过DNS报文带出去。因为DNS几乎是唯一一个在绝大多数网络里都不会被完全阻断的协议所以这条路一直有人走。但它的实际可用性远没有传说中那么高瓶颈很明显带宽极低。每条查询能携带的数据量很小要靠高频次查询堆出吞吐几百KB的文件传起来都要很久。特征明显。为了把数据塞进去子域名往往很长、字符分布随机、熵值很高正常业务的子域名很少长这样。查询量异常。一台机器短时间内几千次查询同一个父域下的随机子域名在任何基线正常的网络里都很扎眼。防守方看的是这三条的组合。单看子域名很长会误报CDN和某些云服务会用长串标识单看查询量大也会误报正常浏览器也会并发查询。三条同时命中基本可以确定有问题。我在实验里用普通查询脚本模拟过几十行代码就能跑出明显的异常模式反过来说这也说明这类行为在日志里藏不住。6.4 反射放大倍数是怎么算出来的DNS能被用作反射放大根源在于查询报文小、响应报文大。一个不带额外参数的查询可能只有六七十字节而一个配置了较多记录的响应特别是TXT类型可以轻松达到几千字节。粗算一下查询60字节响应3000字节比值就是50倍。这意味着攻击者伪造源地址发出1份流量受害者会收到50份。如果再叠加TXT记录里的大段文本、或者利用某些区域传送的响应倍数还能更高。这个链路的两个前提是服务器允许任意来源查询以及协议本身基于无连接的UDP。所以防守侧的对策非常明确限制允许查询的来源范围、对单来源的查询频率做限制、必要时对响应大小做裁剪。这些配置在上面的allow-query里已经涉及了属于五分钟能做完、但很多人不做的那类工作。7. 从日志和流量里读出异常7.1 打开查询日志的正确姿势排查离不开日志。完整实现一般支持把查询记录写进独立文件配置里加一段就行。要注意的是查询日志的体量非常大全量记录会很快把磁盘写满所以实验环境开着生产环境要配合轮转策略或者只开抽样。logging { channel query_log { file /var/log/named/query.log versions 5 size 50m; severity info; print-time yes; print-category yes; }; category queries { query_log; }; };versions 5 size 50m这段的意思是保留5个历史文件、每个到50兆就轮转。日志条目一般是这个形状01-Jun-2024 14:22:31.412 queries: info: client 0x7f2a 192.168.56.31#53142 (www.lab.local): query: www.lab.local IN A (192.168.56.10)拆开看就是时间、客户端地址和源端口、查询的域名和类型、以及服务器自己的地址。字段不多但组合起来信息量不小。7.2 三类值得立刻关注的模式拿到日志后我一般先做三个方向的观察这三个都能用简单的统计命令跑出来。第一同一来源的查询频率异常。统计每个客户端的查询次数排在前面的如果是普通办公终端就要看看它查了什么。awk {print $6} query.log | cut -d# -f1 | sort | uniq -c | sort -rn | head -20第二子域名的长度和随机性。正常业务的子域名大多有语义api、static、img这类。如果出现一长串无规律字符尤其是同一个父域下大量不同的随机串那就值得追一下。awk -F[()] {print $2} query.log | awk {print length($1), $1} | sort -rn | head -30第三查询类型分布。内网正常流量里A和AAAA占绝对多数。如果TXT或者NULL类型的占比明显偏高要么是有人在用某些服务做验证要么就是有东西在借TXT传数据。awk {print $8} query.log | sort | uniq -c | sort -rn这三条单独看都可能误报合起来看就很准。我的原则是频率异常 域名随机 类型偏门三者同时命中才升级处理命中一两条先观察。7.3 抓包看响应大小比看日志更直接日志只记了谁查了什么没记回了多大的包。而响应大小恰恰是做隧道和放大检测的关键指标。抓一小段流量看统计分布比翻日志快得多。抓包时过滤53端口然后关注两个量请求和响应的平均大小比值以及异常大的响应。正常A记录响应通常在一百字节上下如果看到大量超过500字节的响应尤其是TXT类型就需要解释一下它在传什么。还有一个容易忽略的观察点响应数量远多于请求数量。如果你的服务器收到的响应比发出的请求还多那说明有人在拿你当反射目标源地址被人伪造了。这种情况在日志里看不出来因为伪造的请求可能压根没到你这里你只是收到了响应只有抓包或者看接口流量统计才能发现。7.4 加密解析带来的观测盲区现在有些客户端开始使用加密方式发起解析请求传输层做了保护。这对隐私是好事对防守方却制造了盲区出口设备看不到查询内容只能看到连接的目标地址和流量特征。应对思路要调整。既然看不到内容就转向看行为哪些终端在直连外部解析服务而不是走内网配置的服务器、连接的频率和时长是否规律、流量体积是否和这台机器的正常业务匹配。这种基于行为的判断不如内容分析精确但方向是对的。我在实验里对比过两种模式下的可见性差异结论很明确加密解析普及之后终端侧的配置合规性检查比流量内容分析更重要。把终端管住让它走指定的解析路径比在出口拼命解密要现实得多。8. 收个尾几个我反复用到的判断习惯学到这里我觉得DNS这块真正有价值的不是记住多少种记录类型而是养成几个判断习惯。第一个习惯是先分层再怀疑。终端层、网络层、链路层、注册层从上往下逐层验证每一层都有明确的验证方法别一上来就猜是不是被攻击了。大多数所谓的解析异常最后查出来是缓存没到期或者hosts里遗留的一行。第二个习惯是改动必留痕。序列号、TTL、变更时间这三样记下来出问题时能立刻对上时间线。我现在的做法是每次改区域文件都顺手在注释里写一行日期和原因事后回看非常省事。第三个习惯是把DNS日志当资产来管。它体量大、噪音多但它是唯一记录每台机器想访问什么的地方。不用全量分析抓住频率、长度、类型这三个维度做基线异常自然浮出来。最后一个很实际的提醒实验环境里怎么折腾都行生产环境的解析服务改动一定要挑低峰期并且准备好回滚。TTL没到期的这段时间里你的所有改动都是部分生效状态这个窗口期最容易出乱子。我在这个上面栽过一次改完记录以为生效了实际上一半客户端还在用旧地址排查了半天才想起来是缓存的问题。