حماية خوادم الألعاب من ثغرة php2shell: دليل التصحيح خلال 6 ساعات
باختصار
اكتشف كيف تحمي خوادم ألعابك من ثغرة php2shell: دليل تصحيح من 6 خطوات يشمل حصر بيئات PHP، ترقية بيئة التشغيل، تقليص سطح الهجوم، وكشف الاستغلال.
من المحتمل أن تكون طبقة PHP في لعبتك هي الجزء الذي لا ينظر إليه أحد في بنيتك التقنية. متجر الويب الذي يبيع العناصر التجميلية. لوحة الإدارة التي يستخدمها مدير مجتمعك. نقطة نهاية لوحة الصدارة القديمة التي كتبتها في 2022 ولم تلمسها مرة أخرى. هذا هو السطح الذي تهاجمه php2shell.
في 21 أكتوبر 2026، كشفت Calif.io وWiz وOpenAI وQuarkslab عن ثغرتين في بيئة تشغيل PHP، تُعرفان بـ CVE-2026-109618 وCVE-2026-109619، وتُسميان معًا php2shell. تمنحهما معايير CVSS v4.0 درجة 9.2. تؤثران على كل تثبيت PHP من الإصدار 5.4 فصاعدًا، وتسمحان بتنفيذ تعليمات برمجية عن بُعد دون مصادقة، وكشف البيانات، وحجب الخدمة. وقد رصدت Cloudflare حركة هجوم حية تُحوّر الحمولات ضد عملاء حقيقيين حتى قبل الإفصاح العام.
هذه ليست مشكلة إضافة ووردبريس. إنها خلل في سلامة الذاكرة في بيئة تشغيل PHP نفسها. تحديث إطار العمل أو نظام إدارة المحتوى أو تبعيات Composer لن يفيد. إذا كنت تدير خادم لعبة يحتوي على أي PHP، فهذا الدليل موجّه إليك.
ما الذي تكسره php2shell فعليًا؟
تكمن الثغرتان في كيفية تعامل PHP مع الذاكرة عندما تسير معالجة الطلبات في الاتجاه الخاطئ. عادةً، تخصص PHP ذاكرة للطلب، وتستخدمها، وتحررها عند انتهاء الطلب. تظهر الخلل عندما يعمل مسار التنظيف هذا على حالة لم تكن صالحة أصلًا.
CVE-2026-109618: تلف الذاكرة عند استنفادها
تصل PHP إلى سقف الذاكرة في منتصف الطلب وتتوقف قبل اكتمال تهيئة بعض البنى الداخلية. ثم تتعامل روتين التنظيف مع تلك البيانات نصف المبنية على أنها شرعية. المهاجم الذي يستطيع إجبار ظروف استنفاد الذاكرة (OOM) بشكل موثوق — أجسام طلبات كبيرة، مدخلات متداخلة بعمق، رفع ملفات ضخم — يحصل على فرصة للتحكم في محتوى تلك الذاكرة غير المكتملة.
المحفز العملي لخوادم الألعاب واضح: أي نقطة نهاية تقبل جسم طلب بدون حد أقصى للحجم. إرسال نتائج المباريات، رفع لقطات الشاشة، بيانات التعديلات، دفعات القياسات عن بُعد.
CVE-2026-109619: حالة عامة قديمة عبر الطلبات
خطأ فادح يقطع الطلب، تاركًا عملية PHP تحمل مراجع لذاكرة يتم تحريرها عند نهاية الطلب. عندما تلتقط نفس عملية العامل الطلب التالي، قد تتبع تلك المراجع المعلّقة. هذا هو كشف البيانات — بيانات جلسة لاعب أو بيانات اعتماد قاعدة بيانات تتسرب إلى استجابة لاعب آخر — وفي ظروف معينة، تنفيذ تعليمات برمجية.
هذا هو الخلل الأكثر خبثًا لخوادم الألعاب لأن عمال PHP-FPM طويلي العمر بطبيعة التصميم. عامل واحد مسموم يمكن أن يخدم مئات الطلبات اللاحقة قبل إعادة تدويره. إذا كنت تشغّل pm.max_requests = 0 (الافتراضي، أي عدم إعادة التدوير أبدًا)، يمكن للعامل التالف أن يستمر طوال عمر الحاوية.
الخطوة 1: حصر كل بيئة تشغيل PHP تملكها
لا يمكنك تصحيح ما لم تجده. تكتشف معظم الاستوديوهات، أثناء الحادث، أن ثلاثًا من خدماتها تعمل بـ PHP وأن إحداها على الإصدار 8.1 من صورة أساسية بُنيت قبل عامين. شغّل هذا قبل أي شيء آخر.
#!/usr/bin/env bash
# php2shell exposure inventory (CVE-2026-109618 / CVE-2026-109619)
set -euo pipefail
declare -A FIXED=( [8.5]="8.5.12" [8.4]="8.4.27" [8.3]="8.3.36" [8.2]="8.2.35" )
SERVICES=("game-api" "web-store" "admin-panel" "forums")
for svc in "${SERVICES[@]}"; do
ver=$(docker compose exec -T "$svc" php -r 'echo PHP_VERSION;' 2>/dev/null | tr -d '[:space:]') || {
echo "SKIP $svc (no php binary)"; continue; }
branch="${ver%.*}" # "8.3.12" -> "8.3"
min="${FIXED[$branch]:-}"
if [[ -z "$min" ]]; then
echo "EOL $svc on $ver -> no upstream patch, upgrade required"
elif php -r 'exit(version_compare($argv[1], $argv[2], ">=") ? 0 : 1);' "$ver" "$min"; then
echo "OK $svc on $ver"
else
echo "VULN $svc on $ver (fixed in $min)"
fi
done
يقوم هذا السكربت عمدًا بأمرين: يعلّم فروع نهاية الدعم (EOL) بشكل منفصل، ولا يفترض أبدًا أن php -v على المضيف يعكس بيئة التشغيل الفعلية للحاوية. إذا كنت تستخدم Kubernetes، استبدل docker compose exec بـ kubectl exec وكرر على عمليات النشر بدلاً من أسماء الخدمات.
الإصدارات المصححة من المنبع هي 8.5.12 و8.4.27 و8.3.36 و8.2.35. أي شيء على 8.1 أو 8.0 أو 7.x أو 5.x هو في نهاية الدعم ولن يحصل على إصلاح أبدًا. أنت تقوم بالترقية، وليس التصحيح.
الخطوة 2: صحيح بيئة التشغيل، وليس التطبيق
نمط الفشل الأكثر شيوعًا في الساعة الأولى من حادث كهذا هو فريق يحدّث كود التطبيق، ويعيد النشر، ويرى أن CI أخضر، ويفترض أن المهمة انتهت. وهي لم تنتهِ.
- PHP-FPM وmod_php: أعد بناء الصورة الأساسية أو حدّث الحزمة. Debian/Ubuntu:
apt-get install --only-upgrade php8.3-fpm php8.3-cli php8.3-common. Alpine:apk upgrade php83. تحقق باستخدامphp -r 'echo PHP_VERSION;'داخل الحاوية قيد التشغيل، وليس على مضيف البناء. - FrankenPHP / php-cgi: هذه تشمل بيئة التشغيل، لذا تحتاج إلى ثنائي إصدار جديد أو صورة أساسية جديدة، وليس تحديث حزمة.
- الاستضافة المُدارة: إذا كان المزود يتحكم في PHP، افتح تذكرة فورًا واطلب مرجع النشرة الأمنية. لا تقبل "نحن على علم" كحل.
- صور Docker الأساسية: ثبّت الإصدار بالـ digest وليس بالوسم (tag).
php:8.3-fpmقد يُحل إلى بناء مختلف عند السحب التالي، وهذا رائع للتصحيح وسيئ لإعادة الإنتاجية. ثبّت الـ digest بعد التحقق من الإصدار.
بعد التصحيح، اقتل كل عامل موجود. إعادة التشغيل المتدرجة التي تترك العمليات القديمة حية على مقبس مشترك تُبقي العمال الضعفاء يخدمون الحركة. استخدم docker compose restart أو kubectl rollout restart — وتأكد من تغير وقت بدء العملية.
الخطوة 3: قلّص نطاق الانفجار بينما تنتظر
التصحيح يستغرق وقتًا. لديك نافذة بين «نعلم» و«إعادة بناء كل بيئة». استغلها.
حدّد أجسام الطلبات عند الحافة
# Shrink the php2shell trigger surface
client_max_body_size 2m; # game APIs rarely need more than this
client_body_timeout 10s;
client_header_timeout 10s;
large_client_header_buffers 4 8k;
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass php-fpm:9000;
fastcgi_read_timeout 15s;
fastcgi_buffering on;
}
حد 2 ميجابايت لا يوقف مهاجمًا مصممًا — يمكنه تحفيز OOM بالعديد من الطلبات الصغيرة — لكنه يزيل المحفز التافه للطلب الواحد ولا يكلفك شيئًا.
أعد تدوير العمال بقوة
; /usr/local/etc/php-fpm.d/zz-php2shell.conf
php_admin_value[memory_limit] = 256M
php_admin_value[max_execution_time] = 20
pm.max_requests = 500
pm.max_children = 24
request_terminate_timeout = 20s
إليك حسابات المفاضلة. عند 200 طلب في الثانية عبر 24 عاملًا، يعالج كل عامل حوالي 8.3 طلب/ثانية. مع pm.max_requests = 500، يُعاد تدوير كل عامل كل ~60 ثانية. هذا يحد من المدة التي يمكن لعملية ملوثة بـ CVE-2026-109619 أن تخدم الحركة، ولكنه يعني أيضًا 24 ولادة عملية في الدقيقة — حمل قابل للقياس على الأطر الثقيلة في بدء التشغيل.
رفع memory_limit من 128M إلى 256M يقلل من عدد مرات الوصول إلى OOM (محفزات أقل لـ CVE-2026-109618) لكنه يضاعف الذاكرة في أسوأ الحالات: 24 عملية فرعية × 256M = 6 جيجابايت من المساحة التي تحتاجها الآن على العقدة. إذا كانت عقدتك تحتوي على 4 جيجابايت، اخفض pm.max_children بدلاً من التظاهر بأن الذاكرة موجودة.
لا شيء من هذا إصلاح. إنه يشتري لك ساعات. تعامل معه كفرز طبي.
الخطوة 4: اكتشف ما إذا كنت قد تعرضت للهجوم بالفعل
الإفصاح لا يعني أول استغلال. لوحظت حركة هجوم قبل تاريخ الإصدار الرسمي. افترض أنك كنت مستهدفًا.
# 1. OOM aborts — strongest single signal for CVE-2026-109618
grep -c "Allowed memory size" /var/log/php-fpm/error.log
# 2. Worker crashes — signal for both CVEs
journalctl -u php8.3-fpm --since "48 hours ago" \
| grep -Ei "segfault|SIGSEGV|exited on signal 11"
# 3. Which paths are generating 500s
awk '$9 == 500 {print $7}' /var/log/nginx/access.log \
| sort | uniq -c | sort -rn | head -20
# 4. Same source IP hitting the same 500-ing path repeatedly
awk '$9 == 500 {print $1, $7}' /var/log/nginx/access.log \
| sort | uniq -c | sort -rn | head -20
حدد خط الأساس أولاً. إذا كان سجل PHP-FPM لديك يحتوي بالفعل على 40 إيقاف OOM يوميًا من نقطة نهاية تصدير تستهلك الذاكرة، فهذا الرقم مجرد ضوضاء. ما تبحث عنه هو تغيّر في المعدل — قفزة في مخارج الإشارة 11، ظهور مسار جديد في قائمة 500، أو عنوان IP واحد يولّد عشرات الإخفاقات ضد نقطة نهاية واحدة.
إذا وجدت دليلًا على استغلال ناجح، تعامل معه كاختراق كامل: بدّل بيانات اعتماد قاعدة البيانات، ومفاتيح توقيع الجلسات، وأسرار مزود الدفع، وأي مفاتيح API يمكن الوصول إليها من عملية PHP. دليل التعامل مع ذلك ليس خاصًا بـ PHP — نفس الانضباط الذي يمكّن الاستوديو من تصميم خوادم ألعاب تنجو من الاختراقات ينطبق هنا، ويبدأ بافتراض أن المهاجم كان لديه تنفيذ تعليمات برمجية، وليس مجرد وصول للقراءة.
إذا كان خادمك يسقط الطلبات بالفعل وكنت غير متأكد ما إذا كان الحمل أو هجومًا، فإن ترتيب فرز الأعطال في بروتوكول تشخيص تجمّد الخادم ينطبق: اعزل الطبقة الفاشلة قبل أن تلمس الإعدادات.
لماذا فشلت قواعد WAF القائمة على الطلبات فقط
تقرير Cloudflare يحتوي على الدرس الهندسي الأكثر قابلية للنقل في الحادثة بأكملها. كانت حماياتهم الأولية قائمة على الطلبات، ورد المهاجمون بتحوير الحمولات — مستكشفين أي المتغيرات تطابق وأيها تسلل، مستخدمين إشارة التطابق/عدم التطابق كوسيلة استدلال.
بيانات الطلب وحدها لم تستطع فصل حركة الاستغلال عن الحركة الشرعية، لأن POST كبير بمصفوفات متداخلة هو أيضًا ما يرسله عميل لعبة عادي عند إرسال ملخص مباراة. الإشارة التي حسمت الغموض كانت الاستجابة: هل أعاد المصدر 500 مع تتبع خطأ PHP فادح؟ هذا المزيج — طلب مشبوه واستجابة تشخيصية — موثوقية عالية. الطلب وحده ليس كذلك.
أصدرت Cloudflare هذا باسم «كشف المعاملات الكاملة»، حيث تربط الطلب والاستجابة داخل نفس معاملة HTTP وتحجب الاستجابة قبل وصولها إلى الطالب. إذا كنت تدير حافة أو بروكسي عكسي خاص بك، فالنمط يستحق النسخ:
def is_probable_php2shell(resp) -> bool:
"""Correlate request shape with origin response, not request alone."""
body = resp.body[:4096]
return (
resp.status == 500
and b"Fatal error" in body
and resp.origin_latency_ms < 50 # exploit aborts fast
and b"Allowed memory size" in body # OOM path = 109618 signature
)
الدرس يتعمم بعد هذه الثغرة: أي نظام كشف يرى حركة الدخول فقط سيتجاوزه مهاجم يستطيع رؤية النتيجة الصادرة. سجّل الاستجابات. اربطها. هذا هو الفرق بين قاعدة تصمد وقاعدة تتحول إلى لغز.
أفضل الممارسات
- صحيح وفق جدول زمني، وليس عند الإفصاح. الفجوة بين «نشر CVE» و«استغلال في البرية» تُقاس الآن بالساعات، وأحيانًا تكون سلبية. أعد بناء صور PHP الأساسية أسبوعيًا حتى عندما لا يُعلن عن شيء، واحتفظ بمسار استرجاع.
- لا تشغّل أبدًا
pm.max_requests = 0في الإنتاج. عمر العامل غير المحدود يحول كل خلل في سلامة الذاكرة إلى موطئ قدم دائم. 500–1000 هو افتراضي معقول لواجهات برمجة تطبيقات الألعاب. - حدد كل جسم طلب عند الحافة. 2 ميجابايت لواجهات JSON، و10 ميجابايت للرفع، وارفض أي شيء بدون نوع محتوى مطابق. هذا يقتل فئات كاملة من استغلالات ضغط الذاكرة قبل وصولها إلى PHP.
- سجّل أجسام الاستجابات لأخطاء 5xx، بعينة. لا يمكنك بناء كشف المعاملات الكاملة إذا كانت استجابات المصدر غير مرئية لمكدس المراقبة لديك. خذ عينة 10% واحتفظ بها 7 أيام.
- احتفظ بحصر لكل بيئة تشغيل تديرها. ليس PHP فقط — Node وPython وGo وأي شيء يشغّل طبقة الويب لديك. الاستوديوهات التي استجابت لـ php2shell في أقل من ساعة كان لديها كتالوج خدمات. أما تلك التي استغرقت يومًا فكانت تبحث في ملفات Dockerfile.
الدرس الأكبر: طبقة الويب لديك هي سطح الهجوم
php2shell تذكير بأن خوادم الألعاب تتراكم عليها الأكواد القديمة. تطلق لوحة إدارة PHP في السنة الأولى لأن بنائها كان سريعًا. في السنة الثالثة، ما زالت موجودة، على 8.1، تحمل بيانات اعتماد قاعدة بيانات بصلاحية كتابة على جدول اللاعبين — وهي الشيء الذي يُخترق، وليس ثنائي خادم اللعبة المحصّن.
الإصلاح الهيكلي هو تقليل عدد بيئات التشغيل التي تملكها. إذا كان السبب الوحيد لتشغيل طبقة ويب PHP هو التعامل مع حسابات اللاعبين والسحابات السحابية ولوحات الصدارة، فهذه بالضبط هي الإمكانيات التي يوفّرها horizOn كواجهات برمجية مُدارة — مصادقة المستخدم النهائي بجلسات مرتبطة بالحساب، وحفظ سحابي بقراءات وكتابات تراعي المراجعات حيث يحل العميل التعارضات، ولوحات الصدارة، والإعداد عن بُعد. لا يوجد تجمع PHP-FPM لتصحيحه في الثالثة فجرًا لأنه لا يوجد تجمع PHP-FPM.
هذه ليست حجة بأن الخدمات المُدارة محصنة ضد الثغرات. إنها حجة بأن كل بيئة تشغيل تديرها هي بيئة يجب أن تحصرها وتصححها وتراقبها — وأن معظم الاستوديوهات لديها منها أكثر مما يعتقدون.
افعل هذا الآن
إذا اتخذت إجراءً واحدًا من هذا المقال، فليكن سكربت الحصر في الخطوة 1. شغّله على كل بيئة — الإنتاج، والاختبار، وخادم العرض التجريبي المنسي من عرض الناشر. ستجد شبه مؤكد بيئة تشغيل نسيتها، وستكون شبه مؤكد على فرع نهاية الدعم.
ثم صحيح، وأعد تدوير العمال، واترك قواعد الحافة في مكانها. قواعد WAF وحدود الأجسام هي دفاع متعمق، وليست بديلاً عن إصلاح المورد — نشرة PHP الأمنية هي مصدر الحقيقة وهي الشيء الذي يغلق الثغرة فعليًا.
إذا أظهر الحصر PHP أكثر مما تريد امتلاكه مستقبلًا، فإن horizOn يغطي طبقة الحسابات والحفظ ولوحات الصدارة كخدمات مُدارة، وهي أسرع طريقة لتقليص السطح المسؤول عن تصحيحه. في كل الأحوال: الحصر أولاً، والتصحيح ثانيًا، وتوقف عن افتراض أن الجزء الممل من بنيتك هو الجزء الآمن.