11 điểm bởi nuremberg 8 ngày trước | 4 bình luận | Chia sẻ qua WhatsApp

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.POST trướ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
  • 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-data bằng MultiPartParser, và vì middleware CSRF truy cập request.POST trướ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 != 0 vẫn được giữ nên field_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ọi read(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ằng unget() theo mẫu lặp đi lặp lại
    • (Layer 3) phép nối bytes O(C) của unget(): mỗi lần đều tạo đối tượng mới qua bytes + self._leftover
  • 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ước unget() 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_size là 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 định LimitRequestBody củ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

 
kalista22 6 ngày trước

Đi thôi nàoaaa

 
tangokorea 7 ngày trước

Có ai tự tay thử cái này chưa?

 
nuremberg 7 ngày trước

PoC được tạo cho mục đích demo đã được đăng trên GitHub.

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

 
tangokorea 7 ngày trước

Tuyệt vời.