피드로 돌아가기
단 20MB HTTP 패킷으로 Django 서버를 1분간 먹통 만드는 취약점이 공개되었습니다 (CVE-2026-33033)
GeekNewsGeekNews
Security

단 20MB HTTP 패킷으로 Django 서버를 1분간 먹통 만드는 취약점이 공개되었습니다 (CVE-2026-33033)

2.5MB 요청으로 2,100배 지연 유발하는 Pre-Auth CPU Exhaustion 취약점

nuremberg2026년 4월 14일2advanced

Context

Django MultiPartParser의 base64 처리 로직 중 공백 위주의 입력값 처리 시 발생하는 비효율적 스트림 읽기 구조. CSRF 미들웨어가 view 진입 전 request.POST에 접근하며 MultiPartParser를 자동 실행함에 따라 인증 없이도 공격 가능한 설계적 결함 존재.

Technical Solution

  • base64 정렬 while-loop 내 공백 제거 시 remaining != 0 상태가 지속되어 field_stream.read(1)의 반복 호출 유발
  • LazyStream.read(1) 호출 시마다 64KB 버퍼를 추출한 후 65,535바이트를 unget()으로 재삽입하는 O(C) 비용 발생
  • unget() 과정에서 bytes concatenation을 통한 새 객체 생성으로 인해 CPU 및 메모리 복사 부하 가중
  • 단조감소 패턴의 unget() 사이즈로 인해 기존 sanity check 로직인 _update_unget_history 탐지 회피
  • read(4 - remaining) 방식에서 read(self._chunk_size) 방식으로 변경하여 1~3바이트 단위 읽기를 64KB 단위 읽기로 최적화

Impact

  • 2.5MB 요청 하나가 약 86GB 메모리 복사를 유발하며 M2 기준 worker 1개를 5.3초간 점유
  • 20MB 요청 시 단일 worker를 약 1분간 점유하여 gunicorn 설정 환경에서 동시 수십 개 요청만으로 서버 마비
  • 패치 적용 후 read 호출 횟수를 250만 번에서 약 40번으로 획기적으로 감소

Key Takeaway

스트림 처리 시 작은 단위의 반복적 읽기(read(1))와 버퍼 재삽입(unget)의 조합이 최악의 케이스에서 지수함수적 비용을 초래할 수 있다는 설계적 교훈.


1. 외부 입력 기반의 Stream Parser 설계 시 read() 호출 횟수와 버퍼 크기의 상관관계 검토

2. 불필요한 Bytes Concatenation이 발생하는 루프 구간의 메모리 복사 비용 분석

3. Pre-Auth 단계에서 실행되는 미들웨어의 리소스 소비 가능성 점검

4. Proxy 서버의 body size 제한 외에 애플리케이션 레벨의 입력값 검증 로직 강화

원문 읽기