पूरे लेख का एक-पंक्ति सार

Django के MultiPartParser में Content-Transfer-Encoding: base64 पार्ट बॉडी जब मुख्य रूप से whitespace से भरी हो, तब होने वाली Pre-Auth CPU exhaustion भेद्यता के कारण लगभग 2.5MB का एक ही अनुरोध सामान्य स्थिति की तुलना में 2,100 गुना से अधिक प्रोसेसिंग समय पैदा कर सकता है (CVE-2026-33033)

सारांश

  • बिना authentication के, डिफ़ॉल्ट सेटिंग वाले सर्वर पर भी इसे trigger किया जा सकता है
    • क्योंकि CSRF middleware view में प्रवेश से पहले request.POST को access करता है, जिससे MultiPartParser अपने-आप चल जाता है; इसलिए authenticated endpoint पर भी CSRF verification चरण में ही कई सेकंड लग सकते हैं
  • एक 20MB अनुरोध अकेले एक worker को लगभग 1 मिनट तक व्यस्त रख सकता है
    • अगर सामान्य gunicorn सेटअप में 4~16 worker चल रहे हों, तो एक साथ कुछ दर्जन अनुरोधों से ही सर्वर व्यावहारिक रूप से ठप हो सकता है
  • Django, multipart/form-data अनुरोधों को MultiPartParser से प्रोसेस करता है, और CSRF middleware view में प्रवेश से पहले request.POST को access करता है, इसलिए बिना authentication के भी यह parser हमेशा चल जाता है
  • भेद्यता का मूल कारण तीन लेयर के गुणा होने वाली संरचना है
    • (Layer 1) base64 alignment while-loop: chunk से whitespace हटाने पर remaining != 0 स्थिति बनी रहती है, इसलिए field_stream.read(1) बाद की पूरी stream पर बार-बार कॉल होता रहता है
    • (Layer 2) LazyStream.read(1) की छिपी हुई O(C) लागत: read(1) को एक बार कॉल करने पर अंदर से ~64KB buffer पूरा निकाल लिया जाता है और फिर 65,535 bytes को unget() के ज़रिए वापस धकेला जाता है; यह पैटर्न बार-बार दोहरता है
    • (Layer 3) unget() की O(C) bytes concatenation: bytes + self._leftover वाला नया object हर बार बनाया जाता है
  • सिर्फ एक 2.5MB अनुरोध अंदरूनी तौर पर लगभग 86GB memory copy उत्पन्न करता है, और M2 के आधार पर लगभग 5.3 सेकंड तक एक worker को पूरी तरह व्यस्त रखता है। 20MB पर लगभग 1 मिनट लगता है
  • unget() के अंदर sanity check कोड (_update_unget_history) पहले से मौजूद था, लेकिन इस हमले में unget() का आकार हर कॉल पर 1-1 करके घटने वाला monotonic decreasing pattern दिखाता है, इसलिए detection condition (number_equal > 40) कभी पूरी नहीं होती
  • Django टीम के patch का मुख्य बिंदु read(4 - remaining)read(self._chunk_size) है, यानी 1~3 bytes पढ़ने के बजाय एक बार में 64KB पढ़ा जाता है। इससे read कॉल 25 लाख बार से घटकर लगभग 40 बार रह जाती हैं
  • Nginx का client_max_body_size डिफ़ॉल्ट मान 1MB है, लेकिन file upload endpoint में इसे अक्सर ढीला किया जाता है, और Apache httpd का LimitRequestBody डिफ़ॉल्ट मान 1GB है; इसलिए केवल proxy पर निर्भर रहकर सुरक्षा की गारंटी नहीं दी जा सकती
  • यह भेद्यता Claude Code + Codex की मदद से खोजी गई, और यह बात प्रभावशाली है कि लगभग 20 साल से परिष्कृत होते आए framework में भी Pre-Auth DoS बना रहा

अभी कोई टिप्पणी नहीं है.

अभी कोई टिप्पणी नहीं है.