0%

前端安全-XSS和CSRF

本篇主要讲下关于前端安全——XSS 和 CSRF

XSS

概述

XSS(Cross Site Script),指跨站脚本攻击。原本缩写为 CSS,但因为与层叠样式表缩写(CSS)重名要做区分,所以改为 XSS。

攻击方式

攻击者通过向网站页面注入恶意脚本(一般是 JavaScript),通过恶意脚本对客户端网页进行篡改,达到窃取信息等目的,本质是数据被当作程序执行。

XSS 的注入点

  • HTML 的节点内容或属性,存在读取可输入数据
  • javascript 代码,存在由后台注入的变量或用户输入的信息
  • 富文本

XSS 危害

  • 通过 document.cookie 盗取 cookie
  • 使用 js 或 css 破坏页面正常的结构与样式
  • 流量劫持(通过访问某段具有 window.location.href 定位到其他页面:<script>window.location.href="www.baidu.com";</script>
  • Dos 攻击:利用合理的客户端请求来占用过多的服务器资源,从而使合法用户无法得到服务器响应
  • 利用 iframe、frame、XMLHttpRequest 或上述 Flash 等方式,以(被攻击)用户的身份执行一些管理动作,或执行一些一般的如发微博、加好友、发私信等操作
  • 利用可被攻击的域受到其他域信任的特点,以受信任来源的身份请求一些平时不允许的操作,如进行不当的投票活动
  • 偷取网站任意数据、用户资料等等

XSS 的类型

反射型(非持久)

反射型 XSS,也叫非持久型 XSS,是指发生请求时,XSS 代码出现在请求 URL 中,作为参数提交到服务器,服务器解析并响应。响应内容包含 XSS 代码,返回给浏览器解析并执行,这个过程就像一次反射,所以叫反射型 XSS。

这种攻击方式通常需要攻击者诱使用户点击一个恶意链接,或者提交一个表单,或者进入一个恶意网站时,注入脚本进入被攻击者的网站。

该方式只会经过服务器,不会经过数据库。

例子:
比如用户进行搜索时,点击搜索按钮访问到如下链接

1
http://xxx.com/search?keyword="><script>alert('XSS攻击');</script>

当浏览器请求时,服务器解析参数 keyword,得到"><script>alert('XSS攻击');</script>,拼接到 HTML 中返回给浏览器,如下:

1
2
3
4
5
6
7
8
9
10
11
12
<input type="text" value="" />
<script>
alert('XSS攻击')
</script>
">
<button>搜索</button>
<div>
您搜索的关键词是:">
<script>
alert('XSS攻击')
</script>
</div>

因此将其执行了。

存储型(持久)

存储型 XSS,也叫持久型 XSS,主要是将 XSS 代码发送到服务器,当浏览器请求数据时,脚本从服务器上传回并执行。与反射型 XSS 的差别在于,提交的代码会存储在服务器端,下次请求时目标页面时不用再提交 XSS 代码。

比较常见的场景就是网页的留言板,攻击者在留言板写下包含攻击性的脚本代码,发表之后所有访问该留言的用户,留言内容从服务器解析之后发现有 XSS 代码于是当做正常的 HTML 和 JS 解析执行,就发生了 XSS 攻击。

该方式会经过服务器,也会经过数据库。

DOM 型

基于 DOM 的 XSS 攻击是指通过恶意脚本修改页面的 DOM 结构,将攻击脚本写在 URL 中,诱导用户点击该 URL,如果 URL 被解析,那么攻击脚本就会被运行,和前两者 XSS 攻击区别是:取出和执行恶意代码由浏览器端完成,不经过服务端,属于前端 JavaScript 自身的安全漏洞,而其他两种 XSS 都属于服务端的安全漏洞主要在于 DOM 型攻击。

例子引用自:DOM 型攻击例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<h2>XSS:</h2>
<input type="text" id="input" />
<button id="btn">Submit</button>
<div id="div"></div>
<script>
const input = document.getElementById('input')
const btn = document.getElementById('btn')
const div = document.getElementById('div')

let val

input.addEventListener(
'change',
e => {
val = e.target.value
},
false
)

btn.addEventListener(
'click',
() => {
div.innerHTML = `<a href=${val}>testLink</a>`
},
false
)
</script>

点击 Submit 按钮后,会在当前页面插入一个链接,其地址为用户的输入内容。如果用户在输入时构造了如下内容:

1
'' onclick=alert(/xss/)

用户提交之后,页面代码就变成了:

1
<a href onlick="alert(/xss/)">testLink</a>

此时,用户点击生成的链接,就会执行对应的脚本。

如何防范 XSS

总体就是不能将用户的输入直接存到服务器,需要对一些数据进行特殊处理

设置 HttpOnly

HttpOnly 是一个设置 cookie 是否可以被 javasript 脚本读取的属性,浏览器会禁止页面的 Javascript 访问带有 HttpOnly 属性的 Cookie。

严格来说,这种方式不是防御 XSS,而是在用户被 XSS 攻击之后,不被获取 Cookie 数据。

CSP 内容安全策略

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

CSP 可以通过 HTTP 头部(Content-Security-Policy)或

元素配置页面的内容安全策略,以控制浏览器可以为该页面获取哪些资源。比如一个可以上传文件和显示图片页面,应该允许图片来自任何地方,但限制表单的 action 属性只可以赋值为指定的端点。

现在主流的浏览器内置了防范 XSS 的措施,开启 CSP,即开启白名单,可阻止白名单以外的资源加载和运行

输入检查

对于用户的任何输入要进行编码、解码和过滤:

  • 编码:不能对用户输入的内容都保持原样,对用户输入的数据进行字符实体编码转义
  • 解码:原样显示内容的时候必须解码,不然显示不到内容了
  • 过滤:把输入的一些不合法的东西都过滤掉,从而保证安全性。如移除用户上传的 DOM 属性,如 onerror,移除用户上传的 Style 节点、iframe、script 节点等

对用户输入所包含的特殊字符或标签进行编码或过滤,如 <>script,防止 XSS 攻击

1
2
3
4
5
6
7
8
9
function escHTML(str) {
if (!str) return ''
return str
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/x27/g, '&#039;')
.replace(/x22/g, '&quto;')
}

输出检查

用户的输入会存在问题,服务端的输出也会存在问题。一般来说,除富文本的输出外,在变量输出到 HTML 页面时,可以使用编码或转义的方式来防御 XSS 攻击。例如利用 sanitize-html 对输出内容进行有规则的过滤之后再输出到页面中。

输入内容长度控制

对于不受信任的输入,都应该限定一个合理的长度。虽然无法完全防止 XSS 发生,但可以增加 XSS 攻击的难度。

验证码

防止脚本冒充用户提交危险操作。

CSRF

概述

CSRF(Cross Site Request Forgery)指的是跨站请求伪造,是一种劫持受信任用户向服务器发送非预期请求的攻击方式。跨域指的是请求来源于其他网站,伪造指的是非用户自身的意愿。

攻击方式

攻击者诱导受害者进入第三方网站,在第三方网站中,向被攻击网站发送跨站请求。利用受害者在被攻击网站已经获取的注册凭证,绕过后台的用户验证,达到冒充用户对被攻击的网站执行某项操作的目的。

与 XSS 攻击不同的是:XSS 是攻击者直接对我们的网站 A 进行注入攻击,CSRF 是通过网站 B 对我们的网站 A 进行伪造请求。

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

跨站请求可以用各种方式:图片 URL、超链接、CORS、Form 提交等等。部分请求方式可以直接嵌入在第三方论坛、文章中。

CSRF 危害

  • 用户的登录态被盗用
  • 冒充用户完成操作或修改数据

CSRF 的类型

GET 类型的 CSRF

例子引入自:GET CSRF 例子

假设有这样一个场景:目标网站 A(www.a.com),恶意网站 B(www.b.com)

两个网站的域名不一样,目标网站 A 上有一个删除文章的功能,通常是用户单击’删除文章’链接时才会删除指定的文章。这个链接是www.a.com/blog/del?id=1, id 代表不同的文章。实际上就是发起一个 GET 请求

  • 无法使用 Ajax 发起 GET 请求。因为 CSRF 请求是跨域的,而 Ajax 有同源策略的限制
  • 可以通过在恶意网站 B 上静态或者动态创建 img,script 等标签发起 GET 请求。将其 src 属性指向www.a.com/blog/del?id=1。通过标签的方式发起的请求不受同源策略的限制
  • 最后欺骗已经登录目标网站 A 的用户访问恶意网站 B,那么就会携带网站 A 源的登录凭证向网站 A 后台发起请求,这样攻击就发生了

CSRF 攻击有以下几个关键点:

  • 请求是跨域的,可以看出请求是从恶意网站 B 上发出的
  • 通过 img, script 等标签来发起一个 GET 请求,因为这些标签不受同源策略的限制
  • 发出的请求是身份认证后的

POST 类型的 CSRF

假如目标网站 A 上有发表文章的功能,那么我们就可以动态创建 form 标签,然后修改文章的题目。

在网站 B 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
function setForm() {
var form = document.createElement('form')
form.action = 'www.a.com/blog/article/update'
form.methods = 'POST'
var input = document.createElement('input')
input.type = 'text'
input.value = 'csfr攻击啦!'
input.id = 'title'
form.appendChild(input)
document.body.appendChild(form)
form.submit()
}
setForm()

上面代码可以看出,动态创建了 form 表单,然后调用 submit 方法,就可以通过跨域的伪造请求来实现修改目标网站 A 的某篇文章的标题了。

通常是利用自动提交的表单

1
2
3
4
5
6
7
<form action="http://xxx.com/money" method="post">
<input type="hidden" name="account" value="jacky" />
<input type="hidden" name="amount" value="10000" />
</form>
<script>
document.forms[0].submit()
</script>

链接类型的 CSRF

链接类型的 CSRF 并不常见,比起其他两种用户打开页面就中招的情况,这种需要用户点击链接才会触发。这种类型通常是在论坛中发布的图片中嵌入恶意链接,或者以广告的形式诱导用户中招,攻击者通常会以比较夸张的词语诱骗用户点击,例如:

1
<a href="http://test.com/csrf/withdraw.php?amount=1000&for=hacker" taget="_blank"> 重磅消息!! </a>

如何防范 CSRF

验证码

由于 CSRF 攻击伪造请求不会经过受攻击的网站,所以我们可以在网站加入验证码,这样必须通过验证码之后才能进行请求,有效的遏制了 CSRF 请求。

但是,这种方式不是万能的,并不是每个请求都加验证码,那样用户体验会非常不好,只能在部分请求添加,作为一种辅助的防御手段。

验证 Referer

在 HTTP 协议中,头部有个 Referer 字段,他记录了该 HTTP 请求的来源地址,在服务端设置该字段的检验,通过检查该字段,就可以知道该请求是否合法,不过请求头也容易伪造。

设置 SameSite:设置 cookie 的 SameSite 值为 strict,这样只有同源网站的请求才会带上 cookie。这样 cookies 就不能被其他域名网站使用,达到了防御的目的。

添加 token 验证

浏览器请求服务器时,服务器返回一个 token,每个请求都需要同时带上 token 和 cookie 才会被认为是合法请求

这是一种相对成熟的解决方案。要抵御 CSRF,关键在于在请求中放入攻击者所不能伪造的信息,并且该信息不存在于 Cookie 之中。在服务端随机生成 token,在 HTTP 请求中以参数的形式加入这个 token,并在服务器端建立一个拦截器来验证这个 token,如果请求中没有 token 或者 token 内容不正确,则认为可能是 CSRF 攻击而拒绝该请求。

更换登录态方案

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

CSRF 与 XSS 区别

本质上讲,XSS 是代码注入问题,CSRF 是 HTTP 问题。XSS 是内容没有过滤导致浏览器将攻击者的输入当代码执行。CSRF 则是因为浏览器在发送 HTTP 请求时候自动带上 cookie,而一般网站的 session 都存在 cookie 里面。XSS 利用的是用户对指定网站的信任,CSRF 利用的是网站对用户网页浏览器的信任。

参考

-------------本文结束感谢您的阅读-------------