
إثبات المفهوم لـ CVE-2026-9256، وهو تجاوز سعة المخزن المؤقت في الكومة في وحدة ngx_http_rewrite_module الخاصة بـ NGINX. يوضح تعطل العامل (worker) ورفض الخدمة عبر URI مُصمم بعناية يحتوي على مجموعات التقاط PCRE متداخلة. يتضمن تحققًا متعدد المراحل واختبارًا لاتصال keep-alive.
نطاق التطبيق: يُستخدم فقط في البيئات المحلية المصرح بها، وبيئات إعادة إنتاج الثغرات المعتمدة، والتحقق من الثغرات، وتحليل قواعد الحماية. لا تستخدمه ضد أهداف غير مصرح بها. تفترض فكرة PoC في هذه المقالة فقط التحقق من سلوك تعطل عامل NGINX المرئي عن بعد، ولا تتضمن RCE أو تجاوز ASLR أو سلسلة استغلال مستقرة.
CVE-2026-9256 هي ثغرة في تجاوز سعة المخزن المؤقت في كومة الذاكرة (heap buffer overflow) في وحدة ngx_http_rewrite_module في NGINX. لا يتم تشغيل الثغرة بمجرد الوصول إلى URI ثابت معين، بل تعتمد على نمط تكوين rewrite معين: وجود مجموعات التقاط PCRE متداخلة في التعبير المنتظم للـ rewrite، وإشارة جزء الاستبدال (replacement) إلى متغيرات التقاط متعددة، مثل $1، $2.
عندما يقوم المهاجم ببناء URI خاص بحيث يدخل منطق rewrite في المسار ذي الصلة، قد يحدث عدم تطابق بين حساب الطول والكتابة الفعلية أثناء معالجة المحتوى الملتقط، أو ربط نتائج rewrite، أو ترميز URI / المعاملات، مما يؤدي في النهاية إلى تدمير ذاكرة كومة عملية العامل (worker process).
لذلك، لا يكمن مفتاح هذه الثغرة في المسار /api نفسه، بل في ما إذا كان هناك قاعدة rewrite ضعيفة في تكوين NGINX الهدف يمكن أن تتأثر بالطلب. المسار /api المستخدم افتراضيًا في PoC هو مجرد مسار مثال في بيئة إعادة الإنتاج الحالية؛ عند الاختبار الفعلي، يجب ضبط مسار الطلب وفقًا لقاعدة rewrite الموجودة في تكوين NGINX والتي تحتوي على مجموعات التقاط متداخلة وتشير إلى متغيرات التقاط متعددة.
الهدف الحالي لـ PoC هو التحقق من سلوك تعطل العامل (worker crash) / رفض الخدمة (denial of service). لا يحاول بناء تخطيط دقيق للكومة، ولا يحاول الكتابة فوق عنوان الإرجاع أو مؤشرات الدوال، ولا يثبت تنفيذ التعليمات البرمجية عن بُعد. الدليل المستقر الذي يمكن ملاحظته عن بُعد هو بشكل أساسي: قطع اتصال الطلب المُشغّل بشكل غير طبيعي، ثم استئناف استجابة خدمة NGINX، وانقطاع اتصال keep-alive بعد التشغيل بسبب تعطل العامل.
بعد تشغيل هذه الثغرة، لا يظهر بالضرورة كرمز حالة HTTP ثابت مثل 500 أو 502 أو 400. السبب هو أن نموذج master-worker في NGINX يتسبب في انهيار عملية العامل ثم يقوم master بإعادة تشغيل عامل جديد. ما يراه المهاجم عن بُعد ليس عادةً عدم توفر الخدمة بالكامل، بل قطع اتصال مفاجئ، أو انتهاء مهلة القراءة، أو إعادة تعيين الاتصال (reset)، ثم عند زيارة / مرة أخرى، يحصل على استجابة طبيعية.
لذلك، لا يمكن لـ PoC أن يحكم على وجود الثغرة بناءً على رمز حالة HTTP لطلب واحد فقط. إذا تم إرسال URI طويل مرة واحدة، ثم رؤية قطع الاتصال، والحكم مباشرةً بـ "الثغرة موجودة"، فإن خطر النتائج الإيجابية الخاطئة مرتفع. قد يحدث قطع الاتصال أيضًا بسبب اضطراب الشبكة، أو انتهاء مهلة الوكيل، أو اعتراض الطلب بواسطة جهاز وسيط، أو تحديد معدل من جانب الخادم، أو قفل الاتصال من جانب الخادم.
لذلك، يجب تصميم PoC كتحقق متعدد المراحل:
فقط عندما يحدث "قطع اتصال غير طبيعي + استئناف الخدمة لاحقًا + انقطاع متعدد الجولات في keep-alive" في نفس الوقت، يمكن الحكم بشكل أكثر أمانًا على وجود سلوك تعطل العامل على غرار CVE-2026-9256.
المسار الأساسي للتشغيل في PoC الحالي هو:
GET /api/++++++++++++++++++++++++++++++++... HTTP/1.1
Host: 127.0.0.1:19321
حيث /api/ هو المسار المثال في بيئة الهدف لضرب قواعد rewrite، وبعده يتم إلحاق عدد كبير من أحرف +. العدد الافتراضي هو 4096 حرفًا.
هناك ثلاثة أسباب رئيسية لاختيار +.
أولاً، + هو حرف URI قانوني، وعادةً لا يتم قطعه أو تغييره قسرًا مثل المسافات أو # عند استخدام عميل HTTP عادي. لذلك لا يحتاج PoC الحالي إلى استخدام raw socket لبناء سطر طلب غير قانوني كما في بعض ثغرات تجاوز request-target.
ثانيًا، توفر الأحرف المتكررة بكثافة إدخالًا طويلًا لمجموعات التقاط rewrite، مما يوسع نطاق إخراج التوصيل أو الترميز اللاحق لـ replacement، مما يسهل تشغيل عدم التطابق بين حساب الطول والكتابة الفعلية.
ثالثًا، بنية حمولة + المتكررة بسيطة، مما يسهل ملاحظتها في التقاط الحزم والسجلات وقواعد IDS، ويسهل ضبط الطول لاختبار العتبات.
ومع ذلك، تجدر الإشارة إلى أن + ليس الحرف الوحيد النظري لتشغيل الثغرة. الشرط الحقيقي للتشغيل لا يزال هو "ضرب تكوين rewrite ضعيف + إدخال يمكنه دخول مجموعات التقاط ذات الصلة + معالجة إخراج rewrite تؤدي إلى تجاوز سعة الكومة". في بيئات مختلفة، قد يحتاج مسار التشغيل ونوع الحرف وعتبة الطول إلى التعديل.
يختار PoC الحالي افتراضيًا عددًا كبيرًا من + كحرف تشغيل، لكن هذا لا يعني أن + فقط هو القادر على تشغيل المشكلة. + هو فقط أفضل حرف لكتابته في PoC عام، لأنه مستقر نسبيًا في URI، ويسهل إرساله بواسطة عميل HTTP عادي، ويكون واضحًا في التقاط الحزم.
من منظور مبدأ الثغرة، طالما أن الحرف يدخل في منطق الهروب NGX_ESCAPE_ARGS أثناء معالجة rewrite في NGINX، ويتوسع من 1 بايت إلى 3 بايت في شكل %XX، فقد يتسبب في اختلاف "قيمة حساب الطول أقل من قيمة الكتابة الفعلية". أي أن نقطة التشغيل ليست + بحد ذاتها، بل "ظهور كثيف للأحرف التي يمكن هروبها بواسطة نمط args".
بالإضافة إلى +، تشمل الأحرف التي يجب النظر فيها نظريًا:
المسافة: 0x20
#: 0x23
%: 0x25
&: 0x26
?: 0x3F
أحرف التحكم: 0x00-0x1F
البايتات العالية: 0x7F-0xFF
إذا دخلت هذه الأحرف في الالتقاط ذي الصلة وتمت معالجتها كهروب في محتوى args أثناء استبدال replacement، فسينتج تأثير توسع مماثل. على سبيل المثال:
+ -> %2B
& -> %26
% -> %25
# -> %23
? -> %3F
مسافة -> %20
كلما ظهر حرف من هذا النوع، من الناحية النظرية سيتوسع من 1 بايت إلى 3 بايت، مما يزيد طول الكتابة الفعلية بمقدار 2 بايت. إذا كان الإدخال يحتوي على عدد كبير من هذه الأحرف، فقد يتجاوز طول الكتابة الفعلية بشكل كبير طول المخزن المؤقت المحسوب بشكل خاطئ مسبقًا، مما يسهل تشغيل تجاوز سعة كومة الذاكرة.
ومع ذلك، فإن قابلية استخدام الأحرف المختلفة في PoC ليست متساوية تمامًا.
+ هو الأكثر استقرارًا. يمكن أن يظهر عادةً مباشرةً في request-target لـ HTTP، ولا يتم قطعه بسهولة بواسطة المتصفح أو أدوات سطر الأوامر، ولا يغير بنية مسار/استعلام URI بشكل طبيعي. لذلك يستخدم PoC الحالي 4096 حرفًا من + كحمولة افتراضية.
& يمكن أيضًا أن يكون حرفًا مرشحًا، لأنه في نمط args سيتم هربه إلى %26. لكن في shell، & له معنى تنفيذ الخلفية، وفي URL يُستخدم غالبًا كفاصل معاملات الاستعلام، لذا يجب الانتباه إلى الاقتباس والموضع عند الاختبار، وإلا فقد لا يتم إرسال الطلب كما هو متوقع.
% يمكن أيضًا أن يكون حرفًا مرشحًا، حيث سيتم هربه إلى %25. لكن % بحد ذاته هو بادئة ترميز URL، وقد تحاول بعض العملاء أو الوسطاء أو الأطر تفسير تسلسل %XX. إذا لم يتم إنشاؤه بشكل صحيح، فقد لا يستقبل الهدف الحرف % الأصلي، بل محتوى معالج مسبقًا من قبل العميل.
? نظريًا هو أيضًا حرف قابل للهروب، لكنه في request-target لـ HTTP يفصل بين المسار والاستعلام. إذا تم وضعه مباشرةً في المسار، فقد يتم تحليل المحتوى اللاحق كسلسلة استعلام، مما يغير نطاق التقاط rewrite. لذلك هو أكثر ملاءمة كحرف اختبار تكميلي، وليس كحرف رئيسي افتراضي في PoC.
# نظريًا يمكن أن يشغل الهروب، لكن المتصفح لا يرسل # وما بعده (جزء fragment) إلى الخادم، والعديد من عملاء HTTP المتقدمين سيقومون بترميزه أو قطعه. لذلك لاختبار حرف # حرفيًا، عادةً ما تحتاج إلى raw socket أو Burp Repeater أو أداة يمكنها الاحتفاظ بـ request-target الأصلي، ولا يمكن الاعتماد مباشرةً على شريط عنوان المتصفح.
المسافة 0x20 هي أيضًا حرف هروب، لكن في سطر طلب HTTP/1.1 العادي، المسافة نفسها هي فاصل، ووضعها مباشرةً في request-target سيدمر بنية سطر الطلب. أثناء الاختبار الفعلي، إذا كتبت كـ %20، فإن ما يراه الخادم في مرحلة المعالجة يعتمد على ما إذا كان قد تم فك ترميزه أو لا، وكذلك على موقعه في rewrite. لذلك المسافة أكثر ملاءمة لشرح المبدأ والاختبار المساعد، وليس كحمولة افتراضية.
أحرف التحكم 0x00-0x1F والبايتات العالية 0x7F-0xFF تقع أيضًا ضمن نطاق الهروب، لكنها في سلسلة HTTP الحقيقية更容易被 العملاء أو الوسطاء أو WAF أو محلل HTTP في NGINX اعتراضها أو تطبيعها أو رفضها. يمكن استخدامها كشرح لكائنات الهروب على مستوى المصدر، لكن لا ينصح باستخدامها كأحرف تشغيل افتراضية في PoC العادي.