صُممت إعدادات PHP الافتراضية لتحقيق التوافق، لا لخادم مكشوف على الإنترنت. وهناك قائمة قصيرة من إعدادات php.ini ومجمّعات PHP-FPM تقلل ما يكشفه خادمك، وتحدّ مما يمكن لسكربت مخترق واحد أن يصل إليه. لا يغني أي منها عن تحديث PHP باستمرار أو إصلاح الشيفرة المعرضة للثغرات، واثنان من أشهرها، disable_functions وopen_basedir، أضعف مما توحي به أدلة كثيرة. كل توجيه أدناه موثق في دليل php.net، والمسارات خاصة بحزم ديبيان وأوبونتو مع PHP 8.4 مثالًا.
أين تضع الإعدادات؟
لكل واجهة ملفها الخاص، والموقع يقرأ /etc/php/8.4/fpm/php.ini لا ملف سطر الأوامر. وبعد php.ini يقرأ PHP كل ملفات .ini في مجلد الفحص /etc/php/8.4/fpm/conf.d/ بالترتيب الأبجدي، لذا فملف مثل 99-hardening.ini يبقي تعديلاتك منفصلة عن ملف الحزمة ويصمد بسلاسة أمام ترقيات الحزم. أما الإعدادات التي تختلف من موقع لآخر فمكانها ملف المجمّع عبر php_admin_value أو php_admin_flag، والقيم المضبوطة بهذه الطريقة لا تستطيع شيفرة التطبيق تجاوزها عبر ini_set().
افحص ما لديك قبل تغيير أي شيء. تبني ديبيان ملف php.ini الخاص بالويب من ملف php.ini-production الأصلي الذي يضبط مسبقًا display_errors = Off وlog_errors = On، ثم تحوّل ديبيان expose_php إلى Off وتضيف دوال pcntl_* إلى disable_functions:
grep -E '^(expose_php|display_errors|log_errors|disable_functions|allow_url_fopen)' \
/etc/php/8.4/fpm/php.ini /etc/php/8.4/fpm/conf.d/*.iniأوقف تسريب التفاصيل
; /etc/php/8.4/fpm/conf.d/99-hardening.ini
expose_php = Off
display_errors = Off
display_startup_errors = Off
log_errors = On
error_reporting = E_ALL & ~E_DEPRECATEDيتحكم expose_php في الترويسة X-Powered-By: PHP/x.y.z، ولا يُضبط إلا في php.ini. ويصف الدليل إخفاء PHP بأنه إجراء ثانوي من باب الأمن بالتعتيم، لكن لا داعي للإعلان عن إصدارك بدقة. أما display_errors فيجب أن يكون معطلًا في الإنتاج، لأن رسائل الخطأ تكشف مسارات الملفات والاستعلامات وأحيانًا بيانات الدخول. ومع تفعيل log_errors تُكتب الأخطاء في الملف المحدد في error_log، ويجب أن يكون قابلًا للكتابة من مستخدم المجمّع، لذا اضبطه لكل مجمّع كما سيأتي.
ملفات تعريف ارتباط أكثر أمانًا للجلسات
تنطبق هذه الإعدادات على وحدة الجلسات المدمجة في PHP (session_start())، أما الأطر التي تدير ملفات تعريف الارتباط بنفسها فلها إعدادات مكافئة في ملفات إعدادها.
session.use_strict_mode = 1
session.use_only_cookies = 1
session.cookie_httponly = 1
session.cookie_secure = 1
session.cookie_samesite = Laxالإعداد use_strict_mode معطل افتراضيًا، مع أن الدليل يصف تفعيله بأنه إلزامي لأمن الجلسات عمومًا، فهو يرفض معرّفات الجلسات التي لم ينشئها الخادم، فيمنع تثبيت الجلسة (session fixation) بأسلوب التبنّي. ويُبعد cookie_httponly ملف تعريف الارتباط عن جافاسكربت، ويجعل cookie_secure إرساله عبر HTTPS فقط، فلا تفعّله إلا إذا كان الموقع كله على HTTPS، ويمنع cookie_samesite = Lax إرساله مع طلبات POST القادمة من مواقع أخرى. أما use_only_cookies فمفعّل افتراضيًا، وتعطيله مهمل (deprecated) منذ PHP 8.4.
التضمين عن بُعد ورفع الملفات
allow_url_include = Off
file_uploads = On
upload_max_filesize = 16M
post_max_size = 20M
max_file_uploads = 20
memory_limit = 256M
max_execution_time = 30يجب أن يبقى allow_url_include معطلًا، وهو معطل افتراضيًا ومهمل منذ PHP 7.4، لأنه يسمح لـ include بتحميل شيفرة من عنوان URL. أما allow_url_fopen فمفعّل افتراضيًا ويتيح لدوال مثل file_get_contents() فتح عناوين URL، فلا تعطله إلا بعد الاختبار لأن بعض التطبيقات تعتمد عليه. واضبط حدود الرفع على ما يحتاجه التطبيق فعلًا: يجب أن يكون post_max_size أكبر من upload_max_filesize، وإذا تجاوز الطلب post_max_size يترك PHP المتغيرين $_POST و$_FILES فارغين. ويطبّق Nginx حده الخاص أولًا (client_max_body_size وقيمته الافتراضية 1m)، فارفع الحدين معًا. ولاحظ أنه ابتداءً من الإصدارات 8.5.12 و8.4.27 و8.3.36 و8.2.35 أصبح memory_limit حدًا مرنًا، مع حد صارم منفصل يتحكم فيه memory_hard_limit_percent.
disable_functions وopen_basedir: حواجز إرشادية لا جدران
يقبل disable_functions قائمة بدوال داخلية مفصولة بفواصل، ولا يُضبط إلا في php.ini. ومنذ PHP 8.0 تُزال الدالة المعطلة بالكامل. ويحذّر الدليل من إمكانية الالتفاف عليه وأنه لا يُعد إجراءً كافيًا لأمن الاستضافة المشتركة. وإذا استخدمته فانسخ قائمة ديبيان الحالية أولًا (فالملف اللاحق يستبدل القيمة السابقة)، ولا تضف دوال مثل exec,passthru,shell_exec,system,proc_open,popen إلا بعد التأكد من أن تطبيقك لا يستدعيها، وتذكّر أن لسطر الأوامر ملف php.ini خاصًا به. وفي ملف المجمّع يضيف php_admin_value[disable_functions] إلى قائمة php.ini ولا يستبدلها. أما التوجيه المرتبط disable_classes فقد أُزيل في PHP 8.5.
ويقصر open_basedir الوصول إلى الملفات على أشجار المجلدات المذكورة، المفصولة بنقطتين في لينكس، ويحلّ الروابط الرمزية. ويمكن للسكربت تضييقه أثناء التشغيل لكن لا يمكنه توسيعه. وتنص سياسة تصنيف الأمان الخاصة بمشروع PHP على أن المشكلات التي تتطلب تجاوز open_basedir لا تُعامل كمشكلات أمنية، فاستخدمه لالتقاط الأخطاء لا حدًّا فاصلًا بين العملاء. أما العزل الحقيقي فيأتي من تشغيل كل موقع بمستخدم لينكس خاص مع صلاحيات ملفات صارمة، أو في حاويات أو أجهزة افتراضية منفصلة.
مجمّع محصّن لكل موقع
; /etc/php/8.4/fpm/pool.d/shop.conf
[shop]
user = shop
group = shop
listen = /run/php/php8.4-fpm-shop.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
pm = ondemand
pm.max_children = 10
security.limit_extensions = .php
php_admin_value[error_log] = /var/www/shop/logs/php-error.log
php_admin_flag[log_errors] = on
php_admin_flag[display_errors] = off
php_admin_value[open_basedir] = /var/www/shop/:/var/lib/php/sessions/:/tmp/- مستخدم لكل موقع. يحدد السطران
userوgroupما يستطيع السكربت المخترق قراءته وكتابته. أبقِ مجلدlogsخارج جذر الموقع وقابلًا للكتابة من هذا المستخدم. - مقبس يونكس لا منفذ مفتوح. يحذّر دليل FPM من أن نقطة FastCGI المكشوفة تتيح تنفيذ شيفرة عشوائية. فضّل مقبس يونكس مع
listen.ownerوlisten.groupوlisten.mode. وإن اضطررت إلى TCP فاضبطlisten.allowed_clients، فهو غير مضبوط افتراضيًا وعندها يقبل أي عنوان، وفي الحاويات لا تنشر منفذ FPM على المضيف. security.limit_extensions = .phpيضيّق القيمة الافتراضية (.php .phar) حتى لا يتمكن خطأ في إعداد خادم الويب من جعل FPM ينفذ أنواع ملفات أخرى.clear_envقيمته الافتراضية yes، فأبقه كذلك حتى لا تصل متغيرات بيئة عشوائية إلى العمليات العاملة.- الجلسات وopen_basedir. تحفظ ديبيان الجلسات في
/var/lib/php/sessions، فأدرجها مع مجلد الرفع المؤقت عند ضبطopen_basedir، ثم اختبر تسجيل الدخول ورفع الملفات.
التطبيق والتحقق
sudo php-fpm8.4 -t
sudo systemctl restart php8.4-fpm
curl -sI https://example.com/ | grep -i x-powered-byغياب ترويسة X-Powered-By من PHP يعني أن expose_php معطل (مع أن بعض التطبيقات تضيف الترويسة بنفسها). وللتأكد من القيم الفعلية استخدم سكربتًا مؤقتًا باسم عشوائي يطبع ini_get('display_errors') وini_get('session.use_strict_mode') وما شابه، وافتحه مرة واحدة ثم احذفه. بعدها جرّب تسجيل الدخول ورفع الملفات وأي ميزة ترسل بريدًا أو تتصل بواجهات برمجية خارجية.
ما الذي ينبغي فعله
- ضع إعدادات الإنتاج في ملف داخل
conf.dالخاص بواجهة FPM، واحفظ القيم الخاصة بكل موقع في ملفات المجمّعات عبرphp_admin_value. - عطّل
display_errorsوexpose_php، وسجّل الأخطاء في ملف خاص بكل موقع خارج جذره. - فعّل
session.use_strict_modeوخصائص HttpOnly وSecure (في مواقع HTTPS) وSameSite لملفات تعريف الارتباط. - شغّل كل موقع في مجمّع ومستخدم لينكس خاصين على مقبس يونكس، مع
security.limit_extensions = .php. - تعامل مع
disable_functionsوopen_basedirكطبقات إضافية فقط، وأبقِ PHP نفسه محدّثًا، وإصدارات PHP الأمنية في أكتوبر 2026 تذكير بأن لا إعداد يغني عن التحديثات.
المصادر
- php.net/manual/en/ini.core.php
- php.net/manual/en/ini.list.php
- php.net/manual/en/errorfunc.configuration.php
- php.net/manual/en/session.configuration.php
- php.net/manual/en/session.security.ini.php
- php.net/manual/en/filesystem.configuration.php
- php.net/manual/en/install.fpm.configuration.php
- php.net/manual/en/configuration.file.php
- php.net/manual/en/security.hiding.php
- github.com/php/policies/blob/main/security-classification.rst
- github.com/php/php-src/blob/PHP-8.4/php.ini-production
- sources.debian.org/src/php8.4/8.4.27-1~deb13u1/debian/rules
- sources.debian.org/src/php8.4/8.4.27-1~deb13u1/debian/patches/0009-session_save_path.patch
- nginx.org/en/docs/http/ngx_http_core_module.html#client_max_body_size
- manpages.debian.org/trixie/php8.4-fpm/php-fpm8.4.8.en.html
Comments
0 total · 0 threads