HTTP/2 CONTINUATION Flood के तकनीकी विवरण
(nowotarski.info)- 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 की कमी, और
CONTINUATIONprocessing के दौरान 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 है
HEADERSframe request और response के HTTP headers भेजता है, और header dataHPACK-encoded field block fragment में रखा जाता हैHEADERSframe में header और stream के अंत को बताने वाले flags होते हैंEND_HEADERS: संकेत कि इस frame में भेजे जाने वाले सभी headers शामिल हैंEND_STREAM: संकेत कि request या response body आगे नहीं है
- frames का एक maximum size होता है, जो connection शुरू होने पर तय होता है; यदि received frame allowed size से बड़ा हो, तो protocol error के कारण connection बंद कर दिया जाता है
- अगर सभी headers एक
HEADERSframe में नहीं आ पाते, तोEND_HEADERSके बिनाHEADERSframe के बादCONTINUATIONframes आते हैंEND_HEADERSके बिनाHEADERSframeEND_HEADERSके बिना अतिरिक्तCONTINUATIONframes- अंतिम
CONTINUATIONframe परEND_HEADERSसेट किया जाता है
- अंतिम header frame के बाद request data वाला
DATAframe आता है या HTTP/2 stream समाप्त हो जाता है
vulnerability का मूल: कभी खत्म न होने वाली header stream
- अगर client नया HTTP/2 stream शुरू करने के बाद
HEADERSऔरCONTINUATIONframes भेजता रहे और कभी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:
CONTINUATIONheaders memory में store होते रहते हैं; header list size limit तो होती है, लेकिन header timeout न होने से हर connection memory पकड़े रहता है - single-connection आधारित OOM: कुछ implementations memory भरने तक headers पढ़ते रहते हैं, जिससे OS process को terminate कर देता है
- कुछ ही frames में crash:
CONTINUATIONstream के बीच connection टूटने पर implementation bug के कारण server crash हो जाता है
END_HEADERSन होने पर request ठीक से बंद नहीं होती, इसलिए malicious client की request access logs में दर्ज नहीं होती
Go का मामला: HPACK decoding रुकती नहीं, CPU exhaustion
- Go,
CONTINUATIONFlood में CPU exhaustion का प्रमुख उदाहरण है - Go implementation
http2MetaHeadersFrameabstraction के माध्यम से एकHEADERSframe, 0 या उससे अधिकCONTINUATIONframes, और 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_HEADERSflag सेट हो - अगर attacker
END_HEADERSन भेजे, तोreadMetaFramereturn नहीं करता और attacker के भेजते रहने तक HPACK decoder नए bytes process करता रहता है
OOM के मामले और Firefox client पर प्रभाव
- OOM उन implementations में होता है जो
CONTINUATIONframes से बन रही header list के size को सीमित नहीं करतीं - जिन implementations में header timeout नहीं था, उनमें सिर्फ एक single HTTP/2 connection से server crash कराया जा सकता था
- idle timeout होने पर भी यह संभव था कि कई HTTP/2 connections को उनकी प्रति-connection सीमा के करीब RAM पकड़े रहने दिया जाए, और फिर अंतिम
CONTINUATIONframe को हर कुछ सेकंड में byte-by-byte भेजकर connection जिंदा रखा जाए CONTINUATIONFlood केवल 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
CONTINUATIONframe 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
Http2Sessiondestructor के भीतर थी - Node.js, HTTP/2 connection handling के लिए nghttp2 library को embed करता है
current_nghttp2_memory_nghttp2 के भीतर allocated memory को track करता है, और destructor मेंsession_.reset()के बाद यह verify करता है कि सभी nghttp2 artifacts memory से हट चुके हैं- जांच में पता चला कि
CONTINUATIONframe parsing के दौरान nghttp2 callback औरreset()एक साथ चल सकते थेNGHTTP2_IB_EXPECT_CONTINUATIONstate मेंCONTINUATIONframe आता है- state
NGHTTP2_IB_READ_HEADER_BLOCKमें बदलती है - उसके बाद
session_after_header_block_received,session_call_on_frame_received,on_frame_recv_callbackflow चलता है - Node.js के
OnFrameReceiveऔरHandleHeadersFramememory counter update करते हैं
- जब
HandleHeadersFrameऔरHttp2Session::~Http2Session()साथ-साथ चलते हैं, तोcurrent_session_memory_एक साथ update होता है,current_nghttp2_memory_negative हो जाता है, औरCHECK_EQfail हो जाता है
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 खत्म होने तक उसे बनाए रखती हैं
CONTINUATIONFlood में empty headers नहीं, बल्कि server द्वारा configured frame size limit तक बहुत सारे random headers भेजे जाते हैं- CVE-2019-9518, “Empty Frame Flooding”, empty payload वाले
DATA,HEADERS,CONTINUATION,PUSH_PROMISEframes आदि को end-of-stream के बिना भेजकर peer को attack bandwidth की तुलना में disproportionate processing time खर्च करवाता है CONTINUATIONFlood 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_HEADERSset वालेHEADERSframe तथाRST_STREAMframe के संयोजन का उपयोग करता है - इस तरीके में rate limiting जैसी standard mitigations नुकसान कम कर सकती हैं, और server administrators inbound requests की बड़ी संख्या को logs में देखकर alert हो सकते हैं
CONTINUATIONFlood मेंEND_HEADERSनहीं होता, इसलिए एक भी request पूरी नहीं होती, और administrators logs में request देख ही नहीं पाते- कई implementations में
CONTINUATIONFlood सिर्फ एक 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 टिप्पणियां
Hacker News टिप्पणियाँ
पिछले महीने Bandit में इसी समस्या को सीधे कम किया गया था
https://github.com/mtrudel/bandit/blob/main/lib/bandit/http2...
implementer के नज़रिए से देखें तो सच कहें तो यह ऐसी चीज़ है जिसे रोकना बहुत ही स्वाभाविक है। इस पर काफ़ी पहले से ध्यान था, और मुझे लगता रहा कि दूसरी implementations भी स्वाभाविक रूप से इससे बचाव कर रही होंगी
पिछले कुछ महीनों में मैंने दर्जनों 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 ही होना चाहिए, अनंत तक बढ़ने नहीं देते
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/
कम-से-कम यह प्रस्ताव तो मान लिया जाता कि 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 को चलाती रहती है
इसी लेखक की प्रभावित web servers/reverse proxies की सूची वाली पिछली पोस्ट
https://nowotarski.info/http2-continuation-flood/
यह पोस्ट पूरे दिन सबसे ऊपर रही
जिज्ञासा है, अगर किसी website पर traffic कम हो, तो क्या उसे बस HTTP/1.1 पर चलाना ज़्यादा सुरक्षित हो सकता है?
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 काफ़ी सरल था
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 चाहिए
सामान्य 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” को ज़बरदस्ती ठूँसने का तरीका