
PHP CVE-2026-109618/109619 — تنفيذ أوامر عن بُعد (RCE) مكتفٍ ذاتيًا دون مصادقة على PHP 8.5.11 القياسي (الكشف عن الذاكرة عبر PS(id)→cookie في النطاق نفسه + سلسلة UAF)، سلالم تحكم كاملة، ضابط سلبي للنسخة المُصلحة على 8.5.12
حزمة إعادة إنتاج المختبر وأبحاث الثغرات لفئة php2shell في PHP 8.5.11 (CVE-2026-109618، CVE-2026-109619)، أكتوبر 2026. الحالة: مُثبت — RCE مكتفٍ ذاتيًا (أربعة طلبات HTTP عادية، دون مُصحِّح أخطاء، ودون وصول محلي)، بالإضافة إلى نتائج التضخيم وفجوات الترقيع والحالة العالقة في FPM أدناه.
المحتويات: النتائج · السلاسل · التضخيم · فجوات الترقيع · الحالة العالقة في FPM · تجاوز WAF · البيئة · البدء السريع · انضباط الإثبات · التخطيط · السياق العام · البيان
| # | النتيجة | التصنيف |
|---|---|---|
| 1 | RCE مكتفٍ ذاتيًا من طرف إلى طرف على PHP 8.5.11 — أربعة طلبات HTTP عادية، واستخراج العنوان داخل النطاق عبر PS(id)→الكشف عبر الكوكيز، وكلا القاعدتين من تسريب PHPSESSID واحد بحجم 512 بايت؛ 12/12 إطلاق ناجح، uid=33، 8 أجيال ASLR | مُثبت — RCE مكتفٍ ذاتيًا |
| 2 | تضخيم ذاكرة بمقدار 52 ضعفًا عبر ترويسات multipart مكررة بحجم 6 بايت — يتغلب على post_max_size (جسم بحجم 2.57 ميغابايت يُفرغ حدًا قدره 128M) على PHP 7.4–8.5.11 | مُثبت — مختبر، جميع الإصدارات الأربع |
| 3 | ثلاثة مسارات استغلال تنجو من ترقيع الأمان 8.5.12 (UAF لإعادة دخول var_destroy عند n=1015، وعدم تطابق معالج serialize، وحقن تقدم الرفع) | مُثبت — ASAN على 8.5.12 المُتحقق منه بـ GPG |
| 4 | الحالة العالقة BG(unserialize) في FPM: ينجو العامل من OOM فك ترميز الجلسة، وتُستهلك الحالة العالقة بواسطة نافذة rfc1867 للطلب التالي، والاستهلاك المُشكَّل على الكومة يُسبب SIGSEGV للعامل | مُثبت — الآلية + DoS؛ RCE غير مُثبت |
| 5 | تجاوز فاصل Content-Type (multipart/form-data, boundary=X) — كل توقيع WAF منشور يعتمد على Content-Type قابل للتجاوز | مُثبت — سلوك محلل SAPI.c |
| 6 | كلا سلسلتي CVE ميتتان على 8.5.12 المُرقَّع (configure متطابق الرموز، مصدر مُتحقق منه بـ GPG) | مُثبت — ضابط سلبي |
| 7 | نظرية "cached-zval session.upload_progress" العامة — دُحضت على 4 إصدارات PHP قياسية + 3 إصدارات من بائع LiteSpeed، وجميع زوايا التنظيف والتزامن | مدحوضة — استقصاء شامل |
تصل سلسلتان مستقلتان لتلف الذاكرة إلى استدعاءات غير مباشرة يتحكم بها المهاجم
على صورة php:8.5.11-apache القياسية؛ ويجمع الغلاف من طرف إلى طرف
آلية السلسلة B مع كشف داخل النطاق جديد لإطلاق عن بُعد بالكامل.
| السلسلة A — دلو عالق | السلسلة B — مرجع معلّق | |
|---|---|---|
| الآلية | يُخلي OOM عن دلو HashTable منشور لكن غير مُهيأ؛ يُستهلك عند الإغلاق كـ zval مزيف → مصفوفة مزيفة → pDestructor | يُخلي OOM قبل RINIT عن PS(http_session_vars) وهو يحمل zend_reference معلّقًا؛ سلسلة حية بحجم 31 بايت تستعيده؛ مرجع مُزوَّر → zend_hash_destroy pDestructor |
| المخصص | متتبع/نظام (USE_ZEND_ALLOC=0) | Zend MM الافتراضي القياسي |
| الطرفية | system() من libc (عناوين مُعايَرة بـ gdb) | zif_system @ libphp+0x5996e0 (دون libc) |
| مُصحِّح الأخطاء وقت الإطلاق | للمعايرة فقط | لا شيء — FIRE_GDB=0 |
| قابلية إعادة الإنتاج | جيلان أبويان عشوائيان | متكررة، بما في ذلك عبر التشغيلات |
Leak-A يجهّز PS(id) معلّقًا (المتغير رقم 1001 يتجاوز max_input_vars →
headers_sent=1؛ فشل reset_id بالكوكي الفارغ يُبقي send_cookie=1؛
حقل بحجم 24 MiB يسبب OOM قبل RINIT). Leak-B يزرع مصفوفة من 257 عنصرًا
حيث يتداخل zend_array الخاص بها مع PS(id)−0x10: يقرأ ZSTR_LEN قيمة
nTableSize=512، والكوكي المُرمّز بـ URL يحمل pDestructor (→ قاعدة libphp)
بالإضافة إلى تسعة مؤشرات كومة arData (تصويت الأغلبية → قاعدة الـ chunk). الفرق
المقاس بين chunk وlibphp ليس ثابتًا عبر الأجيال (−0x1a00000 / +0x3600000 / −0x1c00000)،
لذا فإن الكشف حامل فعليًا للحمل. ثم يُنفَّذ الحريق بالعناوين
المسرّبة. المتطلبات المسبقة: display_errors=On، وpost_max_size أعلى
من حقل OOM، وmemory_limit أقل منه، وprefork أحادي العامل.
A status='HTTP/1.1 200 OK' → OOM fatal, worker survives, PS(id) armed
B Set-Cookie: PHPSESSID=<512-byte encoded disclosure>
FIRE → MARKER=uid=33(www-data) gid=33(www-data) groups=33(www-data)
FIRE_OK (×12 across 8 distinct ASLR layouts)
سجل الإيصال الكامل وسلّم التحكم: docs/END-TO-END.md.
تضخيم الرؤوس المكررة المُحسَّن: رؤوس X: a القصيرة (6 بايت على الشبكة)
تستغل حجم SMART_STR_START_SIZE الثابت البالغ 256 بايت في PHP لكل رأس محفوظ
(~314 بايت من الذاكرة لكل 6 بايت على الشبكة). قيس ذلك عبر تنصيف دقيق لعتبة OOM على
8.5.11؛ مطابق على 7.4.33 / 8.1.34 / 8.4.26 (51–52x):
| memory_limit | أدنى جسم OOM | التضخيم |
|---|---|---|
| 8M | 153 KB | 54.8x |
| 32M | 636 KB | 52.8x |
| 128M | 2.57 MB | 52.3x |
| 256M | 5.14 MB | 52.2x |
جميع الأجسام تتسع ضمن post_max_size=8M الافتراضي — حاجز الرفع العملي قد سُقط
على كل إصدار غير مُرقَّع. كما أن التأطير المُجزَّأ (chunked)
يتجاوز فحص Content-Length المسبق (8.85 MB مُجزَّأة تم تحليلها
وحدث لها OOM حيث رُفض نفس الجسم مع Content-Length). تُغلق رقعة
8.5.12 كلا المتجهين (تصفية الرؤوس المجهولة + فرض post_max_size بالبث).
شكل "القنبلة" ذو المصفوفة المتداخلة لا يتكرر كأُسّي (بحد أقصى 9.6x، 1.16 MiB — لا يمكنه إحداث OOM حتى عند 8M).
التفاصيل: docs/RELEASE-RECONCILIATION-2026-10-21.md.
تم التحقق على بناء ASAN 8.5.12 موثّق بـ GPG ومطابق بالرمز — الثغرتان CVE مُصلحتان، لكن ثلاثة مسارات منفصلة للتأثير تنجو من الرقعة:
var_unserializer.c:906:67)؛ حواجز
ZEND_HAS_OOM في الرقعة خاملة على هذا المسار؛ ولا يزال BG(serialize_lock)
غير مأخوذ حول إرسال __destruct.| مكتوبة بـ php_serialize
يفكّها معالج php بعد session_start() في مرحلة التطبيق، فتُنشئ الأداة (gadget) على mod_php php_value وFPM .user.ini على حد سواء؛
حاجز incomplete_classes_only في الرقعة محصور في نافذة الرفع
قبل RINIT فقط.تم التحقق مختبريًا على php:8.5.11-fpm: جلسة بحجم 24 MB عند memory_limit=32M
تُحدث OOM داخل php_var_unserialize وينجو العامل — حاجز الخروج-70
ينطبق فقط على OOM عند بدء التشغيل. يُظهر gdb أن BG(unserialize).level=1
ومؤشر البيانات القديم يستمران في نافذة rfc1867 قبل RINIT للطلب التالي؛
حقل multipart بحجم 8191 بايت يُنتج _emalloc بحجم 8216 بايت
يتداخل مع المؤشر القديم ويُسبب SIGSEGV للعامل في
var_tmp_var أثناء بدء الطلب. التأثير المقيس: انهيار عامل قابل للتكرار
(DoS) بالإضافة إلى بدائية إفساد ذاكرة؛ RCE بما يتجاوز الانهيار غير مُثبت.
المتطلبات المسبقة: memory_limit منخفض، جلسة كبيرة متأثرة بالمهاجم،
نقطة نهاية session_start()، upload_progress=On، و
pm=static max_children=1 لاقتران موثوق بنفس العامل. لذلك فإن FPM
محمي شرطيًا، لا بشكل قاطع، لهذه الفئة.
Content-Type: multipart/form-data, boundary=X (فاصل فاصلة) مقبول
من PHP — يقتطع SAPI.c عند ; و, والمسافة. كل توقيع WAF منشور
لـ php2shell مرتكز على Content-Type: multipart/form-data;
قابل للتجاوز. إرشادات المدافع في docs/WAF-BYPASS.md.
display_errors=Off، وmemory_limit المرتفع كلها
تبقى حواجز حقيقية — انظر دراسة الأسطول في
docs/RELEASE-RECONCILIATION-2026-10-21.md)؛