HTTP2Shell के नज़रिए से सुरक्षा को ध्यान में रखकर प्रोग्रामिंग भाषा का चयन

11 min

language: ja bn en es hi pt ru zh-cn zh-tw

नमस्ते, मैं मुनो हूँ।

हाल ही में Brotli के डिकोडर में एक भेद्यता मिली है, जिसे Node2Shell और HTTP2Shell जैसे नामों से पुकारा जा रहा है।

यह CVE-2026-111497 है। इसमें विकृत Brotli-संपीड़ित डेटा को डीकंप्रेस करने पर निर्धारित सीमा के बाहर मेमोरी में लिखने की स्थिति उत्पन्न होती है, जिससे प्रोसेस क्रैश हो सकता है, मेमोरी की जानकारी लीक हो सकती है और कुछ परिस्थितियों में मनचाहा कोड भी चलाया जा सकता है।

यानी, चूँकि समस्या डिकोडर के कार्यान्वयन में है, इसलिए HTTP क्लाइंट को भी अपडेट करना ज़रूरी है। सर्वर की ओर Brotli सक्षम होने पर भी अपडेट की आवश्यकता हो सकती है, लेकिन प्रभाव के लिहाज़ से मुझे लगता है कि HTTP क्लाइंट पर इसका असर अधिक है।

इस बार बात भेद्यता के बारे में उतनी नहीं है, बल्कि इस बारे में है कि ऐसी समस्या सामने आने पर चुनी गई भाषा और लाइब्रेरी की संरचना के अनुसार सुधार उपयोगकर्ताओं तक पहुँचाना कितना आसान या मुश्किल हो सकता है।

सिर्फ़ OS की लाइब्रेरी अपडेट कर देना हमेशा पर्याप्त नहीं होता

उदाहरण के लिए, यदि क्लाइंट की ओर libbrotli को OS की साझा लाइब्रेरी के रूप में इस्तेमाल किया जा रहा है, तो आम तौर पर संबंधित OS के पैकेज मैनेजर से संशोधित संस्करण इंस्टॉल करके समस्या का समाधान किया जा सकता है। बेशक, इसके लिए वितरण की ओर से सुधार उपलब्ध होना ज़रूरी है, और चल रहे प्रोसेस को फिर से शुरू करने की आवश्यकता भी पड़ सकती है।

हालाँकि, यह बात तब लागू होती है जब एप्लिकेशन OS की साझा लाइब्रेरी के साथ डायनेमिक लिंकिंग करता हो।

उसी Brotli का इस्तेमाल करते हुए भी, अगर बिल्ड के समय लाइब्रेरी को निष्पादन योग्य फ़ाइल में स्टैटिक लिंक कर दिया गया हो, तो OS के libbrotli को अपडेट करने से बाइनरी के भीतर की सामग्री नहीं बदलेगी। आपको डेवलपर की ओर से संशोधित संस्करण जारी होने का इंतज़ार करना होगा, या खुद निर्भरताएँ अपडेट करके दोबारा बिल्ड करना होगा।

यानी, महत्वपूर्ण बात केवल यह नहीं है कि “यह किस भाषा में लिखा गया है”, बल्कि यह है कि “समस्याग्रस्त कोड कहाँ मौजूद है और किसके अपडेट से वह बदलेगा”।

Rust में बाइनरी का वितरण सुविधाजनक है, लेकिन इसका एक दूसरा पहलू भी है

Rust में सामान्य Rust crate अक्सर निष्पादन योग्य फ़ाइल में शामिल हो जाते हैं, इसलिए उन्हें एक स्वतंत्र बाइनरी के रूप में वितरित करना आसान होता है। सच कहूँ तो मुझे भी यह सुविधा काफ़ी अच्छी लगती है।

दूसरी ओर, यदि किसी निर्भरता के कोड में भेद्यता मिलती है, तो ऐसे मामले सामने आते हैं जिनमें केवल OS के पैकेज अपडेट करने से समस्या ठीक नहीं होती। बाइनरी में शामिल निर्भरता के कोड को ठीक करने के लिए सामान्यतः एप्लिकेशन को दोबारा बिल्ड और वितरित करना पड़ता है।

इस बार Brotli के मामले में भी, Rust से C कार्यान्वयन को कॉल करने वाले brotli2 जैसे रैपर और Rust में लागू की गई brotli जैसी लाइब्रेरी की स्थिति अलग-अलग है। इसके अलावा, C कार्यान्वयन का इस्तेमाल करने पर भी अपडेट का तरीका इस पर निर्भर करता है कि साझा लाइब्रेरी को डायनेमिक लिंक किया गया है या C के कोड को भी स्टैटिक लिंक कर दिया गया है। ऐसा नहीं है कि Rust में बना होने के कारण उस पर C कार्यान्वयन की यह भेद्यता अनिवार्य रूप से लागू होती है।

मूलतः, Rust की मेमोरी सुरक्षा और निर्भरता के कोड को अपडेट करने की आसानी दो अलग-अलग मुद्दे हैं।

मेरे विचार में Rust लाइब्रेरी विकास और उन हिस्सों के लिए बहुत उपयोगी है जहाँ प्रदर्शन महत्वपूर्ण है। लेकिन व्यापक रूप से वितरित किए जाने वाले एप्लिकेशन में इसका इस्तेमाल करते समय, वितरण के बाद के सुरक्षा अपडेट पर भी विचार करना पड़ता है। तेज़ और आत्मनिर्भर बाइनरी बनाने के लाभ का दूसरा पहलू यह है कि सुधार उपयोगकर्ताओं तक पहुँचाने की ज़िम्मेदारी भी एप्लिकेशन की ओर अधिक आ जाती है।

Node.js में रनटाइम अपडेट कर पाना सुविधाजनक है

Node.js भी इस बार प्रभावित हुआ है। चूँकि Brotli का कार्यान्वयन Node.js में ही शामिल है, इसलिए केवल OS के libbrotli को अपडेट करने से समस्या हल नहीं होगी। Node.js के संशोधित संस्करण में अपडेट करना ज़रूरी है।

इस मामले में, Node.js की सुरक्षा रिलीज़ के तहत 22.23.4, 24.22.1 और 26.12.1 जारी किए गए हैं।

हालाँकि, एक सामान्य Node.js एप्लिकेशन में Node.js रनटाइम को अपडेट करके और एप्लिकेशन को फिर से शुरू करके, उसमें शामिल Brotli के सुधार को लागू किया जा सकता है। बेशक, यदि किसी npm पैकेज में कोई अलग भेद्य कार्यान्वयन शामिल है, या Node.js को ही निष्पादन योग्य फ़ाइल के साथ बंडल किया गया है, तो स्थिति अलग होगी।

इस लिहाज़ से, मुझे लगता है कि कुछ मायनों में इसका संचालन Rust की स्वतंत्र बाइनरी के वितरण से आसान है। हर उपयोगकर्ता से ढेरों एप्लिकेशन दोबारा बिल्ड करवाने की ज़रूरत नहीं पड़ती, और कुछ मामलों में साझा रनटाइम को अपडेट करके काम हो जाता है।

फिर भी, केवल Node.js का इस्तेमाल करने से ही यह भेद्यता सक्रिय नहीं हो जाती। समस्या तब होती है जब विकृत Brotli डेटा को डीकंप्रेस करने का कोई रास्ता मौजूद हो। मुझे लगता है कि ऐसे HTTP सर्वर कार्यान्वयन बहुत अधिक नहीं हैं जो आने वाले अनुरोधों को विशेष रूप से Brotli से डीकंप्रेस करते हों। लेकिन दूसरी ओर, क्लाइंट के रूप में बाहरी संसाधनों तक पहुँचने वाला fetch() Brotli-संपीड़ित प्रतिक्रियाओं को अपने-आप डीकंप्रेस करता है, इसलिए केवल सर्वर के अनुरोध सुनने वाले हिस्से पर ध्यान देना पर्याप्त नहीं है।

पहले मैं कुछ हद तक Node.js का विरोधी था, लेकिन हाल में इसके मानक API जिस तरह समृद्ध हुए हैं, उन्हें देखकर मेरी राय काफ़ी बदल रही है। fetch(), Web Streams और टेस्ट रनर जैसी सुविधाओं के कारण, जिन कामों के लिए पहले बाहरी पैकेजों पर निर्भर रहना पड़ता था, उनमें से बहुत कुछ अब केवल Node.js से ही किया जा सकता है।

निर्भर पैकेज कम होंगे, तो प्रबंधित किए जाने वाले अपडेट भी उतने ही कम होंगे। पहले की तुलना में, एप्लिकेशन लिखने के विकल्प के रूप में Node.js अब मुझे काफ़ी अच्छा लगता है।

मुझे अब भी लगता है कि Go का डिज़ाइन बहुत सोच-समझकर किया गया है

इस लिहाज़ से, मुझे लगता है कि Go को एप्लिकेशन विकास को काफ़ी ध्यान में रखकर डिज़ाइन किया गया है।

net/http सहित इसकी मानक लाइब्रेरी व्यावहारिक है। HTTP क्लाइंट और सर्वर बनाने के लिए ढेर सारी बाहरी निर्भरताएँ जोड़ने की बजाय, अक्सर अधिकांश कार्यान्वयन केवल मानक पैकेजों से ही पूरा हो जाता है। मानक HTTP क्लाइंट मूलतः gzip को ही अपने-आप डीकंप्रेस करता है, और वह डिफ़ॉल्ट रूप से इस बार प्रभावित Google Brotli के C डिकोडर का इस्तेमाल भी नहीं करता।

बेशक, Go में भी निर्भरताओं के कोड को बाइनरी में शामिल करना आम है, इसलिए यदि उस कोड में कोई भेद्यता हो, तो दोबारा बिल्ड और वितरण करना ज़रूरी होगा। Go होने का मतलब यह नहीं है कि केवल OS की लाइब्रेरी अपडेट करने से काम चल जाएगा। और अगर cgo के ज़रिए बाहरी लाइब्रेरी का इस्तेमाल किया जाए, तो डायनेमिक लिंकिंग बनाम स्टैटिक लिंकिंग का वही सवाल भी सामने आएगा।

फिर भी, केवल मानक पैकेजों से बहुत सारे काम पूरे हो जाना और आवश्यक निर्भरताओं की संख्या कम रखना आसान होना, मेरे विचार में रखरखाव ही नहीं बल्कि सुरक्षा के लिहाज़ से भी बहुत बड़ा लाभ है।

भाषा चुनते समय केवल रनटाइम प्रदर्शन ही नहीं देखना चाहिए

मेरा मतलब यह नहीं है कि Rust खराब है या Node.js और Go सुरक्षित हैं। मूलतः, इस बार की भेद्यता Brotli के C कार्यान्वयन में है, और केवल भाषा के नाम से यह तय नहीं किया जा सकता कि कोई एप्लिकेशन प्रभावित है या नहीं।

लेकिन मुझे लगता है कि भाषा चुनते समय रनटाइम प्रदर्शन, मेमोरी उपयोग और विकास की दक्षता के साथ-साथ यह भी परखना चाहिए कि निर्भर लाइब्रेरी के अपडेट अंततः कौन और कैसे उपयोगकर्ताओं तक पहुँचाएगा।

साझा लाइब्रेरी के मामले में, OS के पैकेज अपडेट से कई एप्लिकेशन की समस्या एक साथ ठीक हो सकती है। दूसरी ओर, स्टैटिक लिंक की गई बाइनरी निर्भरता के कोड सहित आत्मनिर्भर हो सकती है, लेकिन सुधार के समय हर एप्लिकेशन को अलग से अपडेट करना पड़ता है। Node.js जैसी रनटाइम-आधारित व्यवस्था में भी साझा रनटाइम को एक साथ अपडेट कर पाने का लाभ है, लेकिन यदि रनटाइम में ही भेद्यता निकल आए, तो उसका असर व्यापक होता है।

अंततः, यह डिज़ाइन का सवाल है कि किस चीज़ को कहाँ शामिल किया जाए, उसका वितरण कैसे किया जाए और किस हद तक उसकी देखरेख की जाए।

व्यक्तिगत रूप से, मैं लाइब्रेरी बनाने और उच्च प्रदर्शन की आवश्यकता वाले घटकों के लिए Rust का सक्रिय रूप से इस्तेमाल करना चाहता हूँ। वहीं, इस बार HTTP2Shell को देखकर मुझे फिर महसूस हुआ कि सामान्य एप्लिकेशन के लिए Go या Node.js चुनने के भी काफ़ी ठोस कारण हैं।

Related Posts