3 पॉइंट द्वारा GN⁺ 2024-04-06 | 1 टिप्पणियां | WhatsApp पर शेयर करें
  • HTTP/2 CONTINUATION Flood HTTP/2 implementation vulnerabilities का एक वर्ग है, जिसमें END_HEADERS के बिना header frames लगातार भेजकर server availability को गिराया जा सकता है
  • attack request पूरी नहीं होती, इसलिए यह HTTP access logs में दर्ज नहीं होती, और root cause समझने के लिए raw traffic bytes का analysis करना पड़ सकता है
  • implementation के अनुसार प्रभाव अलग-अलग हो सकते हैं: CPU exhaustion, multi-connection OOM, single-connection OOM, और disconnect timing bug के कारण crash तक
  • Go, Firefox और Node.js के मामलों में क्रमशः लगातार HPACK decoding, response header size limit की कमी, और CONTINUATION processing के दौरान disconnect तथा memory counter update के टकराव सामने आए
  • Rapid Reset के विपरीत, कई implementations में सिर्फ एक single TCP connection से भी server crash किया जा सकता था, इसलिए HTTP/2 इस्तेमाल करने वाली इंटरनेट की बड़ी संख्या में services प्रभावित हो सकती थीं

HTTP/2 में CONTINUATION frame का उपयोग कैसे होता है

  • HTTP/2, HTTP/1.1 की तरह text lines का आदान-प्रदान नहीं करता, बल्कि binary frames का उपयोग करने वाला protocol है
  • HEADERS frame request और response के HTTP headers भेजता है, और header data HPACK-encoded field block fragment में रखा जाता है
  • HEADERS frame में header और stream के अंत को बताने वाले flags होते हैं
    • END_HEADERS: संकेत कि इस frame में भेजे जाने वाले सभी headers शामिल हैं
    • END_STREAM: संकेत कि request या response body आगे नहीं है
  • frames का एक maximum size होता है, जो connection शुरू होने पर तय होता है; यदि received frame allowed size से बड़ा हो, तो protocol error के कारण connection बंद कर दिया जाता है
  • अगर सभी headers एक HEADERS frame में नहीं आ पाते, तो END_HEADERS के बिना HEADERS frame के बाद CONTINUATION frames आते हैं
    • END_HEADERS के बिना HEADERS frame
    • END_HEADERS के बिना अतिरिक्त CONTINUATION frames
    • अंतिम CONTINUATION frame पर END_HEADERS सेट किया जाता है
  • अंतिम header frame के बाद request data वाला DATA frame आता है या HTTP/2 stream समाप्त हो जाता है

vulnerability का मूल: कभी खत्म न होने वाली header stream

  • अगर client नया HTTP/2 stream शुरू करने के बाद HEADERS और CONTINUATION frames भेजता रहे और कभी END_HEADERS सेट न करे, तो server अनंत header stream को लगातार parse और store करने की कोशिश करता रहेगा
  • HTTP/1.1 servers में आम तौर पर infinite headers को रोकने के लिए दो तंत्र होते हैं
    • header list allowed size से बड़ी होने पर connection बंद करने वाली header size limit
    • request या headers समय पर न आने पर connection बंद करने वाला request/header timeout
  • कई HTTP/2 implementations में ये सुरक्षा या तो थीं ही नहीं या गलत तरह से लागू की गई थीं; इसमें Apache httpd, Envoy, और कई HTTP/2 packages तथा codecs शामिल थे
  • implementation के अनुसार परिणाम चार प्रकार के रहे
    • CPU exhaustion: अतिरिक्त headers पढ़ने और decode करने में CPU usage बढ़ जाता है, जिससे दूसरी requests के responses धीमे या blocked हो जाते हैं
    • multi-connection आधारित OOM: CONTINUATION headers memory में store होते रहते हैं; header list size limit तो होती है, लेकिन header timeout न होने से हर connection memory पकड़े रहता है
    • single-connection आधारित OOM: कुछ implementations memory भरने तक headers पढ़ते रहते हैं, जिससे OS process को terminate कर देता है
    • कुछ ही frames में crash: CONTINUATION stream के बीच connection टूटने पर implementation bug के कारण server crash हो जाता है
  • END_HEADERS न होने पर request ठीक से बंद नहीं होती, इसलिए malicious client की request access logs में दर्ज नहीं होती

Go का मामला: HPACK decoding रुकती नहीं, CPU exhaustion

  • Go, CONTINUATION Flood में CPU exhaustion का प्रमुख उदाहरण है
  • Go implementation http2MetaHeadersFrame abstraction के माध्यम से एक HEADERS frame, 0 या उससे अधिक CONTINUATION frames, और HPACK decoder को एक साथ संभालता है
  • readMetaFrame, header size limit तक पहुंचने या error होने पर SetEmitEnabled(false) को कॉल करके decoded headers का emission रोक देता है
  • लेकिन header emission रुकने के बाद भी HPACK decoder input bytes को decode करता रहता है
  • frame supply loop केवल तब रुकता है जब HeadersEnded() true हो, और यह तभी होता है जब END_HEADERS flag सेट हो
  • अगर attacker END_HEADERS न भेजे, तो readMetaFrame return नहीं करता और attacker के भेजते रहने तक HPACK decoder नए bytes process करता रहता है

OOM के मामले और Firefox client पर प्रभाव

  • OOM उन implementations में होता है जो CONTINUATION frames से बन रही header list के size को सीमित नहीं करतीं
  • जिन implementations में header timeout नहीं था, उनमें सिर्फ एक single HTTP/2 connection से server crash कराया जा सकता था
  • idle timeout होने पर भी यह संभव था कि कई HTTP/2 connections को उनकी प्रति-connection सीमा के करीब RAM पकड़े रहने दिया जाए, और फिर अंतिम CONTINUATION frame को हर कुछ सेकंड में byte-by-byte भेजकर connection जिंदा रखा जाए
  • CONTINUATION Flood केवल server-side नहीं, बल्कि browser जैसे client-side पर भी हो सकता है
  • Mozilla Firefox के fix commit में एक check जोड़ा गया, जिसमें aggregated header size और नए frame size का योग network_http_max_response_header_size() से अधिक होने पर PROTOCOL_ERROR के साथ session error लौटाया जाता है

Node.js का मामला: disconnect के दौरान assertion crash

  • Node.js infinite CONTINUATION frame stream को तो सही तरीके से handle करता था, लेकिन header stream के बीच connection टूटने पर data race हो जाती थी
  • attack code चलाने पर Node.js, Http2Session::~Http2Session() में CHECK_EQ(current_nghttp2_memory_, 0) assertion failure के साथ crash हो जाता था
  • crash, HTTP/2 client द्वारा Node.js server से connection तोड़ने के सटीक timing से जुड़ा था, और assertion Http2Session destructor के भीतर थी
  • Node.js, HTTP/2 connection handling के लिए nghttp2 library को embed करता है
  • current_nghttp2_memory_ nghttp2 के भीतर allocated memory को track करता है, और destructor में session_.reset() के बाद यह verify करता है कि सभी nghttp2 artifacts memory से हट चुके हैं
  • जांच में पता चला कि CONTINUATION frame parsing के दौरान nghttp2 callback और reset() एक साथ चल सकते थे
    • NGHTTP2_IB_EXPECT_CONTINUATION state में CONTINUATION frame आता है
    • state NGHTTP2_IB_READ_HEADER_BLOCK में बदलती है
    • उसके बाद session_after_header_block_received, session_call_on_frame_received, on_frame_recv_callback flow चलता है
    • Node.js के OnFrameReceive और HandleHeadersFrame memory counter update करते हैं
  • जब HandleHeadersFrame और Http2Session::~Http2Session() साथ-साथ चलते हैं, तो current_session_memory_ एक साथ update होता है, current_nghttp2_memory_ negative हो जाता है, और CHECK_EQ fail हो जाता है

2019 की HTTP/2 vulnerabilities से अंतर

  • 2019 में Netflix और Google द्वारा report की गई HTTP/2 vulnerabilities का समूह CERT/CC Vulnerability Note VU#605641 में संकलित है
  • CVE-2019-9516, “0-Length Headers Leak”, ऐसी समस्या है जिसमें कुछ implementations header name और value की length 0 होने पर भी memory allocate करती हैं और session खत्म होने तक उसे बनाए रखती हैं
  • CONTINUATION Flood में empty headers नहीं, बल्कि server द्वारा configured frame size limit तक बहुत सारे random headers भेजे जाते हैं
  • CVE-2019-9518, “Empty Frame Flooding”, empty payload वाले DATA, HEADERS, CONTINUATION, PUSH_PROMISE frames आदि को end-of-stream के बिना भेजकर peer को attack bandwidth की तुलना में disproportionate processing time खर्च करवाता है
  • CONTINUATION Flood empty frames का उपयोग नहीं करता, बल्कि जितने बड़े frames संभव हों उनका उपयोग करके memory घेरता है और decoding process में CPU cycles खर्च करवाता है

Rapid Reset से भी अधिक गंभीर होने की वजह

  • अक्टूबर 2023 में HTTP/2 protocol की zero-day “Rapid Reset” का विवरण सार्वजनिक हुआ, और इसे “अब तक का सबसे बड़ा DDoS attack” कहा गया
  • Rapid Reset, END_STREAM और END_HEADERS set वाले HEADERS frame तथा RST_STREAM frame के संयोजन का उपयोग करता है
  • इस तरीके में rate limiting जैसी standard mitigations नुकसान कम कर सकती हैं, और server administrators inbound requests की बड़ी संख्या को logs में देखकर alert हो सकते हैं
  • CONTINUATION Flood में END_HEADERS नहीं होता, इसलिए एक भी request पूरी नहीं होती, और administrators logs में request देख ही नहीं पाते
  • कई implementations में CONTINUATION Flood सिर्फ एक single TCP connection से server crash कर सकता था, और कुछ मामलों में बहुत कम data से भी यह संभव था
  • Rapid Reset DDoS attacks में इस्तेमाल हुआ था, और अधिकतर मामलों में successful attack के लिए botnet की जरूरत थी

इंटरनेट services पर संभावित प्रभाव

  • Cloudflare Radar के अनुसार HTTP/2 traffic, bots को छोड़कर human HTTP traffic का लगभग 60% है
  • Cloudflare Radar का अनुमान है कि HTTP traffic पूरे इंटरनेट transmission का 70% से अधिक है
  • प्रभावित projects के महत्व और आसान exploitation को देखते हुए, इंटरनेट का बड़ा हिस्सा इस vulnerability के संपर्क में था
  • HTTP सिर्फ websites ही नहीं, बल्कि कई RESTful API में भी उपयोग होता है
  • महत्वपूर्ण enterprise और government APIs तथा websites की availability समस्या से लाखों डॉलर का नुकसान या व्यापक अव्यवस्था हो सकती है
  • अगर इसका exploitation हुआ होता, तो HTTP/2 knowledge के बिना server administrators के लिए debugging बहुत कठिन हो सकती थी
    • malicious HTTP request ठीक से बंद नहीं होती
    • server access logs में request दिखाई नहीं देती
    • अधिकांश HTTP/2 servers में advanced frame analysis सुविधाएं नहीं होतीं
    • manually raw connection data का analysis करना पड़ सकता है

coordinated disclosure और CERT/CC की प्रतिक्रिया

  • यह vulnerability class इंटरनेट सुरक्षा के लिए काफी बड़ा जोखिम रखती थी
  • जनवरी 2024 में report होने के बाद CERT/CC ने इस मुद्दे को track करने के लिए Vulnerability Coordination case खोला
  • कई बड़े tech companies और open source projects ने responsible disclosure process में भाग लिया
  • एक अकेले researcher के लिए इतनी सारी implementations की जांच करना कठिन होने के कारण, कई vendors को प्रभावित करने वाली समस्याओं में Vulnerability Coordination आवश्यक थी
  • CERT/CC ने इस मुद्दे पर Vulnerability Note प्रकाशित की, और ऐसे notes हर साल केवल कुछ ही जारी किए जाते हैं

1 टिप्पणियां

 
GN⁺ 2024-04-06
Hacker News टिप्पणियाँ
  • पिछले महीने Bandit में इसी समस्या को सीधे कम किया गया था
    https://github.com/mtrudel/bandit/blob/main/lib/bandit/http2...
    implementer के नज़रिए से देखें तो सच कहें तो यह ऐसी चीज़ है जिसे रोकना बहुत ही स्वाभाविक है। इस पर काफ़ी पहले से ध्यान था, और मुझे लगता रहा कि दूसरी implementations भी स्वाभाविक रूप से इससे बचाव कर रही होंगी

    • जब हम मान लेते हैं, तो क्या होता है यह आप जानते ही हैं। वही आपको और मुझे front page headline बना देता है
  • पिछले कुछ महीनों में मैंने दर्जनों implementations देखीं, और अजीब बात यह थी कि प्रमुख HTTP/2 servers में भी यह सुरक्षा या तो थी ही नहीं या ग़लत लागू की गई थी
    मूल रूप से, मुझे लगता है यह उस developer culture का नतीजा है जो हर चीज़ को अपने-आप dynamically grow करने की आदी हो चुकी है और यह नहीं सोचती कि आकार कितना बड़ा हो सकता है
    इस तरह की समस्या सिर्फ़ HTTP/2 तक सीमित नहीं है, लेकिन HTTP/2 की अत्यधिक complexity ने इसमें निश्चित रूप से भूमिका निभाई होगी। HTTP/1.x के दौर में C जैसी भाषाओं के ज़्यादा developers थे, इसलिए वे buffer length management पर लगातार ध्यान देते थे, और पूरे request में header allocation को, जो ज़्यादा से ज़्यादा कुछ KB ही होना चाहिए, अनंत तक बढ़ने नहीं देते

    • लोग बार-बार सिर्फ़ happy path पर ध्यान देते हैं और उसी को optimize करते हैं, लेकिन यह नहीं रुककर सोचते कि अगर कोई attacker जानबूझकर सबसे बुरी स्थिति को बार-बार पैदा करे तो क्या होगा
      slowloris, query parameter hash collision जैसी कई denial-of-service attacks तब व्यावहारिक बनीं जब bounded resource usage के बारे में बाद में सोचा गया
  • > प्रभावित नहीं: Nginx, Jetty, HAProxy, NetScaler, Varnish. [0]
    0: https://nowotarski.info/http2-continuation-flood/

    • दूसरे शब्दों में, ये वे implementations हैं जो 10 साल से denial-of-service risk की वजह से CONTINUATION के उपयोग का विरोध करती आ रही थीं। अगर आप लंबे threads पढ़ें, तो हमेशा यही मुख्य सवाल था कि इस परेशान करने वाले CONTINUATION से कैसे बचा जाए: https://lists.w3.org/Archives/Public/ietf-http-wg/2014JulSep...
      कम-से-कम यह प्रस्ताव तो मान लिया जाता कि non-full HEADERS frame के बाद इसे प्रतिबंधित किया जाए, तो चीज़ें और मज़बूत हो सकती थीं, लेकिन माना गया कि इससे encoding का काम खुद और मुश्किल हो जाएगा। वजह थीं compressor byte boundary जैसी समस्याएँ
      हर 10 साल में वही चीज़ें फिर से “खोजी” जाती देखना मज़ेदार है। अभी हाल में मशहूर RESET_STREAM flood था, इस बार CONTINUATION है, और जल्द ही शायद zero-length DATA frames, 1-byte WINDOW_UPDATE, CPU-heavy INITIAL_WINDOW SETTINGS जैसी चीज़ें आएँगी। अगर किसी ज्ञात समस्या को बस एक नाम और संभव हो तो एक logo दे दिया जाए, तो दुनिया इस security circus को चलाती रहती है
    • Caddy का क्या? इतना बढ़िया project है कि इसके लिए अलग से एक पंक्ति मिलनी चाहिए ;)
  • इसी लेखक की प्रभावित web servers/reverse proxies की सूची वाली पिछली पोस्ट
    https://nowotarski.info/http2-continuation-flood/

  • यह पोस्ट पूरे दिन सबसे ऊपर रही
    जिज्ञासा है, अगर किसी website पर traffic कम हो, तो क्या उसे बस HTTP/1.1 पर चलाना ज़्यादा सुरक्षित हो सकता है?

    • HTTP/1.1 को implement करना काफ़ी आसान है, इसलिए उसमें bugs कम होने की उम्मीद करना तर्कसंगत है
      HTTP/2 और HTTP/3 features के लिहाज़ से काफ़ी अलग हैं। multiplexing, windowing, HPACK आदि जुड़ने से HTTP/1.1 का लगभग stateless connection एक stateful connection में बदल जाता है। stateful connection बनाए रखने के लिए state और settings जैसी data store करनी पड़ती है, और यहीं से इस तरह की समस्याएँ पैदा होती हैं
      HTTP/2 में multiplexing जुड़ने से defense characteristics भी बदल जाती हैं। उदाहरण के लिए, अगर connection CDN origin request से आ रहा हो, तो आप कम connections allow करके हर connection पर बड़ा multiplexed channel pool रख सकते हैं, लेकिन अगर access सीधे users से हो, तो आप शायद ज़्यादा connections allow करना चाहेंगे और हर connection में multiplexed channels की संख्या घटाना चाहेंगे। HTTP/1 में लगभग सब कुछ एक जैसा दिखता था, इसलिए defense काफ़ी सरल था
    • सिर्फ़ upgrade करने के लिए upgrade करना अच्छी engineering practice नहीं है। अगर upgrade से कोई अतिरिक्त लाभ नहीं मिलता, तो उसे justify करना मुश्किल है
    • ज़रूरी नहीं। जो लोग कहते हैं कि HTTP/1.1 सरल है, उन्होंने शायद real-world compatible complete parser implement नहीं किया है
      HTTP/1 में कई कम दिखाई देने वाली edge conditions और पुराने exception behaviors हैं। text format सिर्फ़ valid headers देखने पर जितना लगता है, उससे कहीं ज़्यादा flexible है, और इसमें multi-line headers, पुराने MIME features, 100-continue race conditions, custom hop-by-hop headers, GET body जैसी अस्पष्ट features भी हैं
      अच्छी बात यह है कि नए HTTP RFCs ने कई pitfalls को document किया है। सिर्फ़ RFC 2616 देखकर implement करने से सुरक्षित implementation नहीं बनती
      request या response का वास्तविक आकार एक साथ कई तरीकों से निर्दिष्ट हो सकता है, और values टकरा भी सकती हैं। साथ ही, कई feature combinations और ऐसे header values भी होते हैं जिनके लिए backward compatibility के कारण अजीब parsing rules चाहिए, इसलिए “सरल” HTTP implementation request smuggling का शिकार हो सकती है
      किसी भी तरफ़ जाएँ, आपको एक मज़बूत, अच्छी तरह tested, mature implementation चाहिए
    • मैं भी यही सोच रहा था। ज़्यादा mature और कम complex होने की वजह से इसके ज़्यादा सुरक्षित होने की संभावना लगती है
    • शायद हाँ। HTTP/2 streaming के लिए अच्छा है, और वह भी अब नए protocols से बदला जा रहा है
      सामान्य static assets serve करने में HTTP/1 का फ़ायदा बस इतना है कि domain per connection limit की वजह से यह ज़्यादा assets को parallel में ला सकता है। अलग-अलग domains के CDN इस्तेमाल करने पर आम तौर पर यह समस्या भी टल सकती है
      सिद्धांत रूप में HTTP/2 पर बिना bundled JavaScript assets serve किए जा सकते हैं, लेकिन मैंने production environment में ऐसा नहीं देखा। संभवतः इसलिए कि ज़्यादातर मामलों में अब भी compile step की ज़रूरत पड़ती है
  • अगर इसे धीरे-धीरे किया जाए तो इसे slowloris v2 कहा जा सकता है :(

  • HTTP/2, या application-layer protocol में transport-layer “upgrade” को ज़बरदस्ती ठूँसने का तरीका