2021年12月9日,Apache Log4j2 爆出核弹级漏洞,该漏洞可以远程代码执行,一旦被攻击者利用,将造成严重危害。各大公司均连夜进行应急处理和推送业务修复。
在漏洞处理和修复过程中,有个 dnslog 被广泛提及,主要是用于严重业务是否存在漏洞,代码或者命令是否真实执行,因为其使用 dns 查询请求的 payload 在受影响的用户端执行,dnslog 服务端记录 dns 请求,也被叫 dns 回显平台。
这里,我们使用 go 开发一个 dns 记录服务,帮助业务快速自查风险。
首先,定义一个 DNS 结构,用于存储真实服务解析。
1 2 3 4 5
| type DNS struct { Port int Domain string Ip net.IP }
|
绑定端口进行 udp 数据解析, 我们通过 dns 相关信息知道,dns 请求数据最长为 512 字节:
1 2 3 4 5 6 7 8 9 10
| func (dns *DNS) Start() { conn, err := net.ListenUDP("udp", &net.UDPAddr{Port: dns.Port}) if err != nil { panic(err) } defer conn.Close() for { buf := make([]byte, 512) _, addr, _ := conn.ReadFromUDP(buf) }
|
接着解包 dns 数据,这里需要使用到包 golang.org/x/net/dns/dnsmessage
。
1 2 3 4 5 6 7 8 9 10
| if err := msg.Unpack(buf); err != nil { continue } if len(msg.Questions) == 0 { continue } go dns.ServerDNS(addr, conn, msg)
|
在处理的时候,我们首先梳理下,需要做哪些事情:
- 拿到 client ip,用于定位哪个服务发起了这个域名的 dns 查询。
- 正常返回 dns 结果,主要为 A 记录和 AAAA 记录。
- 记录查询请求的域名信息、类型信息、clientip 进行存储。
那我们开始吧,先拿到client ip:
1 2 3 4
| func (dns *DNS) ServerDNS(addr *net.UDPAddr, conn *net.UDPConn, msg dnsmessage.Message) { req := msg.Questions[0] ip := addr.IP port := addr.Port
|
实际测试发现,获取到的 IP 为 dns 查询链路的最后一个节点的 IP 地址,查资料发现,dns 请求是一个类似深度递归一样的操作,每个节点单独与下一个节点连接查询,当前节点无法知道前面节点从哪里接到的请求。所以,实际在 dns 回显记录服务中,难以获取到 client ip 数据。
接着让正常请求能够返回,获取请求类型,判断是否是 A 或者 AAAA ,根据类型进行处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| var reqTypeStr = req.Type.String() var reqNameStr = req.Name.String() var reqType = req.Type var reqName, _ = dnsmessage.NewName(reqNameStr)
fmt.Printf("[%s] reqName: [%s] clientip: [%s]:[%d]\n", reqTypeStr, reqNameStr, ip, port)
var resource dnsmessage.Resource switch reqType { case dnsmessage.TypeA: if strings.HasSuffix(reqNameStr, dns.Domain+".") { ipv4 := dns.Ip.To4() if ipv4 != nil { resource = dnsmessage.Resource{ Header: dnsmessage.ResourceHeader{ Name: reqName, Class: dnsmessage.ClassINET, TTL: 600, }, Body: &dnsmessage.AResource{ A: [4]byte{ipv4[0], ipv4[1], ipv4[2], ipv4[3]}, }, } } default: return } msg.Response = true msg.Answers = append(msg.Answers, resource) go dns.SendAnswers(addr, conn, msg) }
func (dns *DNS) SendAnswers(addr *net.UDPAddr, conn *net.UDPConn, msg dnsmessage.Message) { packed, err := msg.Pack() if err != nil { fmt.Println(err) return } if _, err := conn.WriteToUDP(packed, addr); err != nil { fmt.Println(err) return } }
|
我们来试试:
1 2 3 4 5
| func main(){ ipAddr := net.ParseIP("1.1.1.1") dns := &DNS{Domain: "www.baidu.com", Port: 8080, Ip: ipAddr} dns.Start() }
|
使用 dig 进行验证:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| └─(17:01:07)──> dig @127.0.0.1 -p 8080 111.www.baidu.com 9 ↵ ──(星期日 21年12月12日)─┘
; <<>> DiG 9.10.6 <<>> @127.0.0.1 -p 8080 111.www.baidu.com ; (1 server found) ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 48203 ;; flags: qr rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1 ;; WARNING: recursion requested but not available
;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 4096 ;; QUESTION SECTION: ;111.www.baidu.com. IN A
;; ANSWER SECTION: 111.www.baidu.com. 600 IN A 1.1.1.1
;; Query time: 1 msec ;; SERVER: 127.0.0.1#8080(127.0.0.1) ;; WHEN: Sun Dec 12 17:01:39 CST 2021 ;; MSG SIZE rcvd: 62
|
我们看到,111.www.baidu.com 的 A 记录已经被正常的解析到了 1.1.1.1 这个 ip 地址。
下面来搞定 AAAA 记录:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| case dnsmessage.TypeAAAA: if strings.HasSuffix(reqNameStr, dns.Domain+".") { ipv6 := dns.Ip.To16() if ipv6 != nil { var ipV6 [16]byte copy(ipV6[:], ipv6) resource = dnsmessage.Resource{ Header: dnsmessage.ResourceHeader{ Name: reqName, Class: dnsmessage.ClassINET, TTL: 600, }, Body: &dnsmessage.AAAAResource{ AAAA: ipV6, }, } }
|
这样服务就已经的对域名的 A 和 AAAA 记录了:
1 2
| [TypeA] reqName: [111.www.baidu.com.] clientip: [127.0.0.1]:[60065] [TypeAAAA] reqName: [111.www.baidu.com.] clientip: [127.0.0.1]:[50532]
|
后续只需要进行 dns 记录的存储和查询。在公司内部,也可以将 dnslog 的请求,全部转发到该服务,即可感知到所有被外部检查的存在漏洞的服务了。同时提供查询和删除的管理界面,也能给业务进行自查,帮助业务更快的修复安全漏洞。
最后,祈福永无BUG,无安全漏洞!!!
Comments