WEB前端安全——XSS和CSRF

5,492 阅读7分钟

一 XSS

XSS(Cross-site scripting),指的是跨站脚本攻击,攻击者通过向页面A注入代码,达到窃取信息等目的,本质是数据被当作程序执行。XSS危害是很大的,一般XSS可以做到以下的事情:

  • 获取页面的数据,包括dom、cookies、localStorage等
  • 劫持前端逻辑
  • 发送请求
  • ...

1 XSS的类型

  • 反射型(非持久):通过URL参数直接注入
  • 存储型(持久):存储到数据库后读取时注入
  • 基于DOM:被执行的恶意脚本会修改页面脚本结构

2 XSS的注入点

  • HTML的节点内容或属性
  • javascript代码
  • 富文本

3 XSS的防御

3.1 浏览器的防御

防御和“X-XSS-Protection”有关,默认值为1,即默认打开XSS防御,可以防御反射型的XSS,不过作用有限,只能防御注入到HTML的节点内容或属性的XSS,例如URL参数中包含script标签。不建议只依赖此防御手段。

3.2 防御HTML节点内容

存在风险的代码:

<template>
    <p>{{username}}</p>
</template>

<script>
    username = "<script>alert('xss')</script>"
</script>

编译后的代码:

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

以上例子是采用vue语法,但其实在vue这样的框架中,{{username}}中的内容是经过字符串化的,所以是不会被浏览器执行的,若换其他模板语言例如jade,则可能存在风险。下同。

防御代码:

通过转义<&lt以及>&gt来实现防御HTML节点内容。

<template>
    <p>{{username}}</p>
</template>
<script>
    escape = function(str){
        return str.replace(/</g, '&lt;').replace(/>/g, '&gt;')
    }
    username = escape("<script>alert('xss')</script>")
</script>

3.3 防御HTML属性

<template>
    <img :src="image" />
</template>
<script>
    image = 'www.a.com/c.png" onload="alert(1)'
</script>

编译后代码:

<img src="www.a.com/c.png" onload="alert(1)" />

防御代码:

通过转义"&quto;'&#39;来实现防御,一般不转义空格,但是这要求属性必须带引号!

<template>
    <img :src="image" />
</template>
<script>
    escape = function(str){
        return str.replace(/"/g, '&quto;').replace(/'/g, '&#39;').replace(/ /g, '&#32;')
    }
    image = escape('www.a.com/c.png" onload="alert(1)')
</script>

3.4 防御javaScript代码

假设访问页面地址为www.a.com?id=1";alert(1);"

风险代码:

var id = getQuery('id')

编译后代码:

var id = "1";alert(1);""

防御代码:

通过将数据进行JSON序列化

escape = function(str){
    return JSON.stringify(str)
}

3.5 防御富文本

风险代码:

<template>
    <p v-html="richTxt"></p>
</template>

<script>
    richTxt = '<a onmouseover=alert(document.cookie)>点击</a>'
</script>

上面的这段代码中,当鼠标移动到“点击”上面时,就会触发alert弹窗!这在vue中是会发生的。

防御富文本是比较复杂的工程,因为富文本可以包含HTML和script,这些难以预测与防御,建议是通过白名单的方式来过滤允许的HTML标签和标签的属性来进行防御,大概的实现方式是:

  • 将HTML代码段转成树级结构的数据
  • 遍历树的每一个节点,过滤节点的类型和属性,或进行特殊处理
  • 处理完成后,将树级结构转化成HTML代码

当然,也可以通过开源的第三方库来实现,类似的有js-xss

3.6 CSP 内容安全策略

CSP(content security policy),是一个额外的安全层,用于检测并削弱某些特定类型的攻击,包括跨站脚本 (XSS) 和数据注入攻击等。

CSP可以通过HTTP头部(Content-Security-Policy)或<meta>元素配置页面的内容安全策略,以控制浏览器可以为该页面获取哪些资源。比如一个可以上传文件和显示图片页面,应该允许图片来自任何地方,但限制表单的action属性只可以赋值为指定的端点。一个经过恰当设计的内容安全策略应该可以有效的保护页面免受跨站脚本攻击。

具体的配置描述可以移步MDN

4 关于在vue中的xss防御

vue已经在框架中进行了一些xss防御了,我们对于一些外来的内容(例如接口或URL参数),尽量用{{ }}表达式来显示,因为{{ }}中的内容经过字符串化,浏览器不会对其中的内容进行执行操作。

尽量少用v-hmtl指令,或者先对内容进行xss过滤。虽然 HTML 5 中指定不执行由 innerHTML 插入的 "script" 标签。但是其中的标签是可以有执行javascript的监听函数的,例如onload、onerror、onmouseover等等。

二 CSRF

CSRF(Cross Site Request Forgery)指的是跨站请求伪造。与XSS不同的是,XSS是攻击者直接对我们的网站A进行注入攻击,CSRF是通过网站B对我们的网站A进行伪造请求。

举个例子,你登录购物网站A之后点击一个恶意链接B,B请求了网站A的下单接口,结果是你在网站A的帐号真的会生成一个订单。其背后的原理是:网站B通过表单、get请求来伪造网站A的请求,这时候请求会带上网站A的cookies,若登录态是保存在cookies中,则实现了伪造攻击。

1 CSRF的危害

  • 用户的登录态被盗用
  • 完成业务的请求
  • ......

2 CSRF具有的特点

  • 伪造请求不经过网站A
  • 伪造请求的域名不是网站A

3 CSRF的防御

因为伪造GET请求的难度比POST请求的难度小(打开一个URL或是请求一个资源便是一个GET请求),所以对于涉及业务操作的接口,尽量用POST请求,另外,CSRF的防御手段可以根据CSRF的特点来入手。

3.1 增加验证码

CSRF的一个特点是伪造请求不经过网站A,那么我们可以通过增加网站A的验证手段,例如增加图形验证码或短信验证码等等,只有通过验证的请求才算合法。但是这种方案拥有两个局限性,一个是增加开发成本,另外一个是降低用户体验。

3.2 cookies设置sameSite

对于CSRF的第二个特点伪造请求的域名不是网站A,那么通过限制cookies不被其他域名网站使用,来达到防御的目的,具体的做法是:

cookies设置sameSite属性的值为strict,这样只有同源网站的请求才会带上cookies。但是此方案有浏览器兼容问题。

3.3 验证referer

第二个特点同时会造成伪造请求的referer不是网站A,因此我们可以限制不信任的请求来源。具体做法是:

后端可以根据HTTP请求头的`referer`来判断请求是否来自可信任网站。但是这个方案也有局限性,攻击者可以设置请求不携带referer,所以这个方案适合用于辅助。

3.4 验证csrf token

这是目前相对成熟的方案之一,具体的做法是:

服务端随机生成token,保存在服务端session中,同时保存到客户端中,客户端发送请求时,把token带到HTTP请求头或参数中,服务端接收到请求,验证请求中的token与session中的是否一致。

这个方案适用于前后端不分离的项目和前后端分离的项目,对于前后端不分离的项目,token可以直接在编译模板的过程中写到表单的隐藏字段中,这样发送请求不需要额外的操作;而对于前后端分离的项目,token可以在登录时写入到cookies中,发送请求时,js读取cookies中的token,并设置到HTTP请求头中。

3.5 更换登录态方案

因为CSRF本质是伪造请求携带了保存在cookies中的信息,所以对session机制的登录态比较不利,如果更换JWT(JSON Web Token)方案,其token信息一般设置到HTTP头部的,所以可以防御CSRF攻击。

对于两种登录态方案的优劣,这里就不展开了。

三 后记

关于XSS和CSRF的东西就介绍到这里了,如果想了解除了XSS和CSRF之外的前端安全,可以点击本篇文章的小老弟:

WEB前端安全——Cookie安全、密码安全、点击劫持