阅读:3053回复:0
一个 Chrome XSS Filter Bypass 的分析
前几天,在微博上看到一条关于最近的 Chrome XSS Filter Bypass 的链接:webo,原始补丁在这里:补丁。在补丁中还提供了 PoC 用于后续的单元测试。
攻击者用一种巧妙的方法绕过了 Chrome 的 XSSAuditor 的过滤。不过微博里的那篇短文并没有对这个漏洞的缘由作分析。碰巧前段时间笔者仔细读过 Chrome 的 XSSAuditor 的代码,因此趁此机会自己分析了一下,如果有错误的地方还望指教。 ◆0 漏洞描述 原始 PoC : https://localhost/补丁如下: #!diffIndex: Source/core/html/parser/XSSAuditor.cppt a/Source/core/html/parser/XSSAuditor.cpp b/Source/core/html/parser/XSSAuditor.cppindex a1e1852201d23ac858c3b5065a2e26f52d128f4d..e73259145366c12c821a710fe83d3637529478ee 100644--- a/Source/core/html/parser/XSSAuditor.cpp+++ b/Source/core/html/parser/XSSAuditor.cpp@@ -471,15 +471,18 @@ bool XSSAuditor::filterCharacterToken(const FilterTokenRequest& request) if (m_state == PermittingAdjacentCharacterTokens) return false;- if ((m_state == SuppressingAdjacentCharacterTokens)- || (m_scriptTagFoundInRequest && isContainedInRequest(canonicalizedSnippetForJavaScript(request)))) {+ if (m_state == FilteringTokens && m_scriptTagFoundInRequest) {+ String snippet = canonicalizedSnippetForJavaScript(request);+ if (isContainedInRequest(snippet))+ m_state = SuppressingAdjacentCharacterTokens;+ else if (!snippet.isEmpty())+ m_state = PermittingAdjacentCharacterTokens;+ }+ if (m_state == SuppressingAdjacentCharacterTokens) { request.token.eraseCharacters(); request.token.appendToCharacter(' '); // Technically, character tokens can't be empty.- m_state = SuppressingAdjacentCharacterTokens; return true; }-- m_state = PermittingAdjacentCharacterTokens; return false; }补丁中的描述对这一漏洞进行了介绍,大意是,当过滤器过滤 script 标签的内容时,第一个区块的过滤结果将会影响后续区块。如果第一个区块被处理为空时,过滤匹配将会失败。 其实漏洞原理这一句话就介绍明白了。不过,如果对浏览器解析 HTML 的流程以及 XSS Filter 的实现没有了解的话,可能看不大懂上面这句话。 ◆1 背景知识 首先,浏览器是如何解析 HTML 的? 这里只简单的介绍一下和该漏洞相关的部分。 实际上,在 HTML5 中, HTML 的词法解析和语法解析是被写进规范里的,这个可以直接在 WHATWG 主页上查到。从笔者读源码的了解上看,Chrome(Chromium) 几乎完全遵循了该规范。 其中词法解析部分,将 HTML 源码解析成一个个 token ,比如 #!htmlaaa将被解析成 [Start Tag (div)][Characters (aaa)][Start Tag (img)(attr: {src: x})][Start Tag (script)][Characters x=1;][End Tag (script)]每个中括号即为一个 token ,每一个 token 都有一堆自己的属性,比如 token 的名称,属性值等。 SVG 标签有什么特殊的性质? 实际上,一般的 HTML 标签属于 HTML namespace 范畴下,而 SVG 标签属于 SVG namespace 范畴。在 SVG 内部,HTML 的解析是按照 XML 模式进行的(与此类似的有 MATHML 标签)。而 SVG 内部也支持 SCRIPT 标签,不过这里的 SCRIPT 标签的解析模式和 HTML 环境下有着很大的区别。某些时候,可以借助 SVG 中 SCRIPT 标签的特殊解析模式,构造一些特殊的攻击向量。比如: #!html在 XML 中实体符号会被解析成对应的字符。而在 HTML 环境中,SCRIPT 标签内部不会被做任何处理。 类似的,下面这种用法 Javascript 也会成功执行: #!html因为在 SVG 内,进入SCRIPT 标签内部后,不必等到出现 </script> 才退回标签解析模式,而是一旦遇到了新的标签,即可退出,因此在 SCRIPT 标签内依然可以插入其他标签。 Chrome 是如何过滤反射型 XSS 的? Chrome 的 XSS 过滤并没有用到正则。而且,这个过滤是在词法解析阶段进行的。也就是说,Chrome 实际上是根据 token 来做过滤的。过滤器会审查每一个 token ,如果发现 token 中存在危险的属性或字段,就对该字段进行处理,然后拿去和 URL 比对。如果发现 URL 中出现了该字段,即将该字段清空,并报告恶意脚本的插入。 比如上面的 #!htmlaaa[Start Tag (div)][Characters (aaa)][Start Tag (img)(attr: {src: x})][Start Tag (script)][Characters x=1;][End Tag (script)]首先解析器读取到 [Start Tag (div)],判断没有危险;接下来读取 [Characters (aaa)] ,也没有危险;然后读取 [Start Tag (img)(attr: {src: x})] ,过滤器将会检查 src 属性是否以 javascript: 开头,这里不是,同样没有危险;之后检查 [Start Tag (script)],这里过滤器将会到 URL 中找 script 字样,来确认 URL 是否有引入脚本的可能;并且从这里开始进入了 script 标签内部,过滤器会对此进行标识;之后到了 [Characters x=1;] ,注意到这里已经到了 SCRIPT 标签内部了,因此,过滤器将会拿 "x=1;" 在 URL 中搜索,这也是文章开头的 Patch 中出现的代码: #!diff - if ((m_state == SuppressingAdjacentCharacterTokens) - || (m_scriptTagFoundInRequest && isContainedInRequest(canonicalizedSnippetForJavaScript(request)))) {◆2 漏洞分析 这里我们先不管这个 Patch 是如何修复的,我们先来分析漏洞的成因。 PoC 中的注入代码将会解析成怎样的 token 序列呢? #!html是这样么? [Start Tag (svg)][Start Tag (script)][Characters /alert(0)][End Tag (script)]如果是这样, XSS Filter 是无法绕过的。 在这里,由于是在 SVG 标签内部,词法解析器将会考察每一个 " |
|