原文:http://sirdarckcat.blogspot.jp/2016/12/how-to-bypass-csp-nonces-with-dom-xss.html

原作者:EDUARDO VELA

译:Holic (知道创宇404安全实验室)

TL;DR

CSP nonces 在对抗 DOM XSS 方面似乎并没有所谓的奇效。你可以通过几种方法绕过它们。我不知道如何修复,也许不应该修复。

感谢阅读。这篇博文介绍了绕过 CSP nonce 的方法。从上下文入手,然后介绍了在几种情况下绕过 CSP nonce ,结尾是一些评论。一如既往,这篇博文是我对这些问题的个人意见,而且很想听到你的观点。

我与 CSP 的关系,“错综复杂”

我曾很喜欢 Content-Security-Policy。打印在 2009 年,我为此兴奋无比。我兴致高涨,甚至花了一段时间在我的 ACS 项目 上实现 CSP (据我所知,这是第一个运行的 CSP 实现/原型)。它支持 hash 和白名单,我当时确信这可太棒了!我的摘要都是以“我是如何解决 XSS”作为开头的。

但是有一天,我的朋友 elhacker.net(WHK) 指出 ACS(以及 CSP 的扩展)通过 JSONP 可以很简单地绕过。他指出,如果你将包含 JSON 接口的域名列入白名单,那么你的防护就被破解了,而且确实有那么多问题,我还没有发现一个简单的方法来解决这个问题。我的心都碎了。💔

快进至 2015 年,当时 Mario Heiderich 举办了一个很酷的 XSS 挑战赛,叫做“sh*t, it's CSP”,其中有个挑战题是逃避一个看起来安全的 CSP 防护以及尽可能给出最短的 payload。不出所料,JSONP 出现了(但 Angular 和 Flash 也可以)。多说无益。

然后终于在 2016 年,一篇颇受欢迎的文章 “CSP Is Dead,Long Live CSP!” 总结了 WHK 和 Mario 对互联网整体 CSP 部署进行调查之后所强调的问题,由 Miki, Lukas, Sebastian 以及 Artur展示。这篇文章的结论是,CSP 白名单破碎不堪毫无益处。至少给 CSP 办场葬礼吧,我想。

CSP nonces 首先推出的时候,我对它们的关注点在于,它的传播似乎真的很困难。为了解决问题,2012 年的 dominatrixss-csp 致力于解决此问题,以便所有动态生成的脚本节点通过使用 dynamic resource filter 传播脚本随机数。这直接简化了随机数(nonce)的传播。因此,准确的方法在其文章中提出,并命名为 "strict-dynamic",而不是 "dominatrixss-csp" 的运行时脚本。这便有了很大的改善。我们自己获得了原生 dominatrixss!

这种新风格的 CSP,建议完全忽略白名单,并完全依赖随机数(nonces)。虽然 CSP nonces 的部署比白名单更困难(因为它需要服务器端更改每个单独的页面与策略),尽管如此它似乎提出了真正的安全上的益处,这显然缺乏基于白名单的方法。所以,今年秋天,我再一次为此感到相当乐观。也行有一种方法,使大多数 XSS 实际上 *真的* 无法利用。CSP 毕竟不是盖的。

但是在这个圣诞节,就像是圣诞老人带来一颗煤球,Sebastian Lekies 指出一点,在我看来似乎是对 CSP nonce 的重大打击,几乎使 CSP 对 2016 年的很多 XSS 漏洞无效。

A CSS+CSP+DOM XSS three-way

尽管 CSP nonce 看起来确实能缓解 15 岁的 XSS 漏洞,它对 DOM XSS 来说似乎并不是很有效。为了解释原因,我需要告诉你当代 Web 应用程序是怎么写的,这与2002年有何不同。

以前,大多数应用程序的逻辑存在于服务端,但是过去十年中它已经越来越多地向客户端发展。现如今,开发 Web 应用程序最有效的方法是在在 HTML JavaScript 中变现大多数 UI 代码。这允许 web 程序可离线使用,而且终端提供强大的 web API 访问。

现在,新开发的应用程序仍存在 XSS,区别在于,由于很多代码是用 JavaScript 写的,所以现在它们存在 DOM XSS 漏洞。这些正是 CSP nonces 不能统一防御(至少按照目前的实现)的错误类型。

我给你三个 DOM XSS 的例子(当然,列表不完全概括),CSP nonces 通常不能单独防御:

  1. 持久型 DOM XSS,当攻击者可以强制将页面跳转至易受攻击的页面,并且 payload 不包括在缓存的响应中(需要提取)。
  2. 包含第三方 HTML 代码的 DOM XSS 漏洞(例如,fetch(location.pathName).then(r=>r.text()).then(t=>body.innerHTML=t);)
  3. XSS payload 存在于 location.hash 中的 DOM XSS 漏洞(例如 https://victim/xss#!foo?payload=

个中缘由需要回溯到 2008 年(woooosh!)。早在 2008 年, Gareth Heyes, David Lindsay(http://twitter.com/thornmaker) 和我在微软 Bluehat 做了一个小型演示,叫做 CSS - The Sexy Assassin。其中,我们演示了一种技术,纯粹用 CSS3 选择器读取 HTML 属性(被 WiSec 偶然重新发现,并在几个月后由 kuza55 在他们的 25c3 访谈 Attacking Rich Internet Applications 中呈现)。

这个攻击方式的结论是,创建一个 CSS 程序从 HTML 属性中逐字节提取数据是可行的,只需通过在每次 CSS 选择器匹配时生成 HTTP 请求,并持续重复。如果你没有看懂它是怎么回事,看看这里。它的工作方式非常简单,它只是创建了一个表单的 CSS 属性选择器:

*[attribute^="a"]{background:url("record?match=a")}
*[attribute^="b"]{background:url("record?match=b")}
*[attribute^="c"]{background:url("record?match=c")}
[...]

然后,一旦我们得到匹配结果,重复:

*[attribute^="aa"]{background:url("record?match=aa")}
*[attribute^="ab"]{background:url("record?match=ab")}
*[attribute^="ac"]{background:url("record?match=ac")}
[...]

直到它提取出完整的属性。

script 标签的攻击非常简单。我们需要做完全相同的攻击,只需要确保 script 标签设置为 display:block; 。

因此,我们现在可以使用 CSS 提取 CSP nonce,我们唯一需要这么做的就是在同一页面注入多次。上面我给你的 DOM XSS 的三个例子正是如此。一种在同一文档多次注入 XSS payload 的方法。三个完美方案。

Proof of Concept

Alright! Let's do this =)

首先,要有持久型 DOM XSS。这一点是普遍问题,因为如果在“新世界”中,开发者应该使用 JavaScript 编写 UI,那么动听的内容需要异步来自服务器。

我的意思是,如果你使用 HTML + JavaScript 编写的 UI 代码,那么用户数据必须来自服务器。虽然此设计模式允许你控制应用程序加载的流程,但是它也使得加载同一文档两次可以返回不同的数据。

当然,现在的问题是:如果强制网页文档加载两次!当然是用 HTTP 缓存了!这就是 Sebastian 在圣诞节给我们展示的。

A happy @slekies wishing you happy CSP holidays! Ho! ho! ho! ho!

Sebastian 解释了 CSP nonce 与大多数缓存机制不见人的情况,并提供了一个简单的 PoC 来证明。经过 Twitter 的一系列讨论,结论相当清楚。

向你展示一个例子吧,我们从 APPEngine 入门指南中获取默认的 Guestbook 示例,并添加一些改动:添加 AJAX 支持和 CSP nonces。应用程序很简单,有明显的 XSS 漏洞,但它被 CSP nonces 防护了,真的吗?