听团里说WordPress又爆跨站漏洞了:“ XSS漏洞在Jetpack和二十五默认主题影响百万WordPress用户 ”,分析发现原来是jQuery老版本的DOM XSS漏洞【错误#9521】。
11年dmethvin提交jQuery 1.6.1版本的Ticket#9521,其原因是由$() | jQuery()预选的CSS选择器在其他情况下可用于创建HTML元素,如果编码不当(事实上很多编码不当的情况),将会导致产生DomXSS漏洞。
示例(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默认主题二十一个例子
example.html 297-299行:
- // set permalink
- var permalink = cssclass.split(' genericon-')[1];
- window.location.hash = permalink;
console.log永久链接:
- http://linux.im/wp-content/themes/twentyfifteen/genericons/example.html#123
- console.log(permalink):genericon-123
335-343行:
- // 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则拼接固定链接并使用jQuery的进行属性操作,问题出现,当我们将的location.hash为设置<img src=@ onerror=alert(1)>时,导致跨站。
jQuery 1.6.1源码
- >_ $
- 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对选择器进行过滤,正则为:
- quickExpr = /^(?:[^<]*(<[wW]+>)[^>]*$|#([w-]*)$)/,
显然我们上面的Payload是能通过的。
jQuery 1.7.2源码
当时漏洞报告者在#9521中提到修复方案:
- the quick patch by jquery is here
- - quickExpr = /^(?:[^<]*(<[wW]+>)[^>]*$|#([w-]*)$)/,
- + quickExpr = /^(?:[^#<]*(<[wW]+>)[^>]*$|#([w-]*)$)/,
尽管在开始的例子代码中不能生效,但由于程序开发人员的编码习惯显然按照上面的修复并没有什么卵用,修复后原有的攻击代码效果:
- >_ location.hash
- "#test<img src=1 onerror=alert(1)>"
- >_$(location.hash)
- []
因为正则新增#的原因导致增加失败,在真实环境中属性或其他操作直接使用location.hash的可能性叫小,开发人员以及业务需求使得上面的修复方案没有意义,例如开始提到的WordPress默认主题XSS漏洞337行:
- permalink = "genericon-" + window.location.hash.split('#')[1];
程序将获取到的哈希['#test111'] split后,只保存test111,也就可以得到我们能忽略到1.7.2的修复。
jQuery 1.11.3源码
在前面版本中其实能够得以证明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 = /^(?:[^#<]*(<[wW]+>)[^>]*$|#([w-]*)$)/,
可能开发团队遇到了1.7.2中我提到问题的尴尬窘境,他们在1.11.3又对其进行了升级:
- rquickExpr = /^(?:s*(<[wW]+>)[^>]*|#([w-]*))$/,
看到这个正则我几乎无语,使用开头和结尾<就能轻易绕过:
终于,他们在2.x系列正式修复了这个问题:
- rquickExpr = /^(?:#([w-]+)|(w+)|.([w-]+))$/,
其他浏览器
如何所见,上面这些Payload并不会在Safari中成效,通过调试即可发现Chrome未对location.hash部分进行URL编码处理进入函数,而Safari会经过URL编码进入函数,是这样的:
但是我们仍然可以使用html5的一些特性,引发错误并onerror出来:
- file:///Users/evi1m0/Desktop/1.html#<video><source/onerror=alert(1)>
来源:
http://www.hack80.com/forum.php?mod=viewthread&tid=47045