Lỗ hổng có thể khiến máy chủ Django treo 1 phút chỉ với gói HTTP 20MB đã được công bố (CVE-2026-33033)
(new-blog.ch4n3.kr)Tóm tắt một dòng toàn bộ
Một lỗ hổng làm cạn kiệt CPU trước xác thực trong MultiPartParser của Django xảy ra khi phần thân của part có Content-Transfer-Encoding: base64 chủ yếu là khoảng trắng, khiến chỉ một request khoảng 2.5MB cũng tạo ra thời gian xử lý cao hơn hơn 2.100 lần so với bình thường (CVE-2026-33033)
Tóm tắt
- Có thể bị kích hoạt không cần xác thực, ngay cả trên máy chủ cấu hình mặc định
- Vì middleware CSRF truy cập
request.POSTtrước khi vào view và tự động chạy MultiPartParser, nên ngay cả endpoint cần xác thực cũng đã mất vài giây ở bước kiểm tra CSRF
- Vì middleware CSRF truy cập
- Chỉ một request 20MB có thể chiếm một worker trong khoảng 1 phút
- Với cấu hình gunicorn phổ biến chạy 4~16 worker, chỉ cần vài chục request đồng thời là máy chủ gần như bị tê liệt
- Django xử lý các request
multipart/form-databằngMultiPartParser, và vì middleware CSRF truy cậprequest.POSTtrước khi vào view nên parser này luôn chạy ngay cả khi không xác thực - Điểm cốt lõi của lỗ hổng là cấu trúc trong đó ba lớp nhân với nhau
- (Layer 1) vòng lặp while căn chỉnh base64: khi bỏ khoảng trắng khỏi chunk, trạng thái
remaining != 0vẫn được giữ nênfield_stream.read(1)tiếp tục bị gọi lặp lại trên toàn bộ phần còn lại của stream - (Layer 2) chi phí O(C) ẩn của
LazyStream.read(1): mỗi lần gọiread(1), nội bộ lại lấy ra toàn bộ buffer khoảng ~64KB rồi đẩy ngược 65.535 byte trở lại bằngunget()theo mẫu lặp đi lặp lại - (Layer 3) phép nối
bytesO(C) củaunget(): mỗi lần đều tạo đối tượng mới quabytes + self._leftover
- (Layer 1) vòng lặp while căn chỉnh base64: khi bỏ khoảng trắng khỏi chunk, trạng thái
- Chỉ một request 2.5MB nhưng bên trong gây ra khoảng 86GB sao chép bộ nhớ, và trên M2 sẽ chiếm trọn một worker khoảng 5,3 giây. Với 20MB thì mất khoảng 1 phút
- Bên trong
unget()vốn đã có mã sanity check (_update_unget_history), nhưng đợt tấn công này có mẫu giảm đơn điệu khi kích thướcunget()giảm 1 ở mỗi lần gọi, nên điều kiện phát hiện (number_equal > 40) sẽ không bao giờ được thỏa mãn - Trọng tâm bản vá của nhóm Django là đổi
read(4 - remaining)→read(self._chunk_size), tức thay vì đọc từng 1~3 byte thì đọc 64KB mỗi lần. Nhờ đó số lần gọi read giảm từ 2,5 triệu xuống còn khoảng 40 lần - Giá trị mặc định của Nginx
client_max_body_sizelà 1MB, nhưng ở các endpoint tải tệp lên thường hay được nới lỏng, còn giá trị mặc địnhLimitRequestBodycủa Apache httpd là 1GB nên chỉ dựa vào proxy không đảm bảo phòng thủ được - Đây là lỗ hổng được phát hiện nhờ Claude Code + Codex, và điểm đáng chú ý là một lỗ hổng DoS trước xác thực vẫn còn tồn tại trong một framework đã được mài giũa gần 20 năm
4 bình luận
Đi thôi nàoaaa
Có ai tự tay thử cái này chưa?
PoC được tạo cho mục đích demo đã được đăng trên GitHub.
https://github.com/ch4n3-yoon/CVE-2026-33033-PoC
Tuyệt vời.