什么是XSS攻击
通常指黑客通过“HTML注入”纂改了页面,插入了恶意的脚本,从而在用户浏览页面时,控制用户浏览器的一种攻击。在一开始,这种攻击的演示案例是跨域的,所以叫“跨站脚本”。但是发展到今天,由于Javascript的强大功能以及网站前端应用的复杂化,是否跨域已经不再重要。但是由于历史原因,这个名字保留了下来。
假设一个页面把用户输入的参数输出到页面上: <?php $input=$_GET["param"]; echo “<div>”.$input.”</div>”; ?> 如果提交一段HTML代码:
http://www.a.com/test.php?param=<script>alert(/xss/)</script>
会发现alert(/xss/)被执行了。
防御XSS
1.HttpOnly
浏览器将禁止页面的Javascript访问带有HttpOnly属性的Cookie。是为了解决劫持Cookie攻击。因为Javascript获取不到Cookie的值。
php5中设置HttpOnly的方法
setcookie("abc","1",null,null,null,null,true);
2.输入检查
常见的Web漏洞如XSS、SQL诸如等,都要求攻击者构造一些特殊字符,这些特殊字符可能是正常用户不会用到的,所以输入检查就有存在的必要了。 例如,用户名可能会被要求只能为字母、数字的组合。 输入检查的逻辑应该放在服务器端,否则很容易被绕过。目前的普遍做法是在客户端和服务器端都执行检查。
在XSS的防御上,输入检查一般是检查用户输入的数据中是否包含一些特殊字符,如< > ‘ “等。如果发现,则将这些字符过滤掉或编码。 比较智能的还会检查<script> javascript等敏感字符,网上有很多XSS Filter资源。但因为XSS Filter对语境不了解,有时不能检查出XSS攻击。
3.输出检查
一般来说,除了富文本的输出外,在变量输出到HTML页面时,可以使用编码或者转移的方式来防御XSS
攻击。 1)安全的编码函数: 针对HTML代码的编码方式是HtmlEncode。 HtmlEncode并非专用名词,它只是一种函数体现。它的作用是将字符转换成HTMLEntities,对应的标准是ISO-8859-1。 对了对抗XSS,在HtmlEncode中至少要转换以下字符: & => & < =>< > ” ‘ /
相应地,Javascript的编码方式可以使用JavascriptEncode。 JavascriptEncode与HtmlEncode的编码方法不同,他需要使用\对特殊字符进行转义。在对抗CSS时,还要求输出的变量必须在引号内部,以避免造成安全问题。比较下面两种写法: var x=escapeJavascript($evil); var y=’”‘+escapeJavascript($evil)+’”‘; 如果escapeJavascript函数只转义了几个危险字符,比如’ ” < > \ & #等,那么上面的两行代码输出后可能变成: var x=1;alert(2); var y=”1;alert(2)”; 所以要求使用JavascriptEncode的变量输出一定要在引号内。 可视很多开发者没有这个习惯怎么办?这将只能使用一个更加严格的JavascriptEncode函数–除了数字、字母外的所有字符,都使用十六进制”\xHH”的方式进行编码。在本例中: var x=1;alert(2); 变成了: var x=1\x3balert\x282\x29; 如此代码可以保证是安全的。
2)只需一种编码吗? XSS攻击主要发生在MVC架构中的View层。大部分的XSS漏洞可以在模版系统中解决。 不是使用了auto-escape就万事大吉了,XSS的防御需要区分情况对待。
正确地防御XSS XSS的本质还是一种”HTML注入”。 想要根治XSS,可以列出所有XSS可能发生的场景,在一一解决。 a)在HTML标签中输出:如 <div>$var</div> <a href=# >$var</a> 这样可以构造出: <div><script>alert(/xss/)</script></div> <a href=#><img scr=# onerror=alert(1) /></a> 防御方法是对变量使用HtmlEncode。
b)在HTML属性中输出:如 <div id=”abc” name=”$var” ></div> 可以构造出: <div id=”abc” name=””><script>alert(/xss/)</script><”” ></div> 防御方法也是HtmlEncode。在OWASP ESAPI中推荐了一种更严格的HtmlEncode–除了字母、数字外,其他所有的字符都被编码成HTMLEntities。 String sfa=ESAPI.encoder().encodeForHTMLAttribute(request.getParameter(“input”)]; 这种严格的编码方式,可以保证不会出现任何安全问题。
c)在<script>标签中输出:如 <script> var x=”$var”; </script> 可以构造出: <script> var x=””;alert(/xss/);//”; </script> 防御时也是使用JavascriptEncode
d)在事件中输出:如 <a href=# onclick=”funcA(‘$var’)” >test</a> 可以构造出: <a href=# onclick=”funcA(”);alert(/xss/);//’)” >test</a> 在防御时需要使用JavascriptEncode
e)在CSS中输出:在CSS和style、style attribute中形成的XSS的方式非常多样化,参考下面几个XSS的例子。
<STYLE>@import ‘http://ha.ckers.org/xss.css’;</STYLE> <STYLE>BODY {-moz-binding:url(“http://ha.ckers.org/xssmoz.xml#xss”)}</STYLE> <XSS STYLE=”behavior:url(xss.htc);”> <STYLE>li {list-style-image:url(“javascript:alert(‘XSS’)”);} </STYLE><UIL><LI>XSS <DIV STYLE=”background-image:url(javascript:alert(‘XSS’))”> <DIV STYLE=”width:expression(alert(‘XSS’));”> 所以一般来说,尽可能地禁止用户可控制的变量在”<style>标签”、”HTML标签的style属性”以及”CSS文件”中输出。如果一定有这样的需求,推荐使用OWASP ESAPI中的encodeForCSS()函数。类似于ESAPI.encoder().encodeForJavaScript()函数。
f)在地址中输出: 在地址中输出也较为复杂。一般来说,在URL的path(路径)或者search(参数)中输出,使用URLEncode即可。 例如: <a href=”http://www.evil.com/?test=$var” >test</a> 可能的攻击办法: <a href=”http://www.evil.com/?test=” onclick=alert(1)”” >test</a> 经过URLEncode后就可以防御。 但是还有一种情况,就是整个URL都能够被用户完全控制。这时URL的协议和Host部分是不能够使用URLEncode的,否则会改变URL的语义。 一个URL的组成如下: [Protocol][host][Path][search][Hash] 如
http://www.evil.com/a/b/c/test?abc=123#ssss
protocol=”http://” host=www.evil.com search=”?abc=123″ hash=#ssss 在Protocol和host中,如果使用严格的URLEncode函数,则会把”://”、”.”等都编码掉。 对于如下的输出方式: <a href=”http://supershll.blog.163.com/blog/$var” >test</a> 攻击者可能会构造: <a href=”javascript:alert(1);”>test</a> 除了“JavaScript作为伪协议可以执行代码外,还有VBScript、dataURI等伪协议可能导致脚本执行。 由此可见,如果用户能够完全控制URL,则可以执行脚本的方式有很多,如何解决这种情况呢? 一般来说,如果变量是整个URl,则应该首先检查变量是否是以Http开头,如果不是则自动添加,以保证不会出现伪协议类的XSS攻击。 在此之后再对变量进行URLEncode,即可保证不会有此类的XSS发生了。 OWASP ESAPI中有一个URLEncode的实现: ESAPI.encoder().encodeForURL
5.处理富文本 有些时候,网站需要允许用户提交一些自定义的HTML代码,称之为”富文本”。比如,一个用户在论坛里发帖,帖子里的内容里要有图片、视频、表格等,这些“富文本”的效果都需要通过HTML代码来实现。 如何区分安全的“富文本”和有攻击性的XSS呢? 在处理富文本时,还是要回到”输入检查”的思路上来。”输入检查“的主要功能是,在检查时还不知道变量的输出语境。但用户提交的”富文本“数据,其语义是完整的HTML代码,在输出时也不会拼凑到某个标签的属性中。因此可以特殊情况特殊处理。 在上一节中,列出了所有在HTML中可能执行脚本的地方。而一个优秀的”XSS Filter“,也应该能够找出HTML代码中所有可能执行脚本的地方。 HTML是一种结构化的语言,比较好分析。通过htmlparser可以解析出HTML代码的标签、标签属性和事件。 在过滤富文本时,”事件“应该被严格禁止,因为”富文本“的展示需求里不应该包括”事件“这种动态效果。而一些危险的标签,比如<iframe>、<script>、<base>、<form>等,也是应该严格禁止的。
在标签的选择上,应该使用白名单,避免使用黑名单。比如,只允许<a>、<img>、<div>等比较”安全“的标签存在。 ”白名单“原则不仅仅用于标签的选择,同样应该用于属性与事件的选择。 在富文本过滤中,处理CSS也是一件麻烦的事情。如果允许用户自定义的CSS、style,则也可能导致XSS攻击。因此尽可能地禁止用户自定义CSS与Style。 如果一定要允许用户自定义样式,则只能像过滤”富文本“一样过滤”CSS“。这需要一个CSS Parser对样式进行智能分析,检查其中是否包含危险代码。 有一些比较成熟的开源项目,实现了对富文本的XSS检查。 Anti-Samy是OWASP上的一个开源项目,也是目前最好的XSS Filter。最早它是基于Java的,现在已经扩展到.NET等语言。
6.防御DOM Based XSS DOM Based XSS是一种比较特别的XSS漏洞,前文提到的几种防御方法都不太适用,需要特别对待。 DOM Based XSS是如何形成的呢?回头看看这个例子: <script> function test(){ var str=document.getElementById(“text”).value; document.getElementById(“t”).innerHTML=”<a href=’http://supershll.blog.163.com/blog/”+str+”‘ >testLink</a>”; } </script> <div id=”t”></div> <input type=”text” id=”text” value=”” /> <input type=”button” id=”s” value=”write” oncick=”test()” />
在button的onclick事件中,执行了test()函数,而该函数中最关键的一句是: document.getElementById(“t”).innerHTML=”<a href=’http://supershll.blog.163.com/blog/”+str+”‘ >testLink</a>”; 将HTML代码写入了DOM节点,最后导致了XSS的发生。 事实上,DOM Based XSS是从Javascript中输出数据到HTML页面中。而前文提到的方法都是针对”从服务器应用直接输出到HTML页面”的XSS漏洞,因此并不适用于DOM Based XSS。 看看下面这个例子: <script> var x=”$var”; document.write(“<a href=’http://supershll.blog.163.com/blog/”+x+”‘ >test</a>”); </script> 变量”$var”输出在<script>标签内,可是最后又被document.write输出到HTML页面中。 假设为了保护”$var”直接在<script>标签内产生XSS,服务器端对齐进行了JavascriptEscape。可是$var在document.write时,仍然能够产生XSS,如下所示: <script> var x=”\x20\x27onlick\x3dalert\x281\x29\x3b…”; document.write(“<a href=’http://supershll.blog.163.com/blog/”+x+”‘ >test</a>”); </script> 页面渲染之后的实际结果如下: XSS攻击成功。 其原因在于,第一次执行JavascriptEscape后,只保护了: var x=”$var”; 但是当document.write输出数据到HTML页面时,浏览器重新渲染了页面。在<script>标签执行时,已经对变量x进行了解码,其后document.write再运行时,其参数就变成了: <a href=’http://supershll.blog.163.com/blog/ ‘onclick=alert(1);//’ ‘ >test</a> XSS因此而产生。 如果改成HtmlEncode也是如此。 正确的防御方法是什么呢?
首先,在”$var”输出到<script>时,应该执行一次javascriptEncode;其次,在document.write输出到HTML页面时,要分具体情况看待:如果是输出到事件或者脚本,则要再做一次javascriptEncode;如果是输出到HTML内容或者属性,则要做一次HtmlEncode。 也就是说,从 JavaScript输出到HTML页面,也相当于一次XSS输出的过程,需要分语境使用不同的编码函数。 会触发DOM Based XSS的地方很多,以下几个地方是 JavaScript输出到HTML页面的必经之路。 a) document.write() document.writeln() b) xxx.innerHTML= xxx.outerHTML= c) innerHTML.replace d) document.attachEvent() window.attachEvent() e) document.location.replace() document.location.assign() … 除了服务器端直接输出变量到Javascript之外,还有以下几个地方可能会成为DOM Based XSS的输入点,也需要重点关注。 a) 页面中所有的inputs框 b) window.location(href、hash等) c) window.name d) document.referrer e) document.cookie f) localstorage g) XMLHttpRequest返回的数据
7.换个角度看XSS的风险 从业务角度看XSS风险,用户之间有互动的页面的风险肯定比没有互动的页面的风险高。应重点修补风险高的页面。
理论上,XSS漏洞虽然复杂,但却是可以彻底解决的。在设计XSS解决方案时,应该深入理解XSS攻击的原理,针对不同的场景使用不同的方法。同时有很多开源项目为我们提供了参考
|
还没有人抢沙发呢~