11 ポイント 投稿者 nuremberg 15 일 전 | 4件のコメント | WhatsAppで共有

全体の一行要約

DjangoのMultiPartParserにおいて、Content-Transfer-Encoding: base64のパート本文が空白中心である場合に発生するPre-Auth CPU exhaustion脆弱性で、約2.5MBのリクエスト1件で通常比2,100倍以上の処理時間を引き起こします(CVE-2026-33033)

要約

  • 認証なしで、デフォルト設定のサーバーでもトリガー可能
    • CSRFミドルウェアがviewに入る前にrequest.POSTへアクセスしてMultiPartParserを自動実行するため、認証済みエンドポイントであってもCSRF検証段階ですでに数秒を要します
  • 20MBのリクエスト1件で単一workerを約1分間専有
    • 4〜16個のworkerで運用する一般的なgunicorn設定であれば、同時に数十件のリクエストだけでサーバーは事実上まひします
  • Djangoはmultipart/form-dataリクエストをMultiPartParserで処理し、CSRFミドルウェアがviewに入る前にrequest.POSTへアクセスするため、認証なしでもこのパーサーは常に実行されます
  • 脆弱性の核心は、3つのレイヤーが掛け合わさる構造にあります
    • (Layer 1) base64整列while-loop: チャンクから空白を除去するとremaining != 0の状態が維持され、field_stream.read(1)がその後のストリーム全体に対して繰り返し呼び出されます
    • (Layer 2) LazyStream.read(1)の隠れたO(C)コスト: read(1)を1回呼ぶたびに、内部では約64KBのバッファ全体を取り出してから65,535バイトをunget()で押し戻すパターンが繰り返されます
    • (Layer 3) unget()のO(C) bytes concatenation: bytes + self._leftoverという新しいオブジェクト生成が毎回発生します
  • 2.5MBのリクエスト1件が内部的に約86GBのメモリコピーを誘発し、M2基準で約5.3秒間worker 1つを完全に専有します。20MBでは約1分かかります
  • unget()内部にはすでにsanity checkコード(_update_unget_history)が存在していましたが、今回の攻撃ではunget()サイズが呼び出しごとに1ずつ減少する単調減少パターンのため、検知条件(number_equal > 40)を決して満たしません
  • Djangoチームのパッチの核心は、read(4 - remaining)read(self._chunk_size)で、1〜3バイトずつ読む代わりに一度に64KBずつ読むよう変更した点です。これによりread呼び出しは250万回→約40回へ減少します
  • Nginxのclient_max_body_sizeデフォルト値は1MBですが、ファイルアップロードのエンドポイントでは緩和されることが多く、Apache httpdのLimitRequestBodyデフォルト値は1GBであるため、プロキシだけでは防御が保証されません
  • Claude Code + Codexを活用して発見された脆弱性であり、約20年近く磨かれてきたフレームワークにPre-Auth DoSが残っていた点が印象的です

4件のコメント

 
kalista22 14 일 전

いくぞーーー

 
tangokorea 14 일 전

これ、実際に試してみた人いますか?

 
nuremberg 14 일 전

デモ用に作成されたPoCがGitHubに公開されています。

https://github.com/ch4n3-yoon/CVE-2026-33033-PoC

 
tangokorea 14 일 전

いいねです。