前端攻击方式之XSS攻击
一、XSS跨站脚本攻击
之前我们介绍过,由于浏览器的同源策略的存在,原本跨页面站点之间的跨域请求以及互相操作DOM都会被同源策略进行拦截而无法进行,但是这样子会导致web项目在开发及部署时非常不便利,为了平衡浏览器页面安全和开发维护便利的问题,浏览器又出让了一些安全性,比如:
- 允许页面通过CORS策略来进行跨域资源的安全访问;
- 允许页面引入来自其他源的资源到当前页面进行加载;
以上这些出让安全性的策略本身是好的,但是却又带来了新的问题,那就是著名的XSS攻击。
-
定义
XSS的全称叫做跨站脚本攻击(Cross Site Script),之所以叫做XSS而不是CSS的原因是会和样式表CSS的简称发生冲突。
XSS攻击是指黑客往HTML页面中或者DOM中注入恶意脚本,从而当用户在浏览器页面的时候利用注入的恶意脚本对用户实施攻击的一种手段。 -
Tips
需要注意的是,XSS的中文翻译有一定的误导性。最早开始的时候这种类型的攻击一般是通过跨域注入恶意脚本来实现的,所以命名为跨域脚本攻击。但是时至今日,往HTML文件中注入恶意脚本的方式有很多种,是否跨域注入脚本不再是唯一的注入手段,但是XSS这个名字却一直保留了下来。
二、XSS攻击对页面安全的影响
XSS攻击的前提
-
浏览器无法分辨脚本是否恶意
浏览器在执行脚本的时候,浏览器作为一个软件并不能分辨当前要执行的这个脚本是恶意注入的还是页面需要正常加载的,所以当黑客通过各种手段把恶意脚本注入到HTML页面中之后,浏览器解析DOM然后遇到脚本就会去执行这段恶意脚本。 -
恶意脚本拥有和正常脚本对于页面资源操作的同等权限
最重要的一点是原本正常脚本拥有对当前页面DOM、cookie、Storage等资源操作的权限,这个注入的恶意脚本也同样会有这些操作权限,所以攻击就在这里发生。
XSS攻击对页面安全的影响
-
窃取当前站点cookie信息
恶意脚本可以通过document.cookie读取当前页面的cookie信息,然后通过CORS策略基于Ajax请求将cookie发送给远端的黑客服务器,黑客拿到用户的cookie数据之后,如果这个网站的身份认证是基于cookie+session那一套做的,那么黑客便可以模拟用户登录,向源服务器发起转账等请求。 -
操作DOM伪造页面、生成广告浮窗等
恶意脚本拥有对于当前页面DOM的全部操作权限,黑客完全可以先将当前网站的界面屏蔽然后伪造一个登录页面,等用户输入用户名和密码等敏感信息之后通过Ajax请求发送至自己的黑客服务器。
除了伪造页面之外,还可以通过操作DOM生成广告浮窗,诱导用户点击来增加自己页面的访问量等。
- 监听用户行为
恶意脚本可以通过addEventListener接口来监听当前页面的键盘事件,进而通过事件对象获取到用户的个人信息,将其通过Ajax请求发送给黑客服务器等。
关于XSS攻击对页面安全带来的影响还有很多,重点一旦页面插入了恶意脚本,那么用户在页面的隐私数据和操作行为都将完全暴露给黑客,没有安全可言。
三、XSS攻击的注入方式
通常情况下,主要有三类方式来往一个站点注入恶意脚本:
1. 存储型XSS攻击
- 攻击步骤
- 黑客先利用站点的漏洞将恶意脚本提交到该站点的服务器,服务器由于未对输入做安全校验,直接存入到了数据库;
- 用户向网站请求了包含恶意脚本的HTML页面,浏览器解析HTML页面的时候遇到恶意脚本并执行;
- 恶意脚本读取到当前站点的隐私数据如cookie、用户信息等通过Ajax发送到黑客服务器;
- 黑客利用用户的cookie等信息在其他机器上登录该用户的账号,然后执行一些恶意攻击操作。
- 经典案例:
比如2015年喜马拉雅FM站点就因为服务器没有对用户输入的专辑名称做输入校验,黑客直接将一段当做文本提交给了服务器,然后当用户访问该专辑页面的时候,该恶意脚本就会被执行,随后窃取cookie完成攻击。
2. 反射型XSS攻击
反射性XSS攻击的一个前提是黑客知道当前站点某个网络请求需要用户携带参数,并且服务器会将用户发送的参数又返回给用户。
反射性XSS攻击和存储型XSS攻击的区别在于都没有对用户的输入做合格的关键字过滤,但是前者不存储用户输入,后者会将用户输入存储在服务器上。
- 攻击步骤
- 用户先将恶意脚本当做请求的一部分发送给服务器
- 服务器在收到请求之后又会将这个恶意脚本返回给用户
- 当恶意脚本被浏览器执行的时候就会产生攻击
3. DOM型XSS攻击
一般来说,XSS攻击主要是黑客通过服务器对于关键字过滤不严格等漏洞将恶意脚本插入到服务器中,然后服务器将这段恶意脚本返回给用户的浏览器之后,恶意脚本执行读取用户信息之后发送完成攻击。
但是,XSS攻击还有一类基于DOM的攻击,多数发生在页面在浏览器和服务器传输的过程中被黑客劫持,然后往HTML页面的DOM中插入恶意脚本或者修改页面内容,用户浏览器在加载被插入恶意脚本之后的HTML之后,恶意脚本执行黑客完成攻击。
XSS攻击易发的场景
四、如何阻止XSS攻击
通过之前的学习我们了解到,无论是发生在服务端的存储型和反射性攻击,还是发生在客户端的DOM类型攻击,XSS攻击都有以下几个共同点:
- 通过各种手段将恶意脚本注入当前页面的HTML中
- 恶意脚本执行读取页面敏感信息、监听用户行为等操作
- 恶意脚本通过Ajax请求以及CORS策略将读取到的隐私数据发送至黑客服务器
- 黑客获取到数据之后在另外一台机器上登录账号,完成攻击
我们了解到其实黑客进行XSS攻击的核心就两步:注入恶意脚本和发送用户敏感信息给远端黑客服务器。
所以如何阻止发生XSS攻击也就围绕以上两点来进行:要不阻止黑客注入恶意脚本,要不就阻止黑客发送用户敏感信息给黑客服务器。
策略1:服务器对输入的文本进行过滤或转码
无论是反射型还是存储型XSS攻击,服务器都需要对用户提交的数据进行关键字的过滤或转码,以防止XSS攻击的发生。这样做的原理是就算用户提交了恶意脚本到服务器端,经过过滤之后再次返回给浏览器时浏览器也不会将其当做脚本执行,只会当做普通文本加载。
- 对HTML中可以获取资源的标签文本如script、link、style、meta等文本进行过滤
- 对一些HTML中字符、js字符以及URL中携带的字符进行转码
- HTML标签中对以下字符进行转码,比如<转码为<>转码为>'转码为'比如:
// 用户提交
<script>alert('你被xss攻击了')</script>
// 服务器过滤
"<script>alert('你被xss攻击了')</script>"
- JS中对非数字、字母的字符都转为小于256的ASCII码字符
- URL中通过encodeURIComponent()方法对用户输入进行编码,此方法会将/ ? : @等字符进行转码
策略2:充分利用浏览器自带的CSP策略
CSP策略其主要目的就是为了防止发生XSS攻击的,它的核心是由开发者决定哪些资源可以被当做第三方资源嵌入当前页面,这样就等于给当前站点开了一个第三方资源的白名单,不在这个白名单上的资源浏览器就算获取到也不会加载执行,可以在很大程度上降低XSS的风险。
关于CSP策略后面会出一篇文章详细说明,这里先简单说下执行严格的CSP策略可以实现哪些功能:
- 限制加载其他站点的任意资源。就算黑客往当前页面中嵌入了一个js文件,此文件由于和当前站点不同源也是无法加载执行的
- 禁止执行内联脚本。黑客不能通过嵌入一个内联脚本来发起攻击。
- 禁止向其他站点提交数据,这样就算黑客的恶意脚本执行成功用户的个人信息也无法被发送出去
- 提供当攻击发生时候的上报机制,帮助我们主动发现XSS攻击
策略3:使用HttpOnly属性
有些XSS攻击的主要目的就是窃取用户的cookie,然后通过Ajax请求将其发送到黑客服务器。当然这是恶意脚本已经注入的前提下,对此我们可以要求服务端对于某些重要的cookie在设置响应头set-cookie的时候,将其属性HttpOnly设置为true。
属性设置HttpOnly为true的cookie,浏览器不允许脚本通过document.cookie方法对cookie进行读取和写入,这样就算恶意脚本注入之后也无法将用户的cookie提交给黑客服务器,也就避免了后续的攻击。
写在最后
XSS攻击大多数情况下都是服务端没有做好关键字的过滤和校验,从而导致恶意脚本被注入。
对于一些用户输入的内容表单,前端当然是需要对用户的输入进行合法性的校验,但是这种校验一般是基于业务的校验,比如电话号码比如为11位1开头的数字,比如用户名必须为小写字母和数字组成,真正意义上前端去做关键字过滤意义不大。因为黑客可以通过接口的方式绕过前端界面完成脚本注入,所以我个人认为XSS攻击的主要缺口在于服务端。
相对比服务端对用户输入做安全校验,前端也应该对服务器返回的数据进行校验,也就是一个不信任原则:来自服务器的任何数据都是不可信的,前端都要做为空校验。
比如后端和你约定的返回体中data是一个数组,然后你在代码中写了if(data.length > 0)这样的代码,如果后端某一天返回的data为空的时候是null,前端就会报错从而引发bug。这里只是一点个人的体会。