*本文原创作者:tocttou,本文属FreeBuf原创奖励计划,未经许可禁止转载
在本文中,我向大家分享一个我在去年发现的Edge浏览器的漏洞。这个漏洞利用了浏览器XSS过滤器的缺陷,来绕过另一种XSS防御措施:CSP(Content Security Policy,内容安全策略)。注意这个漏洞并不是在绕过XSS过滤器,而是利用它让一些本来没有XSS的页面,强行制造出可利用的XSS漏洞。
0x01 背景
浏览器XSS过滤器诞生于IE 8,它用于防范反射型XSS攻击。其基本原理是[参1,2,3]:既然是反射型,那么URL中的某个参数的值必然会在页面的某个位置出现。当然不是每个反射到HTML页面中的参数都是XSS,比如example.com/index.php?id=12345
,如果页面中含有12345,这显然是无所谓的。但是如果example.com/index.php?id=<script>alert(1)</script>
中的script元素被反射回来,则有可能是XSS攻击。过滤器的判断逻辑大概是这样:首先判断GET或者POST的数据中有没有参数可能含有XSS代码,这一步浏览器内置了一个比较复杂的正则来匹配。如果匹配成功,则搜索这个参数值有没有出现在服务器返回的HTML中。如果出现了,则浏览器认为这是一个XSS攻击。Edge和IE的XSS过滤器有两种模式,一种是发现攻击后屏蔽整个页面,另一种是尝试修复XSS。默认是第二种模式,服务器可以通过设置HTTP头字段X-XSS-Protection: 1; mode=block
调成第一种模式[参4]。
下面说一下微软浏览器是如何“尝试修复XSS”的。举例来说,假设URL是example.com/index.php?id=<script>alert(1)</script>
,并且HTML代码中含有<script>alert(1)</script>
,那么浏览器就把HTML中的这个元素修改为<sc#ipt>alert(1)</script>
。修改之后再交给HTML解析器。由于它把r
改成了#
,破坏掉了script元素,那么之后这段JavaScript代码就不会被执行。embed、iframe、object、meta等标签同理,都是用#
替换一个字母来破坏这些标签。
上述的这个修复方法虽然有效地修复了一部分反射XSS,但是它也有潜在危险。自IE 8引入XSS过滤器以来就一直有研究者通过滥用这个修复逻辑,向本来没有XSS漏洞的页面注入XSS。一个简单的利用方法是:比如页面中本来就有<script src="jquery.js"></script>
,我们构造这个URL:example.com/index.php?<script src="jquery.js"></script>
。IE和Edge都会误报这是一个XSS攻击,并按上述方式修复,修复之后的代码成了<sc#ipt src="jquery.js"></script>
,于是jquery.js就没法加载了。当然这个例子不是漏洞,因为这就跟jquery.js文件不存在一样,虽然功能可能受影响,但没有安全问题。
在参考资料[5]中作者提到了另一个利用方法,比如有这样一个img标签:<img alt="x onload=alert(0) x" src="x.png">
,其中alt的值是注入点。本来这个标签不会执行代码,但是IE弄巧成拙地修复成<img alt#"x onload=alert(0) x" src="x.png">
。onload就执行了。
0x02 CVE-2017-0135
上文我们说了利用过滤器的误报来干掉jquery.js。既然它能干掉script标签,那么能不能干掉其他标签呢?特别是能不能干掉和安全相关的标签。我想到了Content Security Policy有两种设置方法,既可以在HTTP头字段中设置,也能在HTML中用<meta http-equiv="Content-Security-Policy" content="...">
来设置。于是我就试了下,如果网站用的是meta标签设置CSP,我能不能利用过滤器的误报来干掉这个meta标签,使得CSP失效。结论是可以。我构造了这样一个HTML页面,假设其URL是http://example.com/xss.html
:
<!DOCTYPE html>
<html>
<head>
<title>CSP Test</title>
<meta http-equiv="Content-Security-Policy" content="script-src 'self'">
</head>
<body>
<script>alert(document.domain);</script>
</body>
</html>
在一个支持CSP的浏览器中访问xss.html,由于script-src 'self'这条CSP策略,我们应该是看不见弹窗的。但是在Edge中访问http://example.com/xss.html?%3Cmeta%20http-equiv=%22Content-Security-Policy%22%20content=%22script-src%20'self'%22%3E
,你会发现Edge提醒你过滤器发现了一个XSS,然后弹窗就出现了。
这里Edge把meta标签当成了反射XSS,因为它既出现在URL中,又出现在了HTML head中。然后它就把meta标签修复成了<me#a http-equiv="Content-Security-Policy" content="script-src 'self'">
。于是这个标签就不再起作用。如果页面有存储型或反射型XSS,我们就可以利用这个漏洞来绕过CSP,进而执行XSS。
发现了这个漏洞之后我立即提交给了微软,2016年底提交,2017年3月在MS17-007中修复了这个漏洞,漏洞编号是CVE-2017-0135。因为这个漏洞符合“Windows预览版Edge浏览器赏金计划”要求,微软给了我1500美元奖金。
微软的修复这个漏洞之后,再打开上述PoC,Edge依然会误报它为反射XSS,但是不再尝试自动修复,而是强制屏蔽掉整个页面,不论服务器选择的是不是屏蔽模式。
0x03 宽字节编码XSS
修复之后我在想,其实上述meta标签绕过,应该还有一种利用场景,同理我们可以利用XSS过滤器干掉<meta charset>
标签。这样Edge只能猜页面的编码类型。如果猜错了,比如把本来是GBK编码的网页猜成UTF-8,也许也能导致XSS。这类漏洞原理参见[6]。比如这个HTML:
<!DOCTYPE html>
<html>
<head>
<meta charset="gb2312">
<title>CSP Test</title>
</head>
<body>
<script>
var a = "峔\";alert(1);//";
</script>
</body>
</html>
其中var a
的值,如果服务器按照GB2312编码过滤,浏览器也按照GB2312解析,上述代码不会弹窗。但是我们通过访问xss.html?<meta charset="gb2312">
,让Edge去掉<meta charset="gb2312">
之后,它就有可能认为是ASCII编码的。而“峔”的GB2312编码是8D 5C。按ASCII来说5C恰好是反斜线,与后面转义双引号的反斜线构成\\
。下一个双引号则闭合了这个字符串。这样alert(1)就执行了。(在浏览器编码下,相当于var a = "?\\"; alert(1); //";
)
当然因为这个利用方式是漏洞修复之后好久我才想到的,所以没办法测试了。仅供大家参考思路。
0x04 总结
这个漏洞说明网站尽量不要用默认模式的Edge过滤器。建议用X-XSS-Protection: 1; mode=block
模式。或者X-XSS-Protection: 0
关掉XSS过滤器。即使关掉都比Edge的自动修复安全(如果自信你的网站几乎没有反射XSS漏洞的话)。另外建议尽量使用HTTP头字段的方式设置CSP策略,如果不能控制HTTP响应头再考虑用meta标签。
我估计XSS过滤器今后还能发现其他的恶意利用情景。因为它的判断规则比较复杂,而且HTML标准不仅复杂而且随时变化。难免有些之前无所谓的自动修改,之后发现问题。另外XSS过滤器既要保证尽可能全的识别XSS,又要尽量避免误报,这个平衡点把握不好就容易出bug。所以我认为XSS过滤器是一个值得继续研究的攻击面。
0x05 参考资料
*本文原创作者:tocttou,本文属FreeBuf原创奖励计划,未经许可禁止转载