RSS Feed
更好更安全的互联网

Django CSRF Bypass 漏洞分析(CVE-2016-7401)

2016-10-08

Author: p0wd3r (知道创宇404安全实验室) Date: 2016-09-28

0x00 漏洞概述


1.漏洞简介

Django是一个由Python写成的开源Web应用框架。在两年前有研究人员在hackerone上提交了一个利用Google Analytics来绕过Django的CSRF防护机制的漏洞(CSRF protection bypass on any Django powered site via Google Analytics),通过该漏洞,当一个网站使用了Django作为Web框架并且设置了Django的CSRF防护机制,同时又使用了Google Analytics的时候,攻击者可以构造请求来对CSRF防护机制进行绕过。

2.漏洞影响

网站满足以下三个条件的情况下攻击者可以绕过Django的CSRF防护机制:

  • 使用Google Analytics来做数据统计
  • 使用Django作为Web框架
  • 使用基于Cookie的CSRF防护机制(Cookie中的某个值和请求中的某个值必须相等)

3.影响版本

Django 1.9.x < 1.9.10

Django 1.8.x < 1.8.15

Python2 < 2.7.9

Python3 < 3.2.7

0x01 漏洞复现

1. 环境搭建

project/app/views.py:

project/project/urls.py:

project/app/templates/check.html

project/app/templates/ga.html(放置Goolge Analytics脚本的页面):

2.漏洞分析

我们先来看这样一个场景:

python276-bug

当python内置的Cookie.SimpleCookie()解析a=hello]b=world这种形式的字符串时会以]作为分隔,最后取得a=hellob=world这两个cookie,那么为什么会这样呢?

我们看一下源码,Ubuntu下/usr/lib/python2.7/Cookie.py第622-663行:

当传入load一个字符串时,调用__ParseString,在__ParseString中有这样一句:match = patt.search(str, i),根据之前定义的pattern来查找字符串中符合pattern的cookie,_CookiePattern在529-545行:

在这里我们看到]并没有在_LegalCharsPatt中,由于代码中使用的是search函数,所以在匹配a=hello后碰到]会跳过这个字符然后再匹配b=world。因此正是因为使用search函数来匹配,所以当a=hello后面是任意一个不在_LegalCharsPatt中的字符(例如[\]\x09\x0b\x0c)都会达到同样的效果:

这个漏洞也正是整个Bypass的核心所在。

我们再来看Django(1.9.9)中对cookie的解析,在http/cookie.py 中第91-106行:

根据动态调试发现这里的SimpleCookie也就是我们上面所说的存在漏洞的对象,从而可以确定Django中对cookie的处理也是存在漏洞的。

我们再来看看Django的CSRF防护机制,默认CSRF防护中间件是开启的,我们访问http://127.0.0.1:8000/check/,点击Check然后抓包:

200

可以看到csrftokencsrfmiddlewaretoken的值是相同的,其中csrfmiddlewaretoken的值如图:csrf-form

也就是Django对check.html中的{% csrf_token %}所赋的值。

我们再改下包,使csrftokencsrfmiddlewaretoken不相等,这回服务器就会返回403:

403-1

我们再把两个值都改成另外一个值看看:

200-2

依然成功。

所以Django对于CSRF的防护就是判断cookie中的csrftoken和提交的csrfmiddlewaretoken的值是否相等。

那么如果想Bypass这个防护机制,就是要想办法设置受害者的cookie中的csrftoken值为攻击者构造的csrdmiddlewaretoken的值。

如何设置受害者cookie呢?Google Analytics帮了我们这个忙,它为了追踪用户,会在用户浏览时添加如下cookie:

其中[HOST][PATH]是由Referer确定的,也就是说当Referer: http://x.com/helloworld时,cookie如下:

由于Referer是我们可以控制的,所以也就有了设置受害者cookie的可能,但是如何设置csrftoken的值呢?

这就用到了我们上面说的Django处理cookie的漏洞,当我们设置Referer为http://x.com/hello]csrftoken=world,GA设置的cookie如下:

当Django解析cookie时就会触发上面说的漏洞,将cookie中csrftoken的值赋为world

实际操作一下,为了方便路由我们在另一个IP上再开一个DjangoApp作为中转,其中各文件如下:

urls.py:

views.py:

route.html:

开启中转App:

构造一个攻击页面:

当我们点击Click me,会先打开一个窗口,再回到原窗口,就可以看到保护机制已经绕过:

success

再访问一下http://127.0.0.1:8000/check/,可以看到此时cookie中的csrftoken和form中的csrfmiddlewaretoken都已被设置成boom,证明漏洞成功触发:

cookie-boomform-boom

攻击流程如下:

bypass-flow

3.补丁分析

Python

可以看到这个漏洞在根本上是原生Python的漏洞,首先看最早在2.7.9中的patch:

patch279

search改成了match函数,所以再遇到非法符号匹配会停止。

再看该文件在2.7.10中的patch:

patch2710

这里将[\]设置为了合法的value中的字符,也就是

同样Python3在3.2.7和3.3.6中也做了相应patch:

patch336

patch327

不过尽管上面对[\]做了限制,但是由于pattern最后\s*的存在,所以在以下情况下仍然存在漏洞:

这些情况在最新的Python中并没有被修复,不过在实际情况中由于浏览器和脚本的原因,这些字符不一定会保存原样发送给Python处理,所以在利用上还要根据场景来分析。

Django

Django在1.9.10和1.8.15中做了相同的patch:

django1910

它放弃了使用Python内置库来处理cookie,而是自己根据;分割再取值,使特殊符号不再起作用。

0x02 修复方案


升级Python

升级Django

0x03 参考


作者:kk | Categories:安全研究技术分享 | Tags:

发表评论