postMessage常见安全问题
字数 1537 2025-08-20 18:18:16
postMessage 安全实践指南
1. postMessage 基础概念
1.1 什么是 postMessage
postMessage 是一种跨源通信方法,它不受"同源策略"限制,允许不同源的网页之间进行安全通信。其他常见的跨域通信方法还包括 CORS 和 JSONP。
1.2 同源策略
同源策略是重要的安全策略,"同源"指的是协议、域名、端口都相同。非同源的两个资源间默认不能通信。
1.3 postMessage 工作原理
postMessage 跨源通信不受"同源策略"限制的前提:
- 发送方需要有接收方 window 的引用
- 发送方调用接收方 window 的 postMessage 方法发送消息
- 接收方的消息监听器收到消息进行后续处理
2. postMessage 常见安全问题及修复方案
2.1 未指定接收方 origin
问题描述:
- 发送方直接向 top 发送消息,而不是其直接父级
- 使用通配符 "*" 作为 targetOrigin,允许任何源接收消息
- 消息内容包含敏感信息(URL、调用栈、cookie 等)
修复方案:
- 在复杂的嵌套 iframe 结构中明确指定目标源
- 只向直接父窗口发送消息而不是直接向 top 发送消息
- 最小化通过 postMessage 传输的敏感信息
- 始终指定精确的目标 origin,而不是使用通配符 "*"
示例代码:
// 安全的消息发送
window.postMessage("hello", "https://specific-domain.com");
// 消息接收
window.addEventListener("message", function(event) {
console.log("Message received: ", event.data);
});
2.2 未验证消息来源
问题描述:
- 接收方未验证消息的 origin,直接处理接收到的数据
- 可能导致任意域发送的消息被执行,造成 XSS 等安全问题
攻击示例:
// 攻击者从任意域 iframe 发送恶意消息
frames[0].postMessage('append::<style onload="eval(alert(\'XSS via postMessage\'))">', "*");
// 不安全的接收处理
window.addEventListener('message', function(event) {
parseMessage(event.data); // 直接处理未验证的消息
});
修复方案:
- 始终验证 postMessage 的来源
- 对接收到的数据进行严格的验证和清理
- 避免直接执行或插入接收到的数据
安全代码示例:
window.addEventListener('message', function(event) {
// 验证来源
if (event.origin !== "https://trusted-domain.com") return;
// 安全处理消息
// ...
});
2.3 消息传输数据结构
postMessage 传输的数据通常包含:
data: postMessage 的第一个参数,发送的信息内容origin: 发送方的源(协议、域名、端口)source: 发送方 window 的引用(可选内容)
3. origin 验证的常见错误及修复
3.1 正则表达式验证问题
错误示例:
let re = /imgcache.test.com|user.openqa.test.com|www.dnspod.cn/
if(re.test(e.origin)){}
问题分析:
- 缺少起止符(^ 和 $): 允许部分匹配
- 点号 (.) 没有转义: 在正则表达式中,点号匹配任意字符
绕过方式:
arbitrary.imgcache.test.com.subdomain.example.com- 利用缺少起止符imgcacheatest.com- 利用点号没转义imgcacheatestacom.example.com- 结合上述两个问题
修复方案:
let re = /^(imgcache\.test\.com|user\.openqa\.test\.com|www\.dnspod\.cn)$/
if(re.test(e.origin)){}
3.2 endsWith 匹配问题
错误示例:
if(e.origin.endsWith('test.com')){}
问题分析:
- 未考虑到域名边界
- 任何以 'test.com' 结尾的字符串都会被匹配
绕过方式:
arbitrarytest.com- 添加任意前缀的域名
修复方案:
if(e.origin.endsWith('.test.com') || e.origin === 'test.com'){}
4. 最佳实践总结
-
精确指定目标 origin:
- 避免使用通配符 "*"
- 明确指定接收消息的具体域名
-
严格验证消息来源:
- 使用完整的正则表达式验证,包括起止符和转义
- 维护允许的源白名单
-
最小化敏感信息传输:
- 避免通过 postMessage 传输 cookie、token 等敏感信息
- 限制消息内容范围
-
安全的消息处理:
- 对接收到的数据进行验证和清理
- 避免直接执行或插入未经验证的数据
-
防御性编程:
- 考虑所有可能的边界情况
- 实现多层防御机制
5. 示例代码模板
安全的消息发送
// 明确指定目标 origin
const targetWindow = document.getElementById('iframe').contentWindow;
const targetOrigin = 'https://trusted-domain.com';
targetWindow.postMessage({data: 'secure message'}, targetOrigin);
安全的消息接收
window.addEventListener('message', function(event) {
// 验证来源
const allowedOrigins = [
'https://trusted-domain.com',
'https://sub.trusted-domain.com'
];
if (!allowedOrigins.includes(event.origin)) {
return; // 拒绝非信任源的消息
}
// 验证消息内容
if (typeof event.data !== 'object' || !event.data.hasOwnProperty('data')) {
return; // 拒绝不符合格式的消息
}
// 安全处理消息
processMessage(event.data.data);
});
function processMessage(data) {
// 实现安全的消息处理逻辑
// ...
}
通过遵循这些安全实践,可以充分利用 postMessage 的跨域通信能力,同时有效防范潜在的安全风险。