现实中大多数的WEB应用程序并非都脆弱不堪,漏洞百出,事实上,一个优秀负责的开发人员或团队是不容许自己的程序代码出现任何BUG或漏洞。通常情况下,为了防御跨站脚本攻击,会在WEB应用中设计一个XSS Filter,即跨站脚本过滤器,用于分析用户提交的输入,并消除潜在跨站脚本攻击、恶意的HTML或者简单的HTML格式错误等。
XSS Filter一般是基于黑白名单的安全过滤策略,即把要处理的数据分成白名单和黑名单两大列表,白名单存放可以信赖的对应用程序不构成威胁的数据列表,黑名单则相反。假定某个应用程序使用了基于黑名单式的过滤策略,那么当用户提交信息时,该系统的XSS Filter会对所有的输入进行检验,如检测到黑名单中的数据便进行拦截、编码和消毒过滤处理。XSS Filter实际上是一段精心编写的过滤函数,参看如下一段代码:
function dhtmlspecialchars($string){
if(is_array($string)){
foreach($string as $key = > $val){
$string[$key] = dhtmlspecialchars($val);
}
}
else{
string = preg_replace('/&((#(\d{3,5}|x[a-fA-F0-9]{4})|[a-zA-Z][a-z0-9]{2,5});)/','&\\1',str_replace(array('&','"','<','>'),array('&','"','<','>'),$string));
}
return $string;
}
这段代码的作用是过滤XSS跨站代码,只要程序在处理输入变量的时候使用该函数,就足以过滤常见的跨站脚本。尽管这段代码设计的很完整和详尽,但依然存在被绕过的可能性,因为在实际环境中,要完全确保系统免受XSS攻击是很困难的,开发者必须要考虑各种触发XSS的情况,因此实现起来相当复杂。下面从“攻”的角度去探讨,展示一些绕过XSS Filter的测试用例,透过这些用例来更加了解XSS跨站脚本的本质。
一.利用<>标记注射HTML/JavaScript
如果用户可以随心所欲的引入<>等标记,那么他就能操作一个HTML标签,然后通过<script>标签就能任意输入由JavaScript或VbScript编写的恶意脚本代码,如:
<script>alert("test")</script>
因此,XSS Filter首当其冲要进行过滤和转义的就是“<>”或“<script>”等字符。如此一来,某些形式的XSS即不复存在,如:
<script>shellcode</script>
二.利用HTML标签属性值执行XSS
现在假设用户不能构造自己的HTML标记,但是还可以使用其他形式来执行XSS,例如HTML标签的属性值。很多HTML标记中的属性都支持JavaScript代码,由JavaScript的解释器执行。所以用户可以利用部分HTML标记的属性值进行XSS,参考下面的代码:
<table backgroup="javascript:alert(/XSS/)"></table>
<img src="javascript:alert("XSS"); ">
如果浏览器中运行上述代码没有弹出对话框,那很正常,因为不是所有的WEB浏览器都支持JavaScript的伪协议,所以此类XSS攻击具有一定的局限性,不过如今仍然有大量的用户在使用支持该协议的浏览器如:IE6。当然并不是所有标记的属性值都能产生XSS,通常只有引用文件的属性才能发出跨站脚本,可以使用下面的属性来测试XSS:
- href=
- lowsrc=
- bgsound=
- background=
- value=
- action=
- dynsrc=
<a href="javascript:alert("XSS") ">XSS</a>
<bgsound src="javascript:alert("XSS"); ">
总而言之,要防御基于属性值的XSS,就要过滤JavaScript等关键字。另外,必须了解并非所有嵌入到WEB页面中的脚本都是JavaScript,还有其他允许值,比如VBscript。
三.空格回车TAB
如果XSS Filter仅仅把敏感的输入字符列入黑名单处理,如对敏感字javascript而言,用户可以利用空格、回车和TAB键绕过限制,请看下面的例子:
<img src="javas cript:alert(/XSS/)" width=100>
请注意javas和cript之间的间隔不是空格键,而是用TAB键添加的,把以上的代码保存在HTML中,用IE6打开,此时IE会顺利得弹窗。使用关键字拆分的技巧,用户就能突破过滤器的限制,当然这种技巧不局限在TAB键,还可以使用回车键、空格之类的其他键。为什么会这样呢?JavaScript语句通常以分号结尾,如果JavaScript引擎确定一个语句是完整的,而这一行的结尾有换行符,那么就可以省略分号:
var a=true
var b="andrew"
如果同一行中有多个语句,那么每个语句就必须使用分号来结束:
var a=true;var b="andrew";
除了在引号中分隔单词或强制结束语句之外,额外的空白无论以何种方式添加都无所谓。下面的代码,虽然语句中间有一个换行符,但变量的赋值是完全成功的:
var a="hello world";
alert(a);
引擎没有把换行符解释为语句的终止符,因为到换行处并不是一个完整的语句,JavaScript会继续处理发现的内容,直到遇到一个分号或发现语句完整为止。因此,用户可以构造下面的代码形式绕过系统对JavaScript等关键字的过滤:
<img src="javas
cript:
alert(/XSS/) width=100">
使用以回车符分割关键字(拆分关键字)的技巧,成功执行了跨站脚本代码。
四.对标签属性值转码
对普通HTML标记的属性值进行过滤,用户还可以通过编码处理来绕过,因为HTML中属性值本身支持ASCII码形式。ASCII,即美国信息交互标准代码,是目前计算机最常用的编码标准。因为计算机只能接受数字信息,ASCII码将字符作为数字来表述,以便计算机能够接受和处理,比如大写字母A的ASCII码是65。根据HTML的属性值支持ASCII码的特征,把XSS代码:
<img src="javascript:alert(/xss/);">
替换为:
<img src="javascript:alert(/xss/);">