news 2026/9/23 20:46:39

3个实操案例一文搞懂dns被篡改原理与排查

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
3个实操案例一文搞懂dns被篡改原理与排查

3个实操案例一文搞懂dns被篡改原理与排查

官方文档里关于 DNS 解析的章节动辄几十页,参数配置、协议交互、缓存机制堆砌在一起,新手根本抓不住重点,更别提遇到线上域名突然解析到错误 IP 时如何快速定位是 DNS 被篡改还是本地缓存问题。很多开发者在掘金技术社区提问“为什么我的域名突然打不开了”,回复往往只给出一句“检查 DNS”,却没人讲清楚底层到底发生了什么。今天这篇文章不抄文档,直接拆解 DNS 解析链路中的关键源码片段,带你从代码层面看清 DNS 被篡改的常见手法,一文搞懂如何从源头拦截和排查。

入口定位:从系统调用到内核解析器

要搞懂 DNS 被篡改,先得知道程序是怎么发起 DNS 查询的。大多数语言库(如 Python 的 socket 模块、Java 的 InetAddress)最终都会调用操作系统的 getaddrinfo() 函数。这个函数是用户态与内核态 DNS 解析的边界,也是篡改者最爱动手脚的地方。

在 Linux 系统中,getaddrinfo() 的行为由 /etc/nsswitch.conf 文件控制。默认情况下,系统会先查本地 hosts 文件,再查 DNS 服务器。如果攻击者修改了 hosts 文件,或者劫持了 DNS 服务器返回的应答包,解析结果就会出错。更隐蔽的手段是修改系统库函数,比如通过 LD_PRELOAD 注入恶意 .so 文件,直接拦截 getaddrinfo() 的调用,返回伪造的 IP 地址。

核心片段:Go 标准库的 DNS 解析逻辑

Go 语言的 net 包对 DNS 解析做了深度封装,其源码中有一段关键逻辑,揭示了如何绕过系统 resolver 直接进行 UDP 查询。这段代码位于 net/lookup_unix.go,展示了 Go 如何构造 DNS 报文并发送。

// 文件: net/lookup_unix.go (简化版核心逻辑)
func (r *Resolver) lookupIPAddr(ctx context.Context, net string, host string) (addrs []ipAddr, err error) {// 1. 构造 DNS 查询报文,设置查询类型为 A 记录msg, err := dns.NewMsgWithQuestion(host, dns.TypeA, dns.ClassINET)if err != nil {return nil, err}// 2. 获取配置的 DNS 服务器地址(来自 /etc/resolv.conf)servers := r.servers()// 3. 遍历 DNS 服务器,发送 UDP 请求for _, server := range servers {conn, err := net.DialTimeout("udp", server, r.timeout)if err != nil {continue}// 4. 序列化报文并发送if err := conn.SetDeadline(time.Now().Add(r.timeout)); err != nil {conn.Close()continue}_, err = conn.Write(msg.Pack())if err != nil {conn.Close()continue}// 5. 接收响应并解析 IP 地址buf := make([]byte, 1500)n, err := conn.Read(buf)conn.Close()if err != nil {continue}// 6. 解析 DNS 响应,提取 A 记录中的 IPresp := new(dns.Msg)resp.Unpack(buf[:n])for _, ans := range resp.Answer {if a, ok := ans.(*dns.A); ok {addrs = append(addrs, ipAddr{ip: a.A})}}if len(addrs) > 0 {return addrs, nil}}return nil, &DNSError{Err: "no answer", Name: host}
}

逐行注释如下:

  • 第1行:构造标准 DNS 查询报文,dns.TypeA 表示查询 IPv4 地址。
  • 第4行:从系统配置读取 DNS 服务器列表,这是被篡改的高危点。
  • 第7行:使用 UDP 协议连接 DNS 服务器,设置超时防止阻塞。
  • 第12行:将报文序列化为字节流并发送,UDP 无连接特性使其易被中间人篡改。
  • 第16行:接收响应数据,缓冲区大小设为 1500 字节,符合 DNS 报文最大长度。
  • 第21行:解析响应报文,遍历 Answer 段提取 A 记录中的 IP 地址。
  • 第25行:如果任一服务器返回有效 IP,立即返回,体现 DNS 查询的容错机制。

这段代码的关键在于,Go 绕过了系统 getaddrinfo(),直接进行 DNS 查询。这意味着即使 hosts 文件被篡改,Go 程序可能不受影响,但 DNS 服务器返回的应答包仍可被中间人修改。

设计思想:为什么 DNS 解析如此脆弱

DNS 协议在设计之初未考虑安全性,这导致其极易被篡改。RFC 1035 规范中明确指出,DNS 应答不携带任何认证信息,客户端无法验证应答来源是否合法。攻击者只需在网络上监听 DNS 查询包,就能伪造应答,将域名指向恶意 IP。

现代系统引入了 DNSSEC(DNS Security Extensions)机制,通过数字签名验证应答的真实性。但 DNSSEC 部署率极低,尤其在企业内网和移动网络中,大多数 DNS 查询仍是“裸奔”状态。此外,DNS over HTTPS(DoH)和 DNS over TLS(DoT)协议通过加密通道传输查询,可有效防止中间人篡改,但需要客户端和服务器双方支持。

从源码角度看,DNS 解析的脆弱性源于三个层面:一是协议本身缺乏认证,二是传输层未加密,三是客户端缓存机制可能被利用。攻击者不仅可篡改网络传输中的应答,还可污染本地 DNS 缓存,使后续查询直接返回恶意 IP。

手写简化版:模拟 DNS 应答篡改过程

为了更直观理解 DNS 被篡改的原理,我们用 Python 手写一个简化版 DNS 应答伪造器。该脚本监听指定网段的 DNS 查询包,当查询特定域名时,返回预设的恶意 IP。

import socket
import struct
import timedef parse_dns_query(data):# 解析 DNS 查询报文头部trans_id = struct.unpack("!H", data[:2])[0]flags = struct.unpack("!H", data[2:4])[0]qdcount = struct.unpack("!H", data[4:6])[0]ancount = struct.unpack("!H", data[6:8])[0]nscount = struct.unpack("!H", data[8:10])[0]arcount = struct.unpack("!H", data[10:12])[0]# 解析查询名称offset = 12labels = []while data[offset] != 0:length = data[offset]labels.append(data[offset+1:offset+1+length].decode())offset += length + 1offset += 1  # 跳过末尾的 0qtype = struct.unpack("!H", data[offset:offset+2])[0]qclass = struct.unpack("!H", data[offset+2:offset+4])[0]return trans_id, ".".join(labels), qtype, qclassdef build_dns_response(trans_id, domain, ip):# 构造 DNS 应答报文header = struct.pack("!HHHHHH", trans_id, 0x8180, 1, 1, 0, 0)# 构造名称部分(直接复制查询名称)name = b""for label in domain.split("."):name += struct.pack("!B", len(label)) + label.encode()name += b"\x00"# 构造资源记录rdata = socket.inet_aton(ip)rr = name + struct.pack("!HHHH", 1, 1, 300, len(rdata)) + rdatareturn header + name + struct.pack("!HHHH", 1, 1, 300, 4) + rdata# 主程序:监听 UDP 53 端口
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(("0.0.0.0", 53))
print("监听 DNS 查询...")while True:data, addr = sock.recvfrom(1500)trans_id, domain, qtype, qclass = parse_dns_query(data)# 如果查询目标域名是 evil.com,返回恶意 IPif domain == "evil.com":response = build_dns_response(trans_id, domain, "1.2.3.4")sock.sendto(response, addr)print(f"篡改查询: {domain} -> 1.2.3.4")else:# 正常查询转发到真实 DNS 服务器(此处省略)pass

逐行注释如下:

  • 第1行:导入 socket 模块用于网络通信,struct 模块用于二进制数据打包解包。
  • 第14行:解析查询名称,DNS 域名采用标签长度前缀编码,循环读取直到遇到 0 字节。
  • 第17行:提取查询类型和类别,qtype=1 表示 A 记录查询。
  • 第21行:构造应答头部,0x8180 表示应答标志位(QR=1, AA=0, TC=0, RD=1, RA=1)。
  • 第25行:构造名称部分,将点分域名转换为 DNS 二进制格式。
  • 第29行:构造资源记录,TTL 设为 300 秒,RDATA 为恶意 IP 的二进制表示。
  • 第35行:绑定 UDP 53 端口,这是 DNS 服务的标准端口。
  • 第41行:判断查询域名,如果是目标域名则返回伪造应答,否则正常处理。

这个简化版脚本展示了 DNS 应答篡改的核心原理:监听查询、解析域名、构造伪造应答。在实际攻击中,攻击者通常不会硬编码域名,而是维护一个恶意域名列表,动态匹配查询。

应用场景:企业内网 DNS 安全加固

在企业内网环境中,DNS 被篡改的风险尤为突出。攻击者一旦入侵内网,可通过 ARP 欺骗、路由器配置篡改或内部服务器入侵等方式劫持 DNS 流量。根据掘金技术社区多位安全工程师的实战经验,内网 DNS 篡改常见于供应链攻击和内部威胁场景。

应对策略包括:一是部署 DNSSEC,对关键域名启用签名验证;二是采用 DNS over TLS 或 DNS over HTTPS,加密查询和应答;三是实施 DNS 流量监控,异常应答立即告警;四是使用可信的公共 DNS 服务(如 1.1.1.1、8.8.8.8),避免依赖单一内网 DNS 服务器。

对于开发人员而言,在应用层可增加 DNS 解析结果校验逻辑。例如,对关键域名进行二次解析,比对不同 DNS 服务器返回的 IP 是否一致;或者使用 HTTPS 连接时校验证书 CN/SAN 字段与域名匹配,防止 DNS 劫持导致的中间人攻击。

你公司项目里是怎么处理 DNS 解析安全的?是用过 DoH 还是部署了 DNSSEC?或者遇到过 DNS 被篡改的案例?欢迎在评论区分享你的实战经验,一起避坑。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/23 20:46:00

穿越古剑之我是剑灵实战项目避坑指南

穿越古剑之我是剑灵实战项目避坑指南 版本升级后 API 全变了,是不是让你瞬间懵圈?别慌,这就是很多新人接手【穿越古剑之我是剑灵】相关模块时的第一道坎。在真实的【实战项目】里,这种“断崖式”的接口变更,往往直接导致线上服务抖动,甚至引发数据一致性危机。…

作者头像 李华
网站建设 2026/9/23 20:45:57

安装gcc别踩坑,从入门到精通只需3步

安装gcc别踩坑,从入门到精通只需3步 看了一堆教程还是不会写项目?别急,问题往往出在环境配置上。很多初学者卡在 gcc 安装这一步,明明照着视频敲了命令,结果编译时满屏报错,心态直接崩盘。其实, 安装gcc 只是 C/C++ 开发 入门到精通 的第一道门槛,跨过去,后面才顺。…

作者头像 李华
网站建设 2026/9/23 20:45:39

异步电动机工作原理新手避坑指南

异步电动机工作原理新手避坑指南 刚接触电机控制时,你是不是也被那些旋转磁场公式绕晕了?配置环境就卡半天,连个简单的启停都搞不定,这种挫败感我太懂了。很多新手在学异步电动机工作原理时,容易陷入“只看公式不看物理过程”的误区,结果代码写了一堆,电机要么不转,要么反转,甚至烧毁。今天咱们不整虚的,直接从底…

作者头像 李华
网站建设 2026/9/23 20:45:24

亲子鉴定的流程常见报错与解决

亲子鉴定流程踩坑实录:附完整示例与避坑指南 报错一堆看不懂 StackTrace,满屏的红字让人头皮发麻。刚跑通第一步,第二步直接崩盘,日志里全是 NullPointer 和 IndexOutOfBounds 。别急,这不是你代码写错了,是“亲子鉴定流程”里的隐性坑没踩对。…

作者头像 李华
网站建设 2026/9/23 20:45:14

旺旺英语论坛源码深挖:3个关键节点搞定性能优化

旺旺英语论坛源码深挖:3个关键节点搞定性能优化 面试被问原理答不上来,这种尴尬谁没经历过?尤其当面试官盯着你的简历,追问底层机制时,大脑一片空白是常态。很多开发者觉得性能优化是调参的艺术,其实核心在于对源码逻辑的深刻理解。…

作者头像 李华
网站建设 2026/9/23 20:45:11

5分钟搞定ios手游推荐工具实战项目,告别文档焦虑

5分钟搞定ios手游推荐工具实战项目,告别文档焦虑 官方文档太长抓不住重点?别慌。很多人搜 ios手游推荐 时,只想要一个能跑的列表,却掉进 API 文档的坑里。今天直接给个可落地的实战项目思路,用 Python 抓数据、做筛选,最后输出个性化推荐。 环境准备:装对工具才不踩坑 先说清楚,这不是去…

作者头像 李华