
استشارة وتحليل حول CVE-2026-111497، وهو كتابة خارج الحدود في وحدة فك ترميز Brotli يمكن الوصول إليها عبر فك ضغط HTTP في Node.js، مع السبب الجذري وفرق التصحيح وقواعد الكشف وتدابير التخفيف.
تاريخ آخر تحقق: 2026-11-04 (KST). لم تُدرج سوى القيم التي تم التحقق منها من المصادر المذكورة في قسم المراجع أدناه، وفي حال اختلاف القيم بين المصادر تم إدراجها جميعًا.
c/dec/decode.c) قيمة الإرجاع الخطأ عند تبديل الكتلة (block switch) غير الصحيح، فيستمر في فك الترميز بحالة تالفة مما يؤدي إلى الكتابة خارج حدود المخزن المؤقت (Out-of-Bounds Write).Brotli/brotlicffi، Bun، .NET، Envoy، Istio وغيرها. تتأثر فقط إذا كان هناك مسار لفك ضغط (decompress) بيانات Content-Encoding: br يتحكم بها المهاجم.| البند | المحتوى |
|---|---|
| CVE ID | CVE-2026-111497 (GHSA-9v8h-qqgm-4m35) |
| الأسماء البديلة | Node2Shell (تسمية Imperva)، HTTP2Shell، Broccoli2Shell. خلافًا للاسم، ليست عيبًا في بروتوكول HTTP/2 |
| تاريخ الإفصاح | 2026-10-21 إصدار Node.js الأمني وإصدار ترقيع Brotli / 2026-10-22 نشر سجل CVE |
| المكتشف | OpenAI Codex Security (Khai Tran, Sylvie Mayer) |
| المنتجات/الإصدارات المتأثرة | Google Brotli (libbrotlidec) 1.0.3 ~ 1.2.0 والمنتجات التي تحزمه أو تربط به (انظر الجدول أدناه) |
| الإصدارات المرقّعة | Brotli 1.0.10 / 1.1.1 / 1.2.1، Node.js 22.23.4 / 24.22.1 / 26.12.1، Python Brotli 1.2.1، brotlicffi 1.2.1.0، Bun 1.4.4 |
| CVSS v4.0 (CNA: Google) | 9.2 Critical — CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N |
| CVSS v3.1 (NVD) | 9.8 Critical — CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H |
| CVSS v3.1 (Red Hat / Amazon Linux) | 8.1 — CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H (بمراعاة متطلبات الهجوم AT:P وتقوية المنصة) |
| تصنيف المورّد نفسه | Node.js: High / Red Hat: Important |
| CWE | CWE-787 Out-of-bounds Write (NVD، Red Hat، مركز الأمن السيبراني الكندي) / CWE-252 Unchecked Return Value (وصف CISA KEV، ويُذكر في بعض المقالات التحليلية كسبب جذري) |
| الاستغلال الفعلي | تم تأكيد الاستغلال. إدراج في CISA KEV بتاريخ 2026-10-27، والموعد النهائي لإجراءات الوكالات الفيدرالية 2026-11-10. رصد مشغلو شبكات honeypot (Previdian، ELLIO) محاولات استغلال في 10-26~27 |
| المكوّن | الإصدارات المتأثرة | الإصدارات المصححة |
|---|---|---|
| Google Brotli (C, libbrotlidec) | 1.0.3 ~ 1.2.0 | 1.0.10, 1.1.1, 1.2.1 |
| Node.js 26.x (Current) | أقل من 26.12.1 | 26.12.1 |
| Node.js 24.x (LTS) | أقل من 24.22.1 | 24.22.1 |
| Node.js 22.x (LTS) | أقل من 22.23.4 | 22.23.4 |
| Node.js 25.x / 23.x / 20.x وما دون | جميع الإصدارات (EOL) | لا يوجد ترقيع → الانتقال إلى 22/24/26 |
| Bun | 1.4.3 وما دون | 1.4.4 |
Python Brotli (PyPI) | 1.2.0 وما دون | 1.2.1 |
Python brotlicffi | أقل من 1.2.1.0 | 1.2.1.0 |
| .NET / PowerShell | راجع إرشادات المورّد | تحديث Microsoft الطارئ لـ .NET (dotnet/announcements#494) |
| Envoy / Istio | راجع إرشادات المورّد | إصدارات تصحيحية وُزّعت مطلع أكتوبر (دون ذكر العيب صراحةً) |
| VMware SDDC Manager (VCF / vSphere Foundation) | راجع إرشادات Broadcom | راجع إرشادات Broadcom |
مسارات التعرض غير المباشر: تقوم مكتبات Python requests وurllib3 وaiohttp[speedups] وhttpx بفك ضغط استجابات br تلقائيًا إذا كانت Brotli/brotlicffi مثبتة. وينطبق الأمر ذاته على curl عند استخدام خيار فك الضغط.
| التاريخ | الحدث |
|---|---|
| 02-27 | دمج commit إصلاح في مستودع google/brotli (مُعلَّم كمسألة أمنية ولم يُصدَر) |
| أوائل أكتوبر | إصدار إصلاحات Envoy / Istio وتوزيعها |
| 10-21 | إصدار أمني لـ Node.js (22.23.4 / 24.22.1 / 26.12.1)، وإصدار Google Brotli 1.0.10 / 1.1.1 / 1.2.1 |
| 10-22 | نشر سجل CVE، وعرض Imperva تنفيذ شيفرة على Node.js 24.22.0 (Linux arm64) |
| 10-23 | توزيع حزم PyPI المُرقَّعة |
| 10-25 | توصية Microsoft .NET وتحديثها |
| 10-26 | تقرير ELLIO عن حركة مرور استغلالية |
| 10-27 | إدراج في CISA KEV (الموعد النهائي للإجراء 11-10) |
⚠️ بعض المقالات التحليلية المكتوبة بين 24 و28 أكتوبر تذكر أنه "لم تُؤكَّد هجمات فعلية، ولم يُدرَج في KEV"، لكن ذلك يعكس الوضع قبل الإدراج في KEV ويختلف عن الوضع الحالي.
BROTLI_FALSEفك ترميز Brotli هو آلة حالة (state machine). يتكوّن التدفق من وحدات تُسمى الميتابلوك (metablock)، وداخل كل ميتابلوك تظهر تعليمات تبديل نوع الكتلة (block switch) الخاصة بالحرف الحرفي (literal) وأمر الإدراج والنسخ (insert-and-copy command) والمسافة (distance).
تقوم DecodeBlockTypeAndLength() بتحليل تبديل الكتلة وتُرجع BROTLI_BOOL. المشكلة أن BROTLI_FALSE استُخدمت بمعنيين مختلفين تمامًا.
| المعنى | الطبيعة | المعالجة الصحيحة |
|---|---|---|
| الحاجة إلى مزيد من المدخلات (needs more input) | قابل للاسترداد | انتظار المدخل التالي ثم الاستئناف |
| وجود نوع كتلة واحد فقط في الميتابلوك بحيث يكون التبديل نفسه مستحيلًا | خطأ تنسيقي قاتل | رفض التدفق |
لم يفحص الطرف المستدعي هذه القيمة المُرجعة.
DecodeCommandBlockSwitch) مُعرَّف بـ static void فلا يحتفظ بالقيمة المُرجعة، كما أن الفرع non-safe في ماكرو BROTLI_SAFE يستدعي الدالة فقط ويتجاهل النتيجة. (تحليل SonicWall)DecodeLiteralBlockSwitch). تختلف الدالة المُشار إليها بين التحليلات، لكن وفقًا لوصف إصلاح التوزيعة (TuxCare) فقد عُدِّلت جميع مفككات تبديل الكتل الثلاث: الحرفيات والأوامر والمسافات لتُرجع رمز الخطأ، لذا يمكن اعتبار المسارات الثلاثة ذات صلة.ونتيجة لذلك، حتى عند حدوث خطأ تنسيقي يواصل المفكك التقدم كما لو أن التبديل نجح، ويبقى عدّاد طول الكتلة صفرًا، وتُورَّث حالة الموضع (position) والمسافة إلى مسار كان يجب رفضه.
BrotliDecoderState).ProcessCommandsInternal() الوجهة بـ copy_dst = &s->ringbuffer[pos] وتنفذ تخزين متجهي بـ 16 بايت عبر memmove16. وبما أن pos لم يعد مقيَّدًا بنطاق المخزن الحلقي، تحدث كتابة خارج منطقة التخصيص.pos، فإن هذا يشكّل بدائية write-what-where.ما يلي شيفرة مفاهيمية أُعيد تركيب جوهر التغيير بناءً على أوصاف التحليلات المنشورة (SonicWall وRedfox ووصف إصلاح TuxCare). ليست الشيفرة الأصلية للـ commit الفعلي، والتفاصيل مثل الشروط تقديرية. راجع الفرق الدقيق (diff) في إصدار google/brotli v1.2.1 وتغييرات
deps/brotliفي Node.js. (لم يُتحقَّق من hash الـ commit المُصلِح من مصدر أولي وقت كتابة هذا المستند.)
Before (قابل للاستغلال)
/* 형식 오류와 "입력 부족"이 같은 BROTLI_FALSE로 표현됨 */
static BROTLI_BOOL DecodeBlockTypeAndLength(int safe, BrotliDecoderState* s, int tree_type) {
...
if (/* 블록 타입이 1개뿐인 메타블록 */) {
return BROTLI_FALSE; /* 치명적 오류지만 복구 가능 신호와 구분 불가 */
}
...
return BROTLI_TRUE;
}
static void DecodeCommandBlockSwitch(BrotliDecoderState* s) {
DecodeBlockTypeAndLength(0, s, 1); /* 반환값 폐기 → 손상 상태로 계속 진행 */
}
بعد (التصحيح)
/* 명시적 오류 코드 반환 */
static BrotliDecoderErrorCode DecodeBlockTypeAndLength(int safe, BrotliDecoderState* s, int tree_type) {
...
if (/* 블록 타입이 1개뿐인 메타블록 */) {
return BROTLI_FAILURE(BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH); /* 신규 오류 코드 */
}
...
return BROTLI_DECODER_SUCCESS;
}