jQuery 1.11.3 DomXSS Vulnerability

07 May 2015 - evi1m0

Info

听群里说 WordPress 又爆跨站漏洞了:《XSS Vulnerability in Jetpack and the Twenty Fifteen Default Theme Affects Millions of WordPress Users 》,分析发现原来是 jQuery 老版本的 DOM XSS 漏洞【Bug #9521】。

11 年 dmethvin 提交 jQuery 1.6.1 版本的 Ticket #9521 , 其原因是由 $() | jQuery() 预期的 CSS 选择器在其他情况下可以用于创建 HTML 元素,如果编码不当(事实上很多编码不当的情况),将会导致产生 DomXSS 漏洞。

Example (jQuery 1.6.1)

<html>
<head>
    <title>jQuery DomXSS test</title>
    <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js"></script>
    <script>
        $(location.hash);
    </script>
</head>
<body>
Hello, jQuery.
</body>
</html>

WordPress default themes twentyfifteen example

example.html 297-299 lines:

// set permalink
var permalink = cssclass.split(' genericon-')[1];
window.location.hash = permalink;

console.log permalink:

  • http://linux.im/wp-content/themes/twentyfifteen/genericons/example.html#123
  • console.log(permalink): genericon-123

335-343 lines:

// pick random icon if no permalink, otherwise go to permalink
if ( window.location.hash ) {
    permalink = "genericon-" + window.location.hash.split('#')[1];
    attr = jQuery( '.' + permalink ).attr( 'alt' );
    cssclass = jQuery( '.' + permalink ).attr('class');
    displayGlyph( attr, cssclass );
} else {
    pickRandomIcon();
}

如果存在 window.location.hash 则拼接 permalink 并使用 jQuery 进行属性操作,问题出现,当我们将 location.hash 设置为 <img src=@ onerror=alert(1)> 时,导致跨站。

jQuery 1.6.1 Source

>_ $
jquery.js:25 function ( selector, context ) {
        // The jQuery object is actually just the init constructor 'enhanced'
        return new jQuery.fn.init( selector, context, rootjQuery );
    }

>_ jQuery.fn.init
jquery.js:93 function ( selector, context, rootjQuery ) {
        var match, elem, ret, doc;

        // Handle $(""), $(null), or $(undefined)
        if ( !selector ) {
            return this;
        }

        // Handle $(DOMElement)
        if ( selector.nodeType ) {
            this.context = this[0] = selector;
            this.length = 1;
            return this;
        }
......
        if (selector.selector !== undefined) {
            this.selector = selector.selector;
            this.context = selector.context;
        }

        return jQuery.makeArray( selector, this );
    }

其中 jQuery.fn.init :

if ( typeof selector === "string" ) {
    // Are we dealing with HTML string or an ID?
    if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) {
        // Assume that strings that start and end with <> are HTML and skip the regex check
        match = [ null, selector, null ];

    } else {
        match = quickExpr.exec( selector );
    }

quickExpr 对 selector 进行过滤,正则为:

quickExpr = /^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,

显然我们上面的 Payload 是能通过的。

jQuery 1.7.2 Source

当时漏洞报告者在 #9521 中提到修复方案:

the quick patch by jquery is here
-       quickExpr = /^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,
+       quickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,

尽管在开始的 Example 代码中不能生效,但由于程序开发人员的编码习惯显然按照上面的修复并没什么卵用,修复后原有的攻击代码效果:

>_ location.hash
"#test<img src=1 onerror=alert(1)>"

>_$(location.hash)
[]

因为正则新增 # 的原因导致增加失败,在真实环境中属性或其他操作直接使用 location.hash 的可能性叫小,开发人员以及业务需求使得上面的修复方案没有意义,例如开始提到的 WordPress Default Themes XSS 漏洞 337 行:

permalink = "genericon-" + window.location.hash.split('#')[1];

程序将获取到的 hash [‘#test111’] split 后,只保存 test111 ,也就使得我们能忽略到 1.7.2 的修复。

jQuery 1.11.3 Source

在前面版本中其实能够得以证明 jQuery 团队确实修复 #9521 的问题就是 quickExpr 的上方注释:

// A simple way to check for HTML strings or ID strings
// Prioritize #id over <tag> to avoid XSS via location.hash (#9521)
quickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,

可能开发团队遇到了 1.7.2 中我提到问题的尴尬窘境,他们在 1.11.3 又对其进行了升级:

rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,

看到这个正则我几乎无语,开头使用 < 就能轻易绕过:

终于,他们在 2.x 系列正式修复了这个问题:

rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,

Other browser

如你所见,上面这些 Payload 并不会在 Safari 中成效,通过调试即可发现 Chrome 未对 location.hash 部分进行 URL 编码处理进入函数,而 Safari 会经过 URL 编码进入函数,是这样的:

但是我们仍然可以使用 html5 的一些特性,引发错误并 onerror 出来:

file:///Users/evi1m0/Desktop/1.html#<video><source/onerror=alert(1)>
评论插件使用 Disqus ,需翻墙才能查看及留言。