浏览器页面安全-CSRF【安全篇】

439 阅读11分钟

什么是 CSRF 攻击

CSRF (Cross-site request forgery),又称为“跨站请求伪造”,是指黑客引诱用户打开黑客的网站,在黑客的网站中,利用用户的已登录状态发起的跨站请求。简单来讲,CSRF 攻击就是黑客利用了用户的登录状态,并通过第三方的站点来做一些坏事。

原理

在这里插入图片描述

由上图可知,CSRF一般是由四个步骤构成

  1. 用户在网站中正常登录,比如在某银行中登录
  2. 服务器将登录信息Cookie写入浏览器中
  3. 在同一浏览器中,用户又打开了另外一个网站B,并在B站点中主动或者诱导用户调用银行站点中的转账接口。比如美图图片下载,股市内幕等等...
  4. 用户一点击,实际上是会携带步骤2中返回的Cookie向A的服务器(银行服务端)发送了一个转账的请求。而A的服务器(银行服务)以为是用户A的操作。于是便执行了对应的业务将一笔钱转移到了黑客的账户上

至此,在用户不知情的情况下,网银执行了转账业务,这就是跨站(第三方站点的发起请求)请求伪造(非用户发起的请求)的基本攻击原理。

此外,从CSRF的原理图中我们可以知道,一般CSRF要攻击成功,需要下面的三个必要条件:

  1. 目标站点一定要有 CSRF 漏洞;
  2. 用户要登录过目标站点,并且在浏览器上保持有该站点的登录状态;
  3. 需要用户打开一个第三方站点,可以是黑客的站点,也可以是一些论坛。

CSRF攻击方式

针对上面示意图中的3,在黑客的网站中主要有下面三种的方式来实施CSRF攻击。 这里为方便说明,就以银行有个转账的接口为例说明。

// 银行的转账接口,同时支持GET / POST
// 域名
https://xxx.bank
// API
/transfer/
// 参数
account: 转账的对象
amount: 转账的数目

自动发起的GET请求

借助图片自动发送

这是最简单的一种方式,比如在黑客网站中的代码逻辑如下所示:

<!DOCTYPE html>
<html>
  <body>
    <h1>黑客的站点:CSRF攻击演示</h1>
    <img src="https://xxx.bank/transfer?account=hacker&amount=520">
  </body>
</html>

在黑客的网站上,将一个转账的请求隐藏在了一张图片中,当浏览器加载这个图片的时候,实际上是发出了一个转账的请求,如果对应的服务器没有做防范的话,那就回转给黑客520大洋

后台脚本自动发送

黑客也可以在自己的网站中添加一个自动发送的逻辑,比如在页面加载完毕之后主动发送一个Ajax请求

window.onload = function(){
    axioe.get(''https://xxx.bank/transfer?account=hacker&amount=520")
}

自动发起的 POST 请求

隐藏表单勾结POST请求

如果转账的接口是POST请求,而不是GET请求,那么在黑客的网站中可以内置一个隐藏大表单来发送POST请求

<!DOCTYPE html>
<html>
<body>
  <h1>黑客的站点:CSRF攻击演示-表单POST请求</h1>
  <form id='form-wrap' action="https://xxx.bank/transfer" method=POST>
    <input type="hidden" name="account" value="hacker" />
    <input type="hidden" name="amount" value="520" />
  </form>
  <script> document.getElementById('form-wrap').submit(); </script>
</body>
</html>

后台脚本自动发送

黑客也可以在自己的网站中添加一个自动发送的逻辑,比如在页面加载完毕之后主动发送一个Ajax请求

window.onload = function(){
    axioe.post(''https://xxx.bank/transfer, {
    account: 'hacker',
    amount:520
  })
}

引诱用户点击链接

除了上面的自动发起的GET / POST 恶意请求之外,还有一种方式是诱惑用户点击黑客站点上的链接,这种方式通常出现在论坛或者恶意邮件上。黑客会采用很多方式去诱惑用户点击链接,示例代码如下所示:

<div>
  <img width=150 src='一张美女的图片'> </img> </div> <div>
  <a href="https://xxx.bank/transfer?account=hacker&amount=520" taget="_blank">
    点击下载美女照片
  </a>
</div>

在这个示例中,展示给用户的是一张美女的图片,当用户点击图片时,实际上是发送了一个CSRF的转账请求。

CSRF攻击特点

  1. 攻击一般发起在第三方网站,而不是被攻击的网站,被攻击的网站无法防止攻击发生
  2. 攻击利用受害者在被攻击网站的登录凭证,冒充受害者提交操作;而不是直接窃取数据。
  3. 整个过程攻击者并不能获取到受害者的登录凭证,仅仅是“冒用”。
  4. 跨站请求可以用各种方式:图片URL、超链接、Form提交等等。部分请求方式可以直接嵌入在第三方论坛、文章中,难以进行追踪
  5. 与XSS 相比,CSRF 攻击不需要将恶意代码注入用户的页面,仅仅是利用服务器的漏洞和用户的登录状态来实施攻击

CSRF带来的危害

  1. 利用用户的登录态
  2. 用户不知情 完成业务请求(盗取用户的资金,消费) 冒充用户发帖背锅 损坏网站名誉

如何防止 CSRF 攻击

通过前面的分析我们知道,CSRF与 XSS 攻击不同,CSRF 攻击不会往页面注入恶意脚本,因此黑客是无法通过 CSRF 攻击来获取用户页面数据的;而其中最关键的一点是要能找到服务器的漏洞,所以说对于 CSRF 攻击我们主要的防护手段是提升服务器的安全性。

充分利用Cookie 的 SameSite 属性

通过前面的分析我们知道,通常 CSRF 攻击都是从第三方站点发起的,要防止 CSRF 攻击,我们如果可以控制从第三方站点发送请求时禁止 Cookie 的发送。那这样就能大幅度的避免CSRF的攻击。 Cookie 中的 SameSite 属性正是为了解决这个问题的,通过使用 SameSite 可以有效地降低 CSRF 攻击的风险。 在 HTTP 响应头中,通过 set-cookie 字段设置 Cookie 时,可以带上 SameSite 选项,其通常有三个值

  1. None: 最为宽松,任何情况下都会发Cookie数据
  2. Lax:相对宽松,在跨站点的情况下,从第三方站点的链接打开和从第三方站点提交 Get 方式都会携带 Cookie。但如果在第三方站点中使用 Post 方法,或者通过 img、iframe 等标签加载的 URL,这些场景都不会携带 Cookie,这也是浏览器默认的值
  3. Strict:最为严格,如果 SameSite 的值是 Strict,那么浏览器会完全禁止第三方 Cookie

详细的SameSite的属性请参考:Set-Cookie-SameSite

验证请求的来源站点

由于 CSRF 攻击大多来自于第三方站点,我们可以在服务器端验证请求来源的站点,禁止来自第三方站点的请求。 HTTP 请求头中的 Referer 和 Origin 属性可以作为判断的依据。

Referer 是 HTTP 请求头中的一个字段,记录了该 HTTP 请求的来源地址。虽然可以通过 Referer 告诉服务器 HTTP 请求的来源,但是有一些场景是不适合将来源 URL 暴露给服务器的。 因此标准委员会又制定了 Origin 属性,在一些重要的场合,比如通过 XMLHttpRequest、Fecth 发起跨站请求或者通过 Post 方法发送请求时,都会带上 Origin 属性,比如我们随便看一个京东的网站接口如下图所示: 在这里插入图片描述这种方法并非万无一失,Referer的值是由浏览器提供的,虽然HTTP协议上有明确的要求,但是每个浏览器对于Referer的具体实现可能有差别,并不能保证浏览器自身没有安全漏洞。使用验证 Referer 值的方法,就是把安全性都依赖于第三方(即浏览器)来保障,从理论上来讲,这样并不是很安全。在部分情况下,攻击者可以隐藏,甚至修改自己请求的Referer。具体可参考 Referrer Policy

CSRF token

根据前面的分析我们可以知道,前面讲到CSRF的一个特征是,攻击者无法直接窃取到用户的信息(Cookie,Header,网站内容等),仅仅是冒用Cookie中的信息。

而CSRF攻击之所以能够成功,是因为服务器误把攻击者发送的请求当成了用户自己的请求。那么我们可以要求所有的用户请求都携带一个CSRF攻击者无法获取到的Token。服务器通过校验请求是否携带正确的Token,来把正常的请求和攻击的请求区分开,也可以防范CSRF的攻击。我们可以针对此特点做一些防御。具体的步骤分三步

  1. 服务端生成一个CSRF Token返回给浏览器
  2. 在浏览器端发起的请求需要带上这个CSRF Token
  3. 服务器会验证该 Token 是否合法。

如果是从第三方站点发出的请求,那么将无法获取到 CSRF Token 的值,所以即使发出了请求,服务器也会因为 CSRF Token 不正确而拒绝请求。Token一般是加密字符串以及时间戳,如果加密字符串一致且时间未过期,那么这个Token就是有效的,反之Token验证失败。 相比而言采用CSRF Token 是一个比较有效的防护方法,只要页面没有XSS漏洞泄露Token,那么接口的CSRF攻击就无法成功。

双重Cookie验证

另一种防御措施是使用双重提交Cookie。利用CSRF攻击不能获取到用户Cookie的特点,我们可以要求Ajax和表单请求携带一个Cookie中的值。 具体的流程如下所示:

  1. 在用户访问网站页面时,向请求域名注入一个Cookie,内容为随机字符串(例如csrfcookie=contencsrfcookies)
  2. 在前端向后端发起请求时,取出Cookie中的该字段,并添加到URL的参数中(比如:GET请求 https://xxx/bank/transfer?csrfcookie=contencsrfcookies)
  3. 后端接口验证Cookie中的字段与URL参数中的字段是否一致,不一致则拒绝。

这种方式实现比较简单,但效果相比CSRF Token要差一些。因为任何跨域都会导致前端无法获取Cookie中的字段(包括子域名之间),于是发生了如下情况

  1. 如果用户访问的网站为www.x.com,而后端的api域名为api.x.com。那么在www.x.com下,前端拿不到api.x.com的Cookie,也就无法完成双重Cookie认证
  2. 为了解决上面的问题,这个认证Cookie必须被种在x.com下,这样每个子域都可以访问
  3. 于此同时也导致,任何一个子域都可以修改a.com下的Cookie,比如(doc.x.com、info.x.com)
  4. 假如某个子域名存在漏洞被XSS攻击(例如doc.x.com)。虽然这个子域下并没有什么值得窃取的信息。但攻击者修改了x.com下的Cookie
  5. 攻击者就可以直接使用自己配置的Cookie,对XSS中招的用户再向www.x.com域名中,发起CSRF攻击。

产品功能设计

前面所说的,都是被攻击的网站如何做好防护。但我们也可以做一些产品功能的设计来防止自己的网站被利用成为攻击的目标。 比如 一些验证码,滑块验证,双因子验证等方式都可以有效的避免CSRF攻击。当然对应的缺点就是会对用户体验有一定的伤害。用户体验相对会受影响

总结

要实现一个CSRF攻击需要三个必备的条件:

  1. 目标站点存在漏洞
  2. 用户要登录过目标站点
  3. 黑客需要通过第三方站点发起攻击

根据这三个必要的条件,介绍了如何避免CSRF。

  1. 利用Cookie中的 SameSite 属性
  2. 利用请求头中的Origin来判断请求的来源
  3. CSRF Token的方式
  4. 双Cookie验证的方式
  5. 产品功能的设计,比如验证码,滑块,双因子验证

页面安全问题的主要原因就是浏览器为同源策略开的两个“后门”:一个是在页面中可以任意引用第三方资源,另外一个是通过 CORS 策略让 XMLHttpRequest 和 Fetch 去跨域请求资源。为了解决这些问题,我们引入了 CSP 来限制页面任意引入外部资源,引入了 HttpOnly 机制来禁止 XMLHttpRequest 或者 Fetch 发送一些关键 Cookie,引入了 SameSite 和 Origin 来防止 CSRF 攻击。

参考文档

Set-Cookie-SameSite Referrer Policy