Cookie-Form型CSRF防御机制的不足与反思
今天看了 https://hackerone.com/reports/26647 有感。这个漏洞很漂亮,另外让我联想到很多之前自己挖过的漏洞和写过的程序,有感而发。
Django已经在昨天修复了该漏洞 https://www.djangoproject.com/weblog/2016/sep/26/security-releases/
0x01 借助Session防御CSRF漏洞
我最早接触Web安全的时候(大概大一暑假),写过一个站点。当时边看道哥的《白帽子讲Web安全》,边在写站点的过程中熟悉每种漏洞,并编写尽量安全的代码。
初识CSRF漏洞的我使用了一种中规中矩的方法来防御CSRF漏洞:
- 后端生成随机字符串Token,储存在SESSION中。
- 每当有表单时,从SESSION中取出Token,写入一个隐藏框中,放在表单最底部。
- 接受POST数据时,先验证_POST['token'] === _SESSION['token'],再执行其他逻辑。
这是一个很标准的CSRF防御方法,也很难找出其破绽。但这个方法有个致命的弱点:Session。原因有二:
- 所有用户,不论是否会提交表单,不论是否会用到这些功能,都将生成一个Session,这将是很大的资源浪费。举个例子,Sec-News的Session储存在redis里,每天会生成数千到数万的Session,自动化脚本每天夜里会遍历并清理没有使用的Session,以避免过度消耗资源。
- 除了PHP的很多开发语言中,Session是可选项,很多网站根本没有Server Session。开发框架不能强迫开发者使用Session,所以在设计防御机制的时候也不会使用Session。
所以,像Django之类的Python框架,会选择基于Cookie的CSRF防御方式。
0x02 Cookie-Form型CSRF防御机制
顾名思义,Cookie-Form型CSRF防御机制,是和Cookie和Form有关。它确切的名字我还不太清楚,暂且这样称之。
Sec-News曾经分享过一篇文章 《前后端分离架构下的CSRF防御机制》,当时 @neargle 就提出过疑问。
其实借助Cookie来防御CSRF的方法是一个通用的防御方法,单纯应对CSRF漏洞是绝对可行的。该文章的解决方案是,后端生成一个token和一个散列,均储存于Cookie中,在提交表单时将token附带在表单中提交给后端,后端即可根据表单中的token和cookie中的散列来验证是否存在CSRF攻击。
实际上散列这一步是没有必要的,后端只需要生成好一个随机token储存于Cookie中,前端提交表单时提交该Cookie基本就万无一失了。
我第一次接触这种防御方法是在学习CodeIgniter的过程中(这里提一下,CI框架默认的CSRF防御方法就是本文说的这个方法),当时认为这种防御方法很不可理喻。因为Cookie是可以控制的,如果攻击者将Cookie控制地和Form中相同,不就可以绕过这个防御了么?
但是细想来,立马打脸了:攻击者如何修改受害者的Cookie?
既然是CSRF漏洞,也就不能控制目标域的脚本,当然就无法获取Cookie(如果能获取Cookie就不叫CSRF漏洞了)。
总结一下,基于Cookie的CSRF防御方法,较基于Session的方法有如下优点:
- 无需使用Session,适用面更广,适合“Secure By Default“原则。
- Token储存于客户端中,不会给服务器带来压力。
- 没有其他漏洞的情况下,黑客无法接触Cookie,所以保证了Token的机密性,也就可以防御CSRF漏洞。
0x03 破解Cookie-Form型CSRF防御
那么,基于Cookie的CSRF防御机制,有什么弊端?
弊端也很明显:一旦有其他漏洞(即使是看起来很鸡肋的漏洞)的存在,很容易就能破坏这种防御手法。
我曾经分享过知乎的一个漏洞《知乎某处XSS+刷粉超详细漏洞技术分析》,很经典的一个案例。攻击者获得了一个”看似十分鸡肋“的XSS漏洞(domain是子域名,而且关键cookie都有httponly),无法做一些正常XSS漏洞可以做的攻击,但却可以写入Cookie。
攻击者通过写入一个新的"CSRF_TOKEN",将原有的无法获取的Token覆盖掉,就成功绕过了0x02中描述的防御手法。
这种绕过方法的核心就是:利用其它漏洞写入Cookie,覆盖原有Cookie,来达到Form[token]===Cookie[token]
的目的。
那么,寻找此类绕过漏洞的核心就是寻找注入新Cookie的方法,看过一些案例,我归纳出来几种:
- 某些单纯而不做作的前端编写的页面可以写入Cookie
- 鸡肋XSS漏洞
- 利用CRLF漏洞注入Cookie
- 利用畸形字符使后端解析Cookie出错,注入Cookie
第一种,很久以前我在QQ空间的不止一处看到过,某些页面从location.search中获取参数并设置为Cookie。但找这种地方比较难,没有什么特别的方法,可遇而不可求。
第二种,就看知乎那个案例吧。
第三种,@/fd 曾用一个Twitter的overflow漏洞演示了Cookie的注入:《Overflow Trilogy》。这个漏洞原本是可以用来绕过Twitter的CSRF检测的,不过后来Twitter把CSRF防御方式从0x02换成0x01了,有点可惜:
0x04 Web Server解析Cookie的特性
第四种,就是利用Google Analytics来绕过Django的CSRF防御方式。这个方法其实作者早在 2015年 就已经提出来了(当时是作为Twitter的一个漏洞提交的)。
Google Analytics会将网站的path写入Cookie中,而没有进行编码,导致攻击者可以输入一些“特殊”的字符。
当时使用的是逗号“,”,有些Web Server在解析Cookie时,逗号也会成为分隔符。这样就导致了Cookie: param1=1111,param2=2222;
这样的Cookie被解析成Cookie[param1]=1111
和Cookie[param2]=2222
,成功注入了一个新Cookie,Param2 。
这次Django的Cookie注入也类似。其实原因来自于Python原生的cookielib库,在分割Cookie头的时候,将“]”也作为了分隔符,导致Cookie: param1=value1]param2=value2
被解析成Cookie[param1]=value1
和Cookie[param2]=value2
,成功注入了一个新Cookie,Param2。
关于畸形Cookie注入的一些姿势,可以看看 https://habrahabr.ru/post/272187/ 。
成功注入Cookie后,后续“CSRF攻击”流程就和0x03中讲的一样了,不再赘述。思路很不错,所以写文章说说,和大家分享一下自己的一些看法。
- 妹子你真萌:一次心惊肉跳的服务器误删文件的恢复过程
- Go语言使用sort包对任意类型元素的集合进行排序的方法
- Oracle 12c ASM专题|Flex Diskgroup相关概念
- 移动搜索SEO分享:利用Meta声明来做百度开放适配
- 百度分享无法抓取图片及摘要的折中解决办法
- Golang 持久化
- Golang context 包入门
- 动手实现一个JSON验证器(上)
- Go语言实现冒泡和快速排序
- 利用Meta申明来做百度、谷歌、雅虎、微软等搜索的开放适配
- 【Oracle 12c ASM专题】——我的第一个Flex Diskgroup
- 编写一个go gRPC的服务
- GO语言使用gopsutil包进行机器信息采集
- redigo 连接池代码分析
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Spring源码学习笔记(3)——容器的功能扩展
- Dart数组的常规操作
- 机器学习之logistic回归算法与代码实现原理
- spring整合中application.xml配置
- RocketMQ详解(10)——Consumer详解
- RocketMQ详解(12)——RocketMQ的重试机制
- RocketMQ详解(13)——RocketMQ的消息模式
- 深度学习之卷积神经网络(CNN)详解与代码实现(一)
- Dart自定义类、构造函数
- Dart类中static静态成员及访问
- Dart中..级联操作
- Dart类的继承
- Dart导入自定义库,系统内置库,第三方库
- 国密SSL协议之C语言编程
- 深度学习之卷积神经网络(CNN)详解与代码实现(二)