CSRF防御方案调研结果

场景模拟

用户在登录B站的前提下,点击了黑客送给它的A站的链接。A站的页面中包含一个对B站资源的请求,于是用户浏览器向A站发起请求,并且附带上B站发的cookie,这就达到了冒充用户身份实现某种操作的攻击意图。

CSRF防御方案调研结果_第1张图片
类型:GET型、POST型、链接型

防御办法:

阻止不可信外域的访问

  1. 检验OriginReferrer这两个HTTP Header

在大多数情况下,浏览器在发送请求时会携带它们,并且js无法修改它们。但在少数情况下, 浏览器不会发送它们:
Origin值为发起请求的页面的URL地址(不含path即query);但IE11不会在跨站请求中加上它,另外,302重定向时所有浏览器也不会加它。
Referrer 在ajax、图片和script请求中值是HTTP请求的来源地址,在页面跳转的情况下,是进入最终页面的前一个跳转页面的地址;但在某些情况下,HTTP请求也不含Referrer,如HTTPS转HTTP页面,或使用含有漏洞/可修改HTTP Header的浏览器,或使用IE6/7的部分js API。
W3C 新修的草案里为Referrer Policy提供了5种值:

策略名称 策略值(新) 对应旧值
No Referrer no-referrer never
No Referrer When Downgrade no-referrer-when-downgrade default
Origin Only (same or strict) origin origin
Origin When Cross Origin (strict) origin-when-crossorigin
Unsafe URL unsafe-url always

Referrer可在三个地方设置:

思考: 借助这俩Header验证的本质是依赖第三方(浏览器)的安全,但在某些特殊情况下(如用户使用老旧浏览器、多级重定向跳转等),Referrer的值不足以区分正常请求和恶意请求,此时需要其他验证手段辅助;另外,如果浏览器确定可信,那也可以引入HTTP2.0新加的Sec-Fetch头,它可以提供更加细致的请求描述(请求对象、请求类型、请求模式) ,使用它们做验证依据可靠性更高。P.S.当然,这么做的前提是HTTP2.0普及,目前还需时日。

  1. 设置Cookie为Samesite属性

google的草案里为cookie添加了Samesite字段,它有两个值StrictLax,它是两种级别的同源限制;以链接跳转为例,当登录Cookie的该属性设为Strict时,当你从百度搜索页跳到淘宝页,将不会自动登录,因为cookie没被发送;当值为Lax时,在上面的情形可以自动登录,因为cookie可以发送。但对于在B页面发起A域资源的请求,二者都不会携带该条Cookie。
在防范CSRF来说,该草案很够用,但也存在一些弊端:

  • 当设置为Strict时,子域名跳转或打开新标签重进网站,都不会携带Cookie。这样的用户体验很差。
  • 致命问题:不支持子域,如在top.a.com下无法使用domain字段为a.com的Cookie,这在有多个子域名时会很麻烦。

本域凭证校验(CSRF Token)

方式一(token备份在session中):

服务器在用户cookie的有效周期内,为它生成一个token(通常是:hash( salt + timestamp )),并将备份存入session。如果是后端控制页面的情况,则可以在用户请求页面时直接将token放入表单发给用户,在用户提交表单时进行验证;在后端不可修改页面HTML的情况,可以借助cookie、响应头等媒介,让前端存储下token,在提交表单或ajax请求时动态添加一下。在请求到达服务器后,再和session中的token比较。

方式二(token备份在cookie中):

服务器在生成token后直接并将备份写入cookie发给前端,前端在ajax或提交表单时提取出token(因为在CSRF攻击中黑客无法读取cookie的值)放在请求中(URL、请求头、body),由于cookie也会一并发送给后端,后端在校验时比较二者是否相同。

说明: 方式二的实施成本比方式一要低,但在大型网站上却使用的不多。原因主要在于,为了让不同的子域名能够获取到种了token的cookie,该cookie的域必须设置成较高级别的(比如二级域名),这会给大型网站暴露出更多风险。因为每个子域名下的js都可以修改这条cookie,倘若某个子域名存在XSS漏洞(大型网站的子域名好多的,有些使用老旧的技术开发),则黑客可以借此突破当前域名下的CSRF防御。

思考: 一般来说,token的生命周期和表单一致,当一个表单提交后该条token就被消耗掉,我们可以从token池中获取并分配下一条token,这种设计常用于方式一中;当使用方式二时,因为服务端没有留存生成token的时间戳,不好校验它的有效性,所以可将token的生命周期延长为用户登录cookie的有效期。或者放弃使用哈希运算,换成加解密运算的方式,即可完成有效性的校验(服务端接收到请求的时间 - 解密后获得的生成时间),Django框架正是采用了这种方式(它自写了一个简单的加解密算法)。但因为将哈希运算换成了加解密运算,无疑增加了服务器CPU的开销。

设计案例

Spring Security

Spring Security提供了3个CsrfTokenRepository,其中HttpSessionCsrfToken和CookieCsrfToken顾明思议,用于方式一、方式二的场景,LazyCsrfToken提供了程序员自写方法的接口。前两种token的生成方式十分简单:

   private String createNewToken() {
   	return UUID.randomUUID().toString();
   }

验证逻辑在org.springframework.security.web.csrf.CsrfFilter#doFilterInternal中,流程如下:
CSRF防御方案调研结果_第2张图片

Django

如下图,为Django中的CSRF验证逻辑:

CSRF防御方案调研结果_第3张图片

其中,csrftoken从请求的Cookie中获取,csrfmiddlewaretoken从POST表单或请求头中获取,通过判断二者解密后的secret值来决定是否为CSRF攻击。
在这里插入图片描述
在这里插入图片描述

参考阅读:

  1. https://zhuanlan.zhihu.com/p/46592479
  2. https://www.jianshu.com/p/eaf4a57bbca7

你可能感兴趣的:(WEB安全入门,开发,安全开发)