各位观众老爷,大家好!我是你们的老朋友,今天咱们来聊聊一个前端老生常谈但又经常被忽略的安全话题:JS PostMessage
跨域通信漏洞与 Origin Validation Bypass
。 这玩意儿听起来高大上,其实说白了,就是你家大门没锁好,别人能溜进来偷东西。
咱们先来个简单的背景介绍。
跨域是个啥?
话说浏览器出于安全考虑,搞了个“同源策略”,简单来说,就是协议、域名、端口都一样的才能互相访问。 这就像住在一个小区,你家和隔壁老王家门牌号不一样,你就不能随便进老王家串门,怕你偷东西嘛!
但是!有时候我们就是想串门,比如A网站想把数据传给B网站,怎么办呢? 这时候 PostMessage
就闪亮登场了。
PostMessage是个啥?
PostMessage
是一个安全地实现跨域通信的机制。 它可以让不同源的页面之间传递消息。 就像小区物业允许你给老王家写信,通过物业转交,这样你就不用翻墙进老王家了。
// A网站 (http://a.example.com)
const otherWindow = window.open('http://b.example.com'); // 打开B网站
otherWindow.postMessage('Hello from A!', 'http://b.example.com');
window.addEventListener('message', (event) => {
if (event.origin !== 'http://b.example.com') {
return; // 拒绝来自其他域的消息
}
console.log('A received: ' + event.data);
});
// B网站 (http://b.example.com)
window.addEventListener('message', (event) => {
if (event.origin !== 'http://a.example.com') {
return; // 拒绝来自其他域的消息
}
console.log('B received: ' + event.data);
event.source.postMessage('Got your message A!', event.origin);
});
这段代码中,A网站给B网站发送了一个消息 "Hello from A!",B网站收到后回复 "Got your message A!"。 注意,这里用到了 event.origin
来验证消息的来源,防止有人冒充。
漏洞:Origin Validation Bypass
理论上很安全,但现实往往很骨感。 如果你 Origin Validation
没做好,或者压根没做,那就惨了,相当于你家大门没锁,谁都能进。
1. 缺失 Origin 验证
最常见的错误就是压根没验证 event.origin
。 就像小区物业根本不管谁来送信,直接往你家塞,啥牛鬼蛇神都来了。
// 错误示范!
window.addEventListener('message', (event) => {
console.log('Received: ' + event.data);
// 缺少 origin 验证!
// do something with event.data
});
这段代码直接接收所有来源的消息,并处理 event.data
。 如果 event.data
包含恶意代码,攻击者就可以为所欲为。 例如,攻击者可以修改页面上的敏感信息,甚至执行恶意脚本。
2. 弱 Origin 验证
有时候,开发者做了验证,但是验证写得太烂,跟没做一样。
-
只验证格式,不验证具体值
window.addEventListener('message', (event) => { if (typeof event.origin === 'string') { console.log('Received from origin: ' + event.origin); // do something with event.data } });
这段代码只验证了
event.origin
是字符串类型,但没验证具体的值。 攻击者可以发送任何字符串作为event.origin
,比如 "evil.com",也能通过验证。 -
使用不安全的字符串匹配
const allowedOrigin = 'http://a.example.com'; window.addEventListener('message', (event) => { if (event.origin.startsWith(allowedOrigin)) { console.log('Received from allowed origin: ' + event.origin); // do something with event.data } });
这段代码使用
startsWith
验证event.origin
是否以allowedOrigin
开头。 攻击者可以利用这个漏洞,发送http://a.example.com.evil.com
作为event.origin
,也能通过验证。 -
Origin 白名单不完整
const allowedOrigins = ['http://a.example.com', 'http://b.example.com']; window.addEventListener('message', (event) => { if (allowedOrigins.includes(event.origin)) { console.log('Received from allowed origin: ' + event.origin); // do something with event.data } });
如果你的白名单漏掉了某些可信的域名,或者包含了错误的域名,也会导致安全问题。
3. 利用 Null Origin
有些情况下,event.origin
可能是 null
。 比如,当页面从 file://
协议打开时,或者当 PostMessage
的发送者是 data:
URL 时。
window.addEventListener('message', (event) => {
if (event.origin === 'null') {
console.log('Received from null origin');
// do something with event.data
}
});
如果你的代码允许 null
origin,攻击者就可以通过本地文件或者 data:
URL 发送恶意消息。
4. 协议绕过
某些情况下,开发者只验证了域名,而忽略了协议。
const allowedDomain = 'example.com';
window.addEventListener('message', (event) => {
if (new URL(event.origin).hostname === allowedDomain) {
console.log('Received from allowed domain: ' + event.origin);
// do something with event.data
}
});
这段代码只验证了域名是 example.com
,而没有验证协议是 http
还是 https
。 攻击者可以通过 http://example.com
发送消息,绕过 HTTPS 的安全保护。
5. Content Security Policy (CSP) 绕过
即使你设置了 CSP,也可能存在绕过 PostMessage
的情况。 例如,如果你的 CSP 允许 unsafe-inline
,攻击者就可以通过 PostMessage
注入恶意脚本。
攻击示例
假设A网站 (http://a.example.com) 有一个功能,允许用户修改个人资料。 用户提交的资料通过 PostMessage
发送给一个嵌入在A网站的iframe,这个iframe来自B网站 (http://b.example.com)。 B网站负责处理用户资料,并更新数据库。
<!-- A网站 (http://a.example.com) -->
<iframe src="http://b.example.com/profile.html"></iframe>
<script>
const iframe = document.querySelector('iframe');
document.getElementById('updateButton').addEventListener('click', () => {
const name = document.getElementById('name').value;
const email = document.getElementById('email').value;
iframe.contentWindow.postMessage({ name: name, email: email }, 'http://b.example.com');
});
</script>
<!-- B网站 (http://b.example.com/profile.html) -->
<script>
window.addEventListener('message', (event) => {
// 错误的 origin 验证!
if (event.origin !== 'http://a.example.com') {
return;
}
const data = event.data;
console.log('Received profile data:', data);
// 处理用户资料,更新数据库
// ...
});
</script>
这段代码看起来很正常,A网站发送用户资料给B网站,B网站验证 event.origin
是 http://a.example.com
。 但是,如果B网站的验证逻辑存在漏洞,攻击者就可以伪造 event.origin
,发送恶意数据。
例如,攻击者可以创建一个恶意网站 (http://evil.com),包含以下代码:
<!-- 恶意网站 (http://evil.com) -->
<iframe src="http://b.example.com/profile.html"></iframe>
<script>
const iframe = document.querySelector('iframe');
iframe.onload = () => {
// 伪造 origin,发送恶意数据
iframe.contentWindow.postMessage({ name: '<script>alert("XSS")</script>', email: '[email protected]' }, 'http://a.example.com');
};
</script>
这段代码首先加载 B网站的 profile.html
,然后伪造 event.origin
为 http://a.example.com
,发送包含 XSS 攻击的恶意数据。 由于B网站的验证逻辑存在漏洞,恶意数据被成功接收,并可能导致 XSS 攻击。
如何防范?
防止 PostMessage
跨域通信漏洞,需要做到以下几点:
1. 严格验证 Origin
这是最重要的一点! 必须严格验证 event.origin
,确保消息来自可信的源。
-
使用精确匹配
const allowedOrigin = 'http://a.example.com'; window.addEventListener('message', (event) => { if (event.origin === allowedOrigin) { console.log('Received from allowed origin: ' + event.origin); // 安全地处理 event.data } else { console.warn('Received message from untrusted origin: ' + event.origin); } });
使用
===
进行精确匹配,确保event.origin
与allowedOrigin
完全一致。 -
使用 Origin 白名单
const allowedOrigins = ['http://a.example.com', 'https://a.example.com', 'http://b.example.com']; window.addEventListener('message', (event) => { if (allowedOrigins.includes(event.origin)) { console.log('Received from allowed origin: ' + event.origin); // 安全地处理 event.data } else { console.warn('Received message from untrusted origin: ' + event.origin); } });
维护一个可信的 Origin 白名单,只允许白名单中的域名发送消息。
-
使用 URL 对象进行验证
const allowedOrigin = 'https://a.example.com'; window.addEventListener('message', (event) => { try { const originURL = new URL(event.origin); if (originURL.protocol === 'https:' && originURL.hostname === 'a.example.com') { console.log('Received from allowed origin: ' + event.origin); // 安全地处理 event.data } else { console.warn('Received message from untrusted origin: ' + event.origin); } } catch (e) { console.error('Invalid origin: ' + event.origin); } });
使用
URL
对象解析event.origin
,并验证协议和域名,防止协议绕过。
2. 验证 Message 的格式和内容
除了验证 event.origin
,还要验证 event.data
的格式和内容,防止恶意数据注入。
-
使用 JSON Schema 验证数据格式
const schema = { type: 'object', properties: { name: { type: 'string' }, email: { type: 'string', format: 'email' } }, required: ['name', 'email'] }; window.addEventListener('message', (event) => { if (event.origin === 'http://a.example.com') { try { const data = JSON.parse(event.data); const valid = Ajv.validate(schema, data); // 使用 Ajv 库进行验证 if (valid) { console.log('Received valid data:', data); // 安全地处理 data } else { console.error('Invalid data format:', Ajv.errors); } } catch (e) { console.error('Invalid JSON format: ' + event.data); } } });
使用 JSON Schema 定义数据格式,并使用验证库(例如 Ajv)验证
event.data
是否符合规范。 -
使用正则表达式验证数据内容
const nameRegex = /^[a-zA-Z ]+$/; const emailRegex = /^[^s@]+@[^s@]+.[^s@]+$/; window.addEventListener('message', (event) => { if (event.origin === 'http://a.example.com') { const data = JSON.parse(event.data); if (nameRegex.test(data.name) && emailRegex.test(data.email)) { console.log('Received valid data:', data); // 安全地处理 data } else { console.error('Invalid data content:', data); } } });
使用正则表达式验证
event.data
的内容,确保数据符合预期格式。
3. 避免使用 eval()
和 Function()
永远不要使用 eval()
和 Function()
来处理 PostMessage
收到的数据,因为它们可以执行任意代码。
// 错误示范!
window.addEventListener('message', (event) => {
// 极其危险!
eval(event.data);
});
4. 使用 Content Security Policy (CSP)
CSP 可以限制页面可以加载的资源,以及可以执行的脚本,从而减少 XSS 攻击的风险。
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
这个 CSP 策略只允许从同源加载资源和执行脚本,可以有效防止 XSS 攻击。
5. 使用 Subresource Integrity (SRI)
SRI 可以验证从 CDN 加载的资源的完整性,防止 CDN 被攻击后,你的网站也被感染。
<script src="https://cdn.example.com/script.js" integrity="sha384-..." crossorigin="anonymous"></script>
6. 最小化 PostMessage 的使用
尽量减少 PostMessage
的使用,如果可以用其他方式实现跨域通信,尽量选择更安全的方式。 例如,可以使用 CORS 或者 JSONP。
7. 安全审查和代码审计
定期进行安全审查和代码审计,查找潜在的安全漏洞。
总结
PostMessage
是一个强大的跨域通信工具,但如果不小心使用,也可能导致严重的安全问题。 记住,安全是一个持续的过程,需要不断学习和实践。
防御措施 | 描述 |
---|---|
严格验证 Origin | 使用精确匹配、Origin 白名单或者 URL 对象进行验证,确保消息来自可信的源。 |
验证 Message 的格式和内容 | 使用 JSON Schema 或正则表达式验证数据格式和内容,防止恶意数据注入。 |
避免使用 eval() 和 Function() |
永远不要使用 eval() 和 Function() 来处理 PostMessage 收到的数据。 |
使用 Content Security Policy (CSP) | 限制页面可以加载的资源和执行的脚本,减少 XSS 攻击的风险。 |
使用 Subresource Integrity (SRI) | 验证从 CDN 加载的资源的完整性,防止 CDN 被攻击后,你的网站也被感染。 |
最小化 PostMessage 的使用 | 尽量减少 PostMessage 的使用,如果可以用其他方式实现跨域通信,尽量选择更安全的方式。 |
安全审查和代码审计 | 定期进行安全审查和代码审计,查找潜在的安全漏洞。 |
希望今天的讲座能帮助大家更好地理解 PostMessage
跨域通信漏洞,并采取有效的措施进行防范。 记住,安全无小事,多一份小心,少一份风险! 感谢大家的收听,我们下期再见!