HTTP2Shell-এর দৃষ্টিকোণ থেকে নিরাপত্তাকেও বিবেচনায় রেখে প্রোগ্রামিং ভাষা নির্বাচন
হ্যালো, আমি অযোগ্য।
সম্প্রতি Brotli-এর ডিকোডারে একটি দুর্বলতা পাওয়া গেছে, যেটিকে Node2Shell বা HTTP2Shell ইত্যাদি নামে ডাকা হচ্ছে বলে মনে হচ্ছে।
এটি হলো CVE-2026-111497। ত্রুটিপূর্ণ Brotli-সংকুচিত ডেটা ডিকম্প্রেস করলে নির্ধারিত সীমানার বাইরে ডেটা লেখা হতে পারে, যার ফলে প্রসেস ক্র্যাশ, মেমরির তথ্য ফাঁস এবং কিছু শর্তে ইচ্ছামতো কোড চালানোর সুযোগও তৈরি হতে পারে।
অর্থাৎ, সমস্যাটি যেহেতু ডিকোডারের বাস্তবায়নে, তাই HTTP ক্লায়েন্টের দিকেও আপডেট প্রয়োজন। সার্ভারের দিকে Brotli সক্রিয় থাকলেও আপডেটের ব্যবস্থা করা দরকার বলে মনে করি, তবে প্রভাবের মাত্রা বিবেচনা করলে HTTP ক্লায়েন্টের দিকেই প্রভাব বেশি বলে মনে হয়।
এবার দুর্বলতাটি নিয়ে সরাসরি আলোচনা করার চেয়ে বরং বলতে চাই, এ ধরনের সমস্যা দেখা দিলে নির্বাচিত ভাষা ও লাইব্রেরির বিন্যাসের ওপর নির্ভর করে ব্যবহারকারীদের কাছে সংশোধন পৌঁছে দেওয়ার সহজতা বেশ বদলে যায়।
শুধু OS-এর লাইব্রেরি আপডেট করলেই সব শেষ হয় না
যেমন, ক্লায়েন্টের দিকে যদি OS-এর শেয়ার্ড লাইব্রেরি হিসেবে libbrotli ব্যবহার করা হয়, তাহলে সাধারণত সংশ্লিষ্ট 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 সক্রিয়ভাবে ব্যবহার করতে চাই। তবে সাধারণ অ্যাপ্লিকেশনের জন্য Go বা Node.js বেছে নেওয়ার পক্ষেও যথেষ্ট জোরালো কারণ আছে—এবারের HTTP2Shell দেখে আবারও সেটাই মনে হলো।