前后端分离下如何防止 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 步时就会失败