Django MultiPartParser Base64 Whitespace CPU Amplification DoS — CVE-2026-33033
by ch4n3-yoon · 2026-07-05
- Severity
- Medium
- CVE
- CVE-2026-33033
- Category
- web
- Affected product
- Django (django.http.multipartparser.MultiPartParser)
- Affected versions
- Django <= 6.0.3, <= 5.2.11, 5.1.x, <= 5.0.14, <= 4.2.28 (fixed in 6.0.4, 5.2.12, 5.1.16, 5.0.15, 4.2.29)
- Disclosed
- 2026-07-05
- Patch status
- patched
Archive entry
intelseclab/poc-archiveMetadata
| Field | Value |
|---|---|
| Date Added | 2026-07-05 |
| Last Updated | 2026-04 |
| Author / Researcher | ch4n3-yoon |
| CVE / Advisory | CVE-2026-33033 |
| Category | web |
| Severity | Medium |
| CVSS Score | Not specified in source (Django advisory rates it “Moderate”) |
| Status | PoC |
| Tags | django, dos, multipart, base64, cpu-amplification, python, file-upload |
| Related | N/A |
Affected Target
| Field | Value |
|---|---|
| Software / System | Django (django.http.multipartparser.MultiPartParser) |
| Versions Affected | Django <= 6.0.3, <= 5.2.11, 5.1.x, <= 5.0.14, <= 4.2.28 (fixed in 6.0.4, 5.2.12, 5.1.16, 5.0.15, 4.2.29) |
| Language / Platform | Python exploit script against a Django (WSGI/uWSGI) server |
| Authentication Required | No |
| Network Access Required | Yes |
Summary
Django’s multipart form parser has a special path for file parts declared with Content-Transfer-Encoding: base64. When the stripped chunk length isn’t a multiple of 4, the parser calls field_stream.read(1) in a loop to pull additional bytes for alignment. If the uploaded body is mostly whitespace, every whitespace byte strips to nothing, so the loop never terminates naturally and calls read(1) once per whitespace byte. Because each single-byte read internally triggers a costly “unget” operation that copies an entire ~64KB leftover buffer, this turns a small number of whitespace bytes into an enormous amount of memory-copy work — the PoC’s README documents roughly a 2,100x CPU amplification for a 2.5MB request. A built-in anti-abuse check that flags repeated identical unget sizes is bypassed because the unget sizes here monotonically decrease, so every value is unique and the check never triggers. Because Django’s CSRF middleware accesses request.POST before any view executes, even endpoints that would ultimately reject the request pay the full parsing cost.
Vulnerability Details
Root Cause
MultiPartParser’s base64 alignment logic reads one byte at a time (field_stream.read(1)) when the decoded chunk length isn’t 4-byte aligned; each such read costs O(C) time due to the underlying LazyStream unget mechanism copying its ~64KB leftover buffer, turning a linear amount of attacker-controlled whitespace into quadratic CPU work.
Attack Vector
- Build a
multipart/form-databody with one file part usingContent-Transfer-Encoding: base64. - Start the part’s content with a few non-whitespace base64 characters (e.g.
AAA) so the decoded length isn’t 4-byte aligned. - Fill the remainder of the part (megabytes) with whitespace bytes, which all strip to empty and never restore alignment.
- Send the request to any endpoint that triggers multipart parsing (including ones that will reject it, since CSRF middleware parses
request.POSTfirst). - The parser loops calling
read(1)per whitespace byte, each incurring an expensive buffer copy, tying up the worker for seconds per request.
Impact
A single unauthenticated, small (a few MB) HTTP request can occupy a Django worker for multiple seconds; a handful of concurrent requests can exhaust an application’s worker pool, causing a denial of service.
Environment / Lab Setup
Target: Django <= 6.0.3 (or other affected branch) app, e.g. via `python manage.py runserver` or uWSGI
Attacker: Python 3 + requests (see requirements.txt)
Proof of Concept
PoC Script
See
exploit.pyand the vulnerable Django app undervictim/in this folder.
| |
The script first sends a benign multipart base64 upload as a timing baseline, then sends malicious base64+whitespace uploads over several rounds, measuring and reporting the CPU-time amplification factor.
Detection & Indicators of Compromise
POST /upload HTTP/1.1 Content-Type: multipart/form-data; ... # request takes several seconds to complete
Signs of compromise:
- Multipart requests with
Content-Transfer-Encoding: base64and abnormally large runs of whitespace bytes - Sudden worker/thread saturation or request queue backlog correlated with small-to-moderate sized uploads
- Elevated CPU usage on app servers with no corresponding increase in legitimate traffic volume
Remediation
| Action | Detail |
|---|---|
| Primary fix | Upgrade to Django 6.0.4, 5.2.12, 5.1.16, 5.0.15, or 4.2.29, which read in bulk (self._chunk_size) instead of one byte at a time during base64 alignment |
| Interim mitigation | Enforce strict request body size limits on multipart uploads and rate-limit/monitor endpoints that accept file uploads until patched |
References
Notes
Mirrored from https://github.com/ch4n3-yoon/CVE-2026-33033-PoC on 2026-07-05.
| |