رجوع

2026/10/26

HTTP2Shell (CVE-2026-111497): تحديث الملف التنفيذي لا يُصلح العمليات قيد التشغيل

ثغرة Brotli وراء HTTP2Shell تطال Node.js وBun وPython. كيف تكتشف التعرض الفعلي في العمليات الجارية وتعيد تشغيل وكلاء الذكاء الاصطناعي بأمان.

في 21 أكتوبر 2026 أصدرت Node.js إصلاحات للثغرة CVE-2026-111497، وهي كتابة خارج حدود الذاكرة في مُفكِّك ضغط Brotli من Google. وخلال أيام تبعتها نشرات أمنية لكلٍّ من Bun وحزمتَي Python brotli وbrotlicffi وEnvoy وIstio، وأطلقت الصحافة الأمنية على الثغرة اسم Node2Shell ثم HTTP2Shell. ورغم هذا الاسم، فهي ليست خللًا في بروتوكول HTTP/2، بل خلل في فك الضغط، وتكتسب أهميتها في كل موضع يفك فيه برنامجٌ بياناتِ Brotli يستطيع المهاجم التأثير فيها.

يوضح هذا المقال ما ثبت بالفعل، وما هو محتمل فقط، وكيف تفحص أنظمتك. ويتناول أيضًا درسًا من استجابتنا نحن: تثبيت ملف تنفيذي مُصلَح لا يُصلح عمليةً ما زالت تشغّل الملف القديم. ففي بيئتنا احتفظت جلسات Claude Code طويلة التشغيل ببيئات التشغيل المضمّنة القديمة بعد أن كان Claude Code المثبّت قد حُدِّث.

أهم النقاط

  • الإصدارات المتأثرة: Brotli الأصلي من 1.0.3 حتى 1.2.0. الإصدارات المُصلَحة: 1.0.10 و1.1.1 و1.2.1. المنتجات التي تضمّن Brotli بداخلها تحتاج إلى إصدار مُصلَح خاص بها.
  • التعرض يتطلب مسارًا فعليًا: بيانات Brotli يتحكم فيها المهاجم تصل إلى مُفكِّك ضغط مصاب. اسم المنتج أو إمكانية الوصول عبر الشبكة وحدهما ليسا دليلًا.
  • افحص العمليات الجارية، لا الحزم المثبّتة فقط. تحتفظ العملية ببيئة التشغيل التي بدأت بها إلى أن يُعاد تشغيلها.
  • أعد تشغيل وكلاء الذكاء الاصطناعي ذوي الحالة واحدًا تلو الآخر، بعد تسليم أحداثهم وموافقاتهم وحالة عملهم أولًا.

1. ما هي الثغرة

عندما كان مُفكِّك الضغط يصادف تبديل كتلة غير صالح، كان يُبلغ بأنه “يحتاج إلى مزيد من المدخلات” بدلًا من خطأ تنسيق نهائي. فيواصل المستدعي العامل بنمط التدفق إرسال البايتات، ويُلحقها المُفكِّك بمخزن مؤقت داخلي حجمه ثمانية بايتات، فيكتب متجاوزًا نهايته. وتذكر نشرة Node.js أن المهاجم الذي يستطيع تقديم بيانات Brotli يفك التطبيق ضغطها باستخدام node:zlib “يمكنه إفساد ذاكرة العملية، مما قد يؤدي إلى انهيارها أو كشف محتوى ذاكرتها أو تنفيذ تعليمات برمجية عن بُعد”.

تختلف تقديرات الخطورة باختلاف المصدر. تصنفها Node.js بدرجة High. وتصنفها نشرة GitHub الخاصة بحزم Python ونشرة Envoy بدرجة Critical، بدرجة CVSS 4.0 قدرها 9.2. وتدرجها Ubuntu حاليًا بأولوية Medium. وحتى 26 أكتوبر، يذكر تقييم CISA في سجل الثغرة أن الاستغلال “none”، والثغرة غير مدرجة في كتالوج CISA للثغرات المستغَلة المعروفة (Known Exploited Vulnerabilities). ومع ذلك تذكر نشرة Node.js تنفيذ التعليمات البرمجية عن بُعد كنتيجة محتملة، فلا تعتبر غياب البلاغات عن هجمات سببًا للانتظار.

2. الشروط المسبقة: متى تكون معرّضًا فعلًا

يجب أن تتحقق الشروط الثلاثة جميعها:

  1. مُفكِّك ضغط مصاب محمَّل في العملية: مكتبة Brotli مضمّنة أو مكتبة النظام ضمن النطاق المتأثر دون إصلاح أو نقل للإصلاح (backport).
  2. شيء ما يستدعيه: فك ضغط صريح، أو فك تلقائي لترميز Content-Encoding: br.
  3. المدخلات قابلة لتأثير المهاجم: استجابات من عناوين URL عشوائية، أو ملفات مرفوعة، أو أجسام طلبات واردة، أو حركة HTTP غير مشفرة على شبكة معادية.

فك الترميز التلقائي هو المسار الذي تغفل عنه الفرق أكثر من غيره. تشير نشرة Bun إلى أن fetch يرسل Accept-Encoding: br افتراضيًا ويفك ترميز الاستجابة، لذا فإن أي برنامج Bun يجلب عناوين URL غير موثوقة لديه مسار للثغرة. وفي Python، بمجرد تثبيت brotli أو brotlicffi، تفك urllib3 تلقائيًا ترميز الاستجابات ذات Content-Encoding: br، كما تُعلن requests دعمها لـ br في طلباتها. ويحدث ذلك حتى لو لم يذكر كودك Brotli مطلقًا. أما Envoy وIstio فلا يكونان معرّضين إلا عند تهيئة مُفكِّك ضغط Brotli.

3. المتأثر المؤكَّد مقابل المحتمل فقط

الوضع حتى 26 أكتوبر 2026. راجع النشرات المرتبطة، فالأمور تتغير بسرعة.

المكوّنالحالةأُصلح في
Brotli الأصلي (C)مؤكَّد: من 1.0.3 حتى 1.2.01.0.10، 1.1.1، 1.2.1
Node.js 22، 24، 26مؤكَّد22.23.4، 24.22.1، 26.12.1
Node.js 20 والأقدم (انتهى دعمها)لم يُقيَّم؛ تعتبر Node.js الخطوط المنتهي دعمها متأثرة دائمًاالانتقال إلى خط مدعوم
Bun (بما في ذلك الملفات التنفيذية المُجمَّعة)مؤكَّد: من 1.0.21 حتى 1.4.31.4.4، مع إعادة بناء التطبيقات المُجمَّعة
Python brotli / brotlicffiمؤكَّد: بدءًا من 1.0.31.2.1 / 1.2.1.0
Envoy (مع تهيئة مُفكِّك ضغط Brotli)مؤكَّد1.40.0، 1.39.3، 1.38.6، 1.37.8 (و1.36.12 على الفرع 1.36 غير المدعوم)
Istio (مع تفعيل فك ضغط Brotli)مؤكَّد1.31.2، 1.30.6، 1.29.9 (آخر إصدار من 1.29)
حزمة brotli في Ubuntuقيد التقييم (جميع الإصدارات)لا توجد حزمة مُصلَحة بعد
حزمة nodejs في Ubuntuمصابة على 20.04 (Brotli مضمّن). على 22.04 وما بعده تستخدم Brotli النظام، فتتبع الصف السابقانظر متتبع Ubuntu
حزمة brotli في Debianأُصلحت في bookworm وtrixie؛ ولم تُصلح بعد في forky1.0.9-2+deb12u1، 1.1.0-2+deb13u1
Chrome وElectron وتطبيقات Electronغير معروف: لم نجد بيانًا من المورّدتابع ملاحظات إصدارات المورّد

بشأن الصف الأخير: يضمّن Chrome وElectron نسخًا خاصة بهما من Brotli، كما يضمّن تطبيق Electron بيئة تشغيل Node.js. ووقت كتابة هذا المقال، تتضمن أحدث إصدارات Electron المستقرة (45.0.0 و44.9.3 و43.8.3، وكلها بتاريخ 21 أكتوبر) الإصدار Node.js 24.22.0 لا 24.22.1، ولا تذكر ملاحظاتها هذه الثغرة. أما الإصدارات التجريبية من Electron بتاريخ 23 أكتوبر فتحمل Node.js 24.22.1، لذا يبدو صدور إصلاح مستقر مرجحًا، لكنه لم يصدر بعد. وهذا يجعل تطبيق سطح المكتب المبني على Electron معرّضًا على نحو محتمل، لا مصابًا بشكل مؤكَّد. وينطبق الأمر نفسه على تطبيقات الدردشة وأدوات الذكاء الاصطناعي المكتبية: راجع بيان كل مورّد قبل الحكم بأنها متأثرة أو آمنة.

فخ آخر: رقم إصدار المكتبة المُبلَّغ عنه ليس دليلًا في أي من الاتجاهين. فإصدار Node.js 22.23.4 المُصلَح ما زال يُبلغ عن process.versions.brotli على أنه 1.1.0، لأن الإصلاح نُقل إليه (backport). وتفعل التوزيعات الشيء نفسه. احكم بناءً على الإصدار المُصلَح للمنتج أو على الحالة الأمنية في التوزيعة، لا على سلسلة إصدار Brotli.

4. ما وجدناه في بيئتنا

أجرينا جردًا للقراءة فقط قبل تغيير أي شيء. إليك ما لاحظناه، بعد إخفاء الهوية، مع فصل النتائج المؤكَّدة عن المخاطر التي لم نتمكن من التحقق منها.

ملاحظات مؤكَّدة

  • كان Claude Code المثبّت قد حُدِّث بالفعل، لكن نحو اثنتي عشرة جلسة طويلة التشغيل كانت لا تزال تعمل بإصدارات أقدم بدأت قبل أسابيع، ولم يُعَد تشغيلها قط.
  • فاتنا ثلاث منها في العدّ الأول، إذ كانت تعمل تحت عملية مساعدة مختلفة، فلم يطابقها البحث بسطر الأوامر. أما البحث بالملف التنفيذي فعثر عليها.
  • كان المُحدِّث قد حذف ملفات الإصدارات القديمة من القرص، لذا تعذّرت قراءة إصدار Bun المضمّن في تلك العمليات.
  • تضمّن تثبيت متبقٍّ عبر مدير الحزم لإصدار أقدم من Claude Code الإصدار Bun 1.4.0. لم يكن أي شيء يستخدمه في التشغيل العادي، لكن مسارًا احتياطيًا كان من الممكن أن يستخدمه. أزلناه بعد أن أثبتنا أنه لا شيء يعتمد عليه.
  • كانت بيئة تشغيل Node.js غير مستخدمة ومنتهية الدعم لا تزال مثبّتة على أحد الخوادم. أزلناها بالطريقة نفسها.
  • يحتوي إصدار Claude Code الحالي الذي رقّينا إليه (2.1.310) على Bun 1.4.4. وقد تأكدنا من ذلك بفحص الملف التنفيذي. لا يذكر سجل تغييرات Anthropic هذه الثغرة، لذا افحص إصدارك بالطريقة نفسها.

غير متحقَّق منه، ويُعامَل كخطر

  • ما إذا كانت الجلسات القديمة مصابة فعلًا. فقد تعذّرت قراءة إصدار Bun فيها. ولم يظهر Bun 1.4.4 المُصلَح إلا نحو 22 أكتوبر، بعد تلك الإصدارات، لذا عاملناها على أنها متأثرة.
  • ما إذا كان بإمكان مهاجم الوصول إلى مُفكِّك الضغط عبر تلك الجلسات، مثلًا بجلب عنوان URL خبيث. هذا المسار معقول استنادًا إلى سلوك fetch الموثّق في Bun، لكننا لم نثبته.
  • ما إذا كانت برمجيات أخرى تضمّن نسختها الخاصة من Brotli متأثرة. وحيث لا يوجد بيان من المورّد، نتابع المنتج مقابل إصدارات المورّد بدلًا من افتراض أنه آمن أو مصاب.

لم نجد أي دليل على الاستغلال. ولم نعتبر ذلك أيضًا دليلًا على الأمان: فالحل كان إزالة التعرض، لا تبريره.

5. قائمة التحقق للاكتشاف والمعالجة

اعثر على الاعتماديات

ابحث عن Brotli حيث يكون مضمّنًا، لا فقط حيث يكون مثبّتًا كحزمة. افحص قوائم SBOM وصور الحاويات بحثًا عن Node.js وBun وحزم Python وEnvoy وBrotli المربوط ربطًا ساكنًا. وافحص اعتماديات Python غير المباشرة، لأن Brotli كثيرًا ما يصل كإضافة اختيارية.

# Node.js: compare with 22.23.4 / 24.22.1 / 26.12.1
node -p process.version
# Python packages: run once per interpreter and virtual environment
python -m pip list 2>/dev/null | grep -iE '^(brotli|brotlicffi) '
pipdeptree -r -p brotli          # what pulled it in
# Debian / Ubuntu system library (then check the distro tracker)
dpkg-query -W -f='${db:Status-Abbrev} ${Package} ${Version}\n' 'libbrotli*'
# Container images, with an SBOM scanner such as Syft
syft <image> | grep -iE 'brotli|node|bun'

اعثر على بيئات التشغيل المضمّنة

البرامج المبنية باستخدام bun build --compile تحمل نسختها الخاصة من Bun. وتقول نشرة Bun إنها تظل متأثرة إلى أن يُعاد بناؤها بالإصدار 1.4.4. وترقية Bun على جهاز البناء لا تغيّر الملفات التنفيذية المنشورة بالفعل.

# The Bun version inside a compiled executable
grep -aoE 'Bun v1\.[0-9]+\.[0-9]+' /path/to/binary | sort -u

اعثر على العمليات الجارية

افحص ما يعمل، لا ما هو مثبّت. على Linux، تظهر العملية التي استُبدل ملفها التنفيذي أو حُذف بعلامة (deleted). وهذه علامة قوية على أنها ما زالت تشغّل إصدارًا قديمًا.

# Processes still running a deleted (replaced) executable (run as root to see all users)
for p in /proc/[0-9]*; do
  e=$(readlink "$p/exe" 2>/dev/null) || continue
  case "$e" in *"(deleted)") echo "${p#/proc/} $e";; esac
done
# Processes older than the fix
pgrep -af 'node|bun|claude'      # then: ps -o pid,lstart,args -p <pid>

على macOS، يعرض الأمر lsof -p <pid> الملف التنفيذي الذي حمّلته العملية. طابِق بالملف التنفيذي لا باسم العملية، وإلا فاتتك العمليات المساعدة وعمليات الخلفية.

أصلِح، وأعد البناء، وأعد النشر

  • رقِّ إلى الإصدارات المُصلَحة الواردة في الجدول. واستخدم الحزمة المُرقَّعة من توزيعتك حيث يوجد إصلاح منقول (backport).
  • أعد بناء صور الحاويات والملفات التنفيذية المُجمَّعة، ثم أعد نشرها. فالصورة الأساسية المُرقَّعة لا تفيد صورةً لم تُعِد بناءها.
  • أزل بيئات التشغيل غير المستخدمة والتثبيتات المتبقية، بعد إثبات أنه لا توجد خدمة أو مهمة أو سكربت تعتمد عليها. واحتفظ بإمكانية التراجع.
  • حيث لا يوجد إصلاح بعد، عطّل فك ترميز Brotli للمدخلات غير الموثوقة متى كان ذلك آمنًا. في Bun، يعمل fetch(url, { decompress: false }) على مستوى كل طلب. وعيّن لكل إجراء تخفيف مسؤولًا وتاريخ انتهاء.

أعد التشغيل وتحقّق

بعد إعادة التشغيل، تحقّق من الملف التنفيذي الفعلي للعملية الجديدة ومن إصدار بيئة التشغيل. ثم تحقّق من مخرجات الأعمال: تفريغ قوائم الانتظار، ونجاح المهام المجدولة، ووصول تنبيهات المراقبة. فنجاح أمر الترقية لا يثبت إلا أن الأمر قد نُفّذ.

6. إعادة تشغيل وكلاء الذكاء الاصطناعي الدائمين بأمان

يحتفظ وكلاء الذكاء الاصطناعي طويلو التشغيل بحالة: استدعاءات أدوات معلّقة، وموافقات، ورسائل في قوائم الانتظار، ومهام استحوذوا عليها. وإعادة تشغيلهم جميعًا دفعة واحدة قد تُضيّع هذه الحالة. وقد نجح معنا التسلسل التالي:

  1. اجرد كل جلسة: إصدار الملف التنفيذي، والحالة (خاملة أو منتظرة أو مشغولة)، وآخر نشاط، والعمل المعلّق، والموافقات التي تنتظرها أو تحتفظ بها، والأحداث غير المؤكَّد استلامها وكيفية استعادتها.
  2. انقل الموافقات من الذاكرة. الموافقة التي لا توجد إلا في محادثة الجلسة تضيع عند إعادة التشغيل. قبل إعادة التشغيل، سجّل كل موافقة معلّقة (ما الذي وُوفق عليه، ومن وافق، ولأي تغيير بالتحديد) في مكان دائم، مثل التذكرة أو طلب الدمج (pull request). وبعد ذلك تأكد أن الجلسة ما زالت تراها. وإن ساورك الشك، فاطلب الموافقة مجددًا بدلًا من افتراضها.
  3. أثبت جاهزية البديل أولًا. تأكد أن الجلسات العاملة على الإصدار المُصلَح تعمل بالفعل، وأن هناك مراقب أحداث مستقلًا عن الجلسات القديمة قيد التشغيل، حتى لا يعتمد أي حدث على جلسة يُعاد تشغيلها.
  4. أعد تشغيل الجلسات الخاملة أولًا، واحدة تلو الأخرى. بعد كل واحدة، تحقّق من أن العملية القديمة انتهت، وأن العملية الجديدة تشغّل الملف التنفيذي المُصلَح، وأن سجل المحادثة سليم، وأن أي اتصال عن بُعد قد عاد. ثم انتقل إلى التالية.
  5. أعد تشغيل المالك الأكثر انشغالًا في النهاية، بين مهمتين. اطلب منه كتابة مذكرة تسليم، ولا توقف مراقبه إلا عندما يصل عدد الأحداث المعلّقة وغير المؤكَّد استلامها إلى صفر، ثم أعد التشغيل، وتأكد أن مراقبه استأنف من الموضع المحفوظ دون أن يفوته شيء.
  6. توقّع مدخلات متراكمة عند إعادة الاتصال. تلقّت إحدى جلساتنا بعد إعادة تشغيلها رسالة كانت تنتظر منذ عشرة أيام، وتصرّفت بناءً عليها. افحص صندوق وارد كل جلسة قبل إعادة تشغيلها، وأبلغ مالكها.
  7. توقّف وصعّد الأمر إذا لم تستطع أي خطوة إثبات أن العمل قد استُعيد. ولا تلجأ إلى إنهاء كل شيء دفعة واحدة.

حدّد معنى “الانتهاء” قبل أن تبدأ. الانتهاء يعني عدم وجود أي عملية جارية على إصدار متأثر، وتوثيق كل استثناء مع مسؤول عنه وتاريخ لإعادة الفحص، ونجاح فحوصات الأعمال. أما عبارة “تمت ترقية الحزمة” فلا تعني الانتهاء.

المصادر