واتسابدمشقالسبت – الخميس·10:00 ص – 7:00 م

الـ middleware ليس authorization

4 ثغرات في NestJS خلال 7 أشهر، وكلها ثغرة واحدة: الـ matcher والـ handler اختلفا على العنوان نفسه. مكان التحقق هو حيث تُقرأ البيانات.

الأمننُشر في 4 دقائق قراءة

4 ترقيعات، وثغرة واحدة

بين كانون الأول 2025 وحزيران 2026 أصدرت حزمة @nestjs/platform-fastify 4 تحديثات أمنية منفصلة، لما هو عند القراءة المتأنية الثغرة نفسها:

  • CVE-2025-69211 — تخطّي الـ middleware عبر مسار مُرمَّز بـ percent-encoding. عولجت في 11.1.11.
  • CVE-2026-2293 — تخطّيه مجدداً حين تكون خيارات path normalization في Fastify مفعّلة. عولجت في 11.1.14.
  • CVE-2026-33011 — تخطّيه بإرسال HEAD بدل GET، لأن Fastify يوجّه طلب HEAD إلى handler الـ GET المقابل. عولجت في 11.1.16.
  • CVE-2026-54281 — تخطّيه بإضافة trailing slash في آخر العنوان. عولجت في 11.1.24.

4 إصلاحات في 7 أشهر. كل واحد منها صحيح وكان يستحق أن يُنشر. لكن ثغرة تعود في كل مرة برقم جديد ليست ثغرة في الغالب، بل موقف تصميمي لا يصمد.

الـ matcher والـ handler لا يتفقان

ربط الـ middleware بمسارات بعينها يعني أن نظامين يقرآن الطلب نفسه، وكلٌّ منهما يقرر ما هو. الـ middleware يطابق المسار مع نمط ليقرر هل يعمل أم لا، والـ router يطابق المسار نفسه مع route مسجَّل ليقرر ماذا ينفّذ. هذان تطبيقان منفصلان لسؤال واحد — "أي route هذا؟" — وكل ثغرة أعلاه حالة أجابا فيها إجابتين مختلفتين.

/admin و/admin/ عنوان واحد عند الـ router ونصّان مختلفان عند الـ matcher. والمسار المُرمَّز شيء قبل فكّ الترميز وشيء آخر بعده. وHEAD /admin لا يطابق أي middleware مسجَّل على GET، ثم يصل إلى handler الـ GET رغم ذلك. الـ handler عمل، والحارس لم يعمل.

هذا الصنف من الثغرات لا يُغلق بإحصاء الحالات. القائمة بطول مجموعة النصوص التي يمكن لـ parserين أن يختلفا عليها، وهي تطول كلما اكتسب أحدهما خياراً جديداً.

Next.js، والخطأ نفسه

الثغرة CVE-2025-29927، بدرجة 9.1 على مقياس CVSS: طلب يحمل الـ header المسمّى x-middleware-subrequest كان يُعامَل كاستدعاء داخلي فيتخطّى الـ middleware كلياً. عولجت في 15.2.3 و14.2.25 و13.5.9 و12.3.5. ليست اختلافاً بين parserين هذه المرة، بل trust boundary قَبِل header يرسله العميل دليلاً على أن الطلب داخلي — لكن النتيجة واحدة وللسبب البنيوي نفسه: قرار الـ authorization كان في موضع يمكن للطلب أن يتجاوزه.

ثم تكرر الأمر. الثغرة CVE-2026-64642 في تموز 2026: تطبيقات App Router المبنية بـ Turbopack مع locale واحد في config.i18n.locales كان يمكن تخطّي الـ authentication فيها المعتمد على الـ middleware. عولجت في 16.2.11. والحل البديل في نص الـ advisory نفسه يستحق أن يُقرأ كما كُتب:

enforce authorization in the page's server-side data path
instead of relying solely on middleware

هذا الـ framework نفسه يقول لك أين ينتمي التحقق.

هل أنت معرَّض؟

  • هل لديك ملف middleware.ts يعيد توجيه الزائر غير المسجَّل، وpages أو route handlers تفترض أنه عمل؟
  • هل استدعاء MiddlewareConsumer.forRoutes() في تطبيق Nest هو الـ authorization الوحيد الواقف أمام route محمي؟
  • لو حذفت ملف الـ middleware كلياً، هل تصبح أي بيانات محمية قابلة للقراءة؟

إن كان جواب السؤال الثالث نعم، فالـ middleware عندك هو نظام الصلاحيات، وهو على بُعد اختلاف واحد بين parserين من أن يكون غائباً.

أين يوضع التحقق

عند النقطة التي تُقرأ فيها البيانات. الدالة التي تجلب السجل هي آخر موضع في الطلب لا يمكن الالتفاف حوله، لأنه لا يوجد تطبيق ثانٍ لها ينافسها على تحديد ما طابقه. هناك دالة واحدة تقرأ ذلك الصف، وإما أن يعمل التحقق بداخلها أو لا يُقرأ الصف.

عملياً: جلب الـ session والتحقق من الملكية يعيشان في طبقة البيانات، أو في دالة واحدة يستدعيها كل handler قبل أن يعيد شيئاً. هذا كود أكثر من ملف middleware واحد، ويتكرر تكراراً يبدو زائداً عن الحاجة — إلى أن يأتي اليوم الذي لا يكون فيه كذلك. وهو أيضاً النسخة الوحيدة التي تصمد حين يغيّر الـ framework رأيه في معنى الـ trailing slash.

ما الذي يصلح له الـ middleware إذن

كل ما ليس قراراً أمنياً: اختيار الـ locale، وإعادة التوجيه، وإضافة response headers، ورفض الطلبات المشوّهة مبكراً كي لا يعمل المسار المكلف أصلاً. هو مكان جيد لاتخاذ قرار رخيص، ومكان سيّئ لاتخاذ القرار الوحيد.

عامله على أنه تحسين للأداء. إن تُخطّي، فالمفترض أن يصبح الطلب أبطأ — لا أن يصبح أوسع صلاحية.

الـ middleware ليس authorization · Qasioun Cloud