XSS攻击,前端应该懂的web安全知识

2,690 阅读10分钟

背景

事情发生在十一放假前。某天下午,正在努力写(mo)代(yu)码的我忽然收到了一封邮件,打开一看是安全部发的安全工单,内容为『下列代码可能导致安全漏洞』,再看风险级别——高危

当时我就感觉心跳加速开始害怕,于是点进链接看了下,发现是代码库中使用的v-html指令被判断为有XSS漏洞的风险。和同事进行了一番探讨后,决定用vue-dompurify-html替换了原来的指令,解决了工单。

就在我以为事情结束的时候,过了一个多星期我又收到了同样的安全工单,只不过是不同的代码库,以最快速度解决后就十一放假了。然而放假回来的第一个星期,我又收到了第三个安全工单emmmm。

连续三个安全工单,让我不得不重新审视一下XSS,所以趁(还没凉)热打铁学习下吧。

什么是XSS

XSS,即 Cross Site Script,中文翻译成跨站脚本攻击,其原本缩写是 CSS,但这会与层叠样式表(Cascading Style Sheets,CSS)的缩写混淆。因此,在安全领域我们将跨站脚本攻击缩写为XSS。

跨站脚本攻击(XSS),是最普遍的Web应用安全漏洞。这类漏洞能够使得攻击者嵌入恶意脚本代码到正常用户会访问到的页面中,当正常用户访问该页面时,则可导致嵌入的恶意脚本代码的执行,从而达到恶意攻击用户的目的。

XSS漏洞可以追溯到上世纪90年代。大量的网站曾遭受XSS漏洞攻击或被发现此类漏洞。根据开放网页应用安全计划(Open Web Application Security Project)公布的统计数据来看,在2010年之前,Web安全威胁前10位中,XSS排名第2,仅次于代码注入(Injection)。

而根据最新2021年的owasp top 10(owasp.org/www-project… 排名来看,XSS被归入到Injection类别中,排名第3,相较于十年前的占比已经小了很多。失效的控制访问和敏感信息泄露成为了排名Top1和Top2的漏洞。 image.png

XSS攻击的类型

根据攻击来源,XSS攻击可分为存储型XSS反射型XSSDOM型XSS三种。

存储型XSS

顾名思义,存储型XSS即可以存储在数据库能够造成XSS攻击的漏洞,是持久化的,也是较为稳定的XSS。

存储型XSS攻击常见于带有用户保存数据的网站功能,如论坛发帖、商品评论、用户私信等。

进行存储型XSS攻击的步骤一般如下:

  1. 找到网站中能够注入脚本的注入点,如评论框,私信栏,表单等能够输入文本的位置,将脚本通过注入点存储到数据库中;
  2. 当用户打开目标网站时,网页就会从数据库中取出恶意脚本,拼接到HTML中返回给浏览器。
  3. 用户浏览器解析了混有恶意脚本的网页,恶意脚本开始do something(比如偷你的数据,或者拿了cookie冒充正常用户去调用接口等等。

它是最危险的一种跨站脚本,相比反射型XSS和DOM型XSS具有更高的隐蔽性,所以危害更大,因为它不需要用户手动触发任何允许用户存储数据的web程序都可能存在存储型XSS漏洞,当攻击者提交一段XSS代码后,被服务器端接收并存储,当所有浏览者访问某个页面时都会被XSS攻击

反射型XSS

反射型XSS和存储型XSS不同,无法进行持久化,需要借助介质来进行攻击,例如通过在包含动态参数URL中加入恶意脚本来实现攻击。

进行反射型XSS攻击的步骤一般如下:

  1. 在网站中找到包含动态参数的URL,构造出包含恶意脚本的URL;
  2. 通过一定手段将URL发送给普通用户,用户点击链接进入网站,恶意代码从URL中取出,拼接在HTML 中返回给浏览器。
  3. 用户浏览器解析页面,其中的恶意代码也被执行。恶意代码就可以do something(同上)

反射型XSS常见于通过URL传参的功能,例如跳转、搜索,并且需要用户主动打开URL才能触发,为了能让攻击生效攻击者往往会使用诱导手段。换言之用户不点进链接就无事发生。

DOM型XSS

DOM型XSS实际上是一种特殊的反射型XSS。只不过DOM型XSS攻击完全发生在前端,从注入恶意代码到执行恶意代码都由DOM完成,不经过服务端也不会存储在数据库中。

进行DOM型XSS攻击的步骤一般如下:

  1. 在网站中找到包含动态参数的URL,构造出包含恶意脚本的URL;
  2. 通过一定手段将URL发送给普通用户,用户点击链接进入网站,前端JS从URL中拿到恶意代码并执行;
  3. 用户浏览器接解析页面,其中的恶意代码也被执行。恶意代码就可以do something(你懂的)

DOM型XSS攻击完全是由于前端JS漏洞所造成的,而其他两种XSS都属于服务端的漏洞。

对比小结

我们将三种XSS进行对比分析:

攻击类型注入点持久化用户交互漏洞发生的位置
存储型XSS输入框(能将用户输入存储的点)是,存储于数据库中不需要服务端
反射型XSSURL中的动态参数需要用户点击URL服务端
DOM型XSSURL中的动态参数需要用户点击URL客户端

总结一下三种XSS攻击的所需条件:

  1. 存在注入点(URL中的动态参数,接受用户输入的表单等等);
  2. 服务端返回的结果未进行转义,客户端完全信任,并且渲染成HTML;
  3. 对于DOM型XSS,客户端完全信任URL中的动态参数并且不转义,渲染成HTML。

实践案例

说了那么多文字,都不如show me your code来的实在,于是我们结合案例来看下XSS漏洞是如何攻击我们的服务的。

某一天你正在工作,领导忽然找到你说,『小明,咱们这个页面中毒啦快看看』,并且给了你链接ceshi.com:8080/wenzhang ,小明立刻紧张了起来,打开链接,页面蹦出了个弹窗:

image.png 小明疑惑了,这个弹窗从哪来的?于是F12查看页面的代码,发现有这么一段:

    <p>B: 
        <script>
                setTimeout(function(){
                        alert('你中奖啦')
                }, 3000)
        </script>
    </p>

看到这我们聪明的小明立刻去代码库里搜代码啦,然后发现并没有这段js。

作为一个自信的前端直觉告诉他是后端接口的问题,果不其然,在后端接口返回的信息中找到了未经转义的这段文本。小明很开心,将问题转给了后端同学心想这个锅不是我的,这下子自己没事了,后端同学用了十分钟把接口返回的文本进行了转义,修好了bug,于是小明回到了工位继续工(mo)作(yu)。

但是作为一个爱学习的前端同学,小明开始思考:这个地方是不是真的要渲染成HTML?是不是还有防范的空间?于是针对上述的存储型XSS,小明想了些措施来防范:

  1. 明确页面中的输入点,限制文本的长度(防止出现较长的恶意脚本)。
  2. 页面中输出数据的部分明确类型,究竟是渲染成普通文本还是HTML,不需要渲染成HTML的地方只渲染为text就好。
  3. 如果一定要渲染成HTML,后端接口输出时一定要转义

小明满意地写好了上面的总结。过了两天老板又甩给小明一个链接:http://ceshi.com:8080/article?key="><script>alert('又中奖啦');</script>,小明忐忑地点进链接,页面又弹出了XSS的弹窗。

小明开始紧张了,但是没办法还是得看看问题出在哪。一番ctrlF之后发现这是搜索文章关键字的页面,代码是这么写的:

<div> 文章关键词是:<%= getParameter("keyword") %> </div>

当key后的内容为><script>alert('又中奖啦');</script>时,服务端拿到关键字进行拼接后返回的HTML就是:

<div> 文章关键词是:"><script>alert('XSS');</script> </div>

于是页面上就执行了这段脚本。

既然这样,小明想,那让浏览器不执行脚本只渲染文本不就可以了么?于是小明找了个能够将HTML进行转义的库套上去,解决了这个反射型XSS漏洞

防范措施

经过这么两次XSS的漏洞攻击后,小明学会了很多,从中也总结出了一些防范XSS攻击的心得。

首先分析XSS攻击的必经路径:

  1. 客户端具有输入点
  2. 服务端返回未转移的恶意代码,并且由客户端执行。

针对客户端的输入点,我们是否做些措施来防范呢?比如:

  1. 限制输入点的长度,防止输入较长的恶意代码;
  2. 过滤其中可能存在的代码字符;
  3. 限制url参数中可能出现的参数类型
  4. ...

虽然以上条件都可以在一定程度上限制客户端的输入,但是如果攻击者绕过客户端借助工具(postman之类的)来构造参数调用服务端接口,那么这些措施就都没用了。所以要防止XSS攻击,效率最高也最有效的方法是限制服务端返回恶意代码并且客户端不执行非预期的代码

针对服务端我们要做的就是对内容进行转义,最大可能的防止恶意代码直接传输到客户端;针对客户端,对于服务端返回的内容,需要明确渲染的类型是文本还是DOM,对于可能出现DOM的部分也要进行转义过滤。例如只需要文本内容时使innerText方法,需要渲染为DOM时则要确认数据来源是否可信(来自服务端还是客户端),如果数据来自客户端则也需要进行处理,来避免DOM型XSS的攻击。

对于现代的前端开发来说,大部分项目都已经是前后端分离,使用诸如Vue/React等框架,前端的代码和后端的数据分离,已经能够减少很大一部分XSS攻击的可能了。对于前端开发者而言,仍然需要注意对DOM型XSS攻击的防范,例如v-html指令等。

话说回来,虽然我们可以做很多工作去防范XSS,但并不一定就能防住所有漏洞。我们的目标是能够最大化的增加使用XSS攻击的成本,上文说到的诸如前端的字符限制、过滤、类型限制等虽然不能100%防止攻击,但却能够增加成本,因此这样的措施也是有意义的。

参考文章

前端安全系列(一):如何防止XSS攻击? (美团的文章写得很好,值得一看)

XSS 实战:我是如何拿下你的百度账号 (漏洞案例)

前端面试查漏补缺--(七) XSS攻击与CSRF攻击