前后端分离下如何防止 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 请求做敏感操作
局限
:这种方法可以避免img
、script
、iframe
等带src
属性的标签发起CSRF
攻击。但是无法防止攻击者构建POST
请求等发起CSRF
攻击
使用 Anti-CSRF-Token
CSRF
为什么能够攻击成功?其本质原因是重要操作的所有参数都是可以被攻击者猜测到的。攻击者只有预测出 URL 的所有参数与参数值,才能成功地构造一个伪造的请求;反之,攻击者将无法攻击成功。
措施
:所以我们可以生成一个足够随机
的参数Token
放入该次请求,保证该次请求无法被攻击者成功伪造。核心思路:使用一个由服务器派发的
Token
,在前端进行状态修改时,也同时提交这个Token
(往往会放在html form
的input
中,或者ajax header
中),这时候服务端验证该Token
是否是之前所生成的,以此来判断这个请求是否被允许局限
:- 依赖于
Token
足够随机 - 无论是
Token
放在哪里,只要JS
能读取到,都会面临XSS
风险
- 依赖于
在常见的 Web 后端框架比如 Rails
、Django
中都自带实现了 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
才能保证后端能够校验请求
措施
:- 后端引入安全模块,可能是写在
WAF
里,也有可能是Security Sidecar
或者自定义的API Gateway
- 前端请求安全模块,由安全模块生成
csrf_token
并返回,在提交时验证请求
- 后端引入安全模块,可能是写在
局限
:依然无法避免XSS
攻击
XSRF
CSRF
的 Token
仅仅用于对抗 CSRF
攻击,当网站还同时存在 XSS
漏洞时,这个方案就会变得无效。
因为 XSS
可以模拟客户端浏览器执行任意操作,在 XSS
攻击下,攻击者完全可以请求页面后,读出页面内容里的 Token
值,然后再构造出一个合法的请求。这个过程就被称之为 XSRF
XSS
带来的问题,应该使用 XSS
的防御方案予以解决,否则 CSRF
的 Token
防御就是空中楼阁。安全防御的体系是相辅相成、缺一不可的
迷思
Q:以下攻击能成功吗?
- 攻击者构建了一个恶意网站
- 恶意网站先通过请求上述所说的安全模块获取
csrf_token
- 恶意网站构造隐藏的
form
表单并对参数进行伪造 - 利用 JS 自动提交(恶意网站是攻击者所有,所以绕过了
XSS
防御策略)form
并携带csrf_token
给目标网站
A:不会,因为浏览器的 同源策略 不允许恶意网站获取安全模块所返回的结果(
csrf_token
),所以攻击在第 2 步时就会失败