前后端分离下如何防止 CSRF 攻击

什么是 CSRF 攻击

CSRF 的全名是 Cross Site Request Forgery,翻译成中文就是跨站点请求伪造

CSRF 利用的是网站对用户网页浏览器的信任,简单地说,是攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并运行一些操作(如发邮件,发消息,甚至财产操作如转账和购买商品)。
由于浏览器曾经认证过,所以被访问的网站会认为是真正的用户操作而去运行。这利用了 web 中用户身份验证的一个漏洞:简单的身份验证只能保证请求发自某个用户的浏览器,却不能保证请求本身是用户自愿发出的

例子:

假如一家银行用以运行转账操作的 URL 地址如下:https://bank.example.com/withdraw?account=AccoutName&amount=1000&for=PayeeName

那么,一个恶意攻击者可以在另一个网站上放置如下代码:<img src="https://bank.example.com/withdraw?account=Alice&amount=1000&for=Badman" />

如果有账户名为 Alice 的用户访问了恶意站点,而她之前刚访问过银行不久,登录信息尚未过期,那么她就会损失 1000 资金

常见的防御手段

验证码

CSRF 攻击往往是在用户不知情的情况下伪造了请求,而验证码强制用户必须进行交互

  • 措施:给敏感操作添加验证码校验
  • 局限:影响用户体验

检查 Referer 字段

在处理敏感请求时,通常发出请求的网址应该与接受请求的网址属于同一域名

  • 措施:所以我们可以通过校验 HTTP headers 中的 Referer 字段是否是属于本网站,如果不是则可能遭受 CSRF 攻击
  • 局限:这种办法简单易行,工作量低,但是依赖于浏览器的发送正确的 Referer 字段,无法保证浏览器没有安全漏洞影响 Referer 的发送

不使用 GET 请求做敏感操作

在上面的例子中,我们可以知道,Alice 打开恶意站点后,浏览器通过 img 标签向银行转账网址发送了 GET 请求从而实现了 CSRF 攻击。

  • 措施:所以,避免 CSRF 攻击最基础的一条是 永远不使用 GET 请求做敏感操作
  • 局限:这种方法可以避免 imgscriptiframe 等带 src 属性的标签发起 CSRF 攻击。但是无法防止攻击者构建 POST 请求等发起 CSRF 攻击

使用 Anti-CSRF-Token

CSRF 为什么能够攻击成功?其本质原因是重要操作的所有参数都是可以被攻击者猜测到的。攻击者只有预测出 URL 的所有参数与参数值,才能成功地构造一个伪造的请求;反之,攻击者将无法攻击成功。

  • 措施:所以我们可以生成一个足够随机的参数 Token 放入该次请求,保证该次请求无法被攻击者成功伪造。

    核心思路:使用一个由服务器派发的 Token,在前端进行状态修改时,也同时提交这个 Token(往往会放在 html forminput 中,或者 ajax header 中),这时候服务端验证该 Token 是否是之前所生成的,以此来判断这个请求是否被允许

  • 局限

    1. 依赖于 Token 足够随机
    2. 无论是 Token 放在哪里,只要 JS 能读取到,都会面临 XSS 风险

在常见的 Web 后端框架比如 RailsDjango 中都自带实现了 csrf_token 功能,不同的是

  • Rails 使用 meta 标签存放 csrf_token,通过 HTTP headers 发送
  • Django 使用隐藏的 input 标签存放 csrf_token,通过 POST body 发送

前后端分离带来的问题

在现在大前端时代,前端后分离是个再常见不过的情况,我们该如何在前后端分离的情况下防止 CSRF 攻击呢?

首先,我们分析下上面所提到的几种手段在前后端分离下发生了什么变化:

  • 验证码:
    • form 表单完全由前端生成,后端是无法感知的,即使前端使用某种方式在 form 中放入了验证码,但是后端也无法验证
  • 检查 Referer 字段:无变化
  • 不使用 GET 请求做敏感操作:无变化
  • 使用 Anti-CSRF-Token:
    • form 表单完全由前端生成,后端是无法感知的,即使前端使用某种方式在 form 中放入了 Token,但是后端也无法验证

所以根本上是前后端分离之后,后端无法控制 form 生成时机,而前端则必须通过某种方式获取到由后端生成的 验证码 或者 Token 才能保证后端能够校验请求

  • 措施
    1. 后端引入安全模块,可能是写在 WAF 里,也有可能是 Security Sidecar 或者自定义的 API Gateway
    2. 前端请求安全模块,由安全模块生成 csrf_token 并返回,在提交时验证请求
  • 局限:依然无法避免 XSS 攻击

XSRF

CSRFToken 仅仅用于对抗 CSRF 攻击,当网站还同时存在 XSS 漏洞时,这个方案就会变得无效。
因为 XSS 可以模拟客户端浏览器执行任意操作,在 XSS 攻击下,攻击者完全可以请求页面后,读出页面内容里的 Token 值,然后再构造出一个合法的请求。这个过程就被称之为 XSRF

XSS 带来的问题,应该使用 XSS 的防御方案予以解决,否则 CSRFToken 防御就是空中楼阁。安全防御的体系是相辅相成、缺一不可的

迷思

  • Q:以下攻击能成功吗?

    1. 攻击者构建了一个恶意网站
    2. 恶意网站先通过请求上述所说的安全模块获取 csrf_token
    3. 恶意网站构造隐藏的 form 表单并对参数进行伪造
    4. 利用 JS 自动提交(恶意网站是攻击者所有,所以绕过了 XSS 防御策略) form 并携带 csrf_token 给目标网站

    A:不会,因为浏览器的 同源策略 不允许恶意网站获取安全模块所返回的结果(csrf_token),所以攻击在第 2 步时就会失败

参考

白帽子讲Web安全

跨站请求伪造

CSRF - 前后端分离后带来的新问题