حماية أدلة رفع الملفات
تُعد عمليات رفع الملفات من أخطر الميزات في تطبيق الويب، لأن المستخدمين يُسمح لهم عمدًا بإرسال ملفات إلى الخادم. وإذا كان منطق التحقق ضعيفًا، فقد يحاول المهاجم تمويه شيفرة قابلة للتنفيذ على أنها صورة أو مستند.
القاعدة الأولى التي أتبعها بسيطة: يجب ألا تكون الملفات المرفوعة قابلة للتنفيذ.
بالنسبة إلى دليل رفع ملفات قائم على Apache، يمكن حظر تنفيذ PHP باستخدام تهيئة مشابهة لما يلي:
<FilesMatch "\.(php|phtml|phar|php[0-9]*)$">
Require all denied
</FilesMatch>
اعتمادًا على بيئة الاستضافة، قد تكون هناك حاجة أيضًا إلى قيود إضافية على معالجات PHP. والهدف هو التأكد من أنه حتى إذا وصل نص برمجي ضار بطريقة ما إلى دليل الرفع، فإن الخادم يرفض تنفيذه.
كما أتجنب الوثوق باسم الملف الذي يقدمه المستخدم. قد يزعم المتصفح أن اسم الملف هو vacation.jpg، لكن ينبغي للتطبيق التحقق بشكل مستقل من نوع الملف وإنشاء اسم التخزين الخاص به.
قد تبدو آلية رفع أساسية في PHP على النحو التالي:
<?php
$allowedTypes = [
'image/jpeg' => 'jpg',
'image/png' => 'png',
'image/webp' => 'webp'
];
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime = $finfo->file($_FILES['upload']['tmp_name']);
if (!isset($allowedTypes[$mime])) {
throw new RuntimeException('Unsupported file type.');
}
$filename = bin2hex(random_bytes(16)) . '.' . $allowedTypes[$mime];
?>
الفكرة المهمة هي أن الخادم يحدد الامتداد بدلًا من الوثوق بأي امتداد قدمه المستخدم.
لا تثق أبدًا بأنواع MIME التي يرسلها المتصفح
يمكن التلاعب بنوع MIME المضمّن في عملية الرفع عبر HTTP. فإذا تلقى PHP ملفًا مرفوعًا يدّعي أنه image/jpeg، فلا ينبغي لهذه القيمة وحدها أن تحدد ما إذا كان الملف آمنًا.
يتيح استخدام finfo لـ PHP فحص المحتوى الفعلي للملف وتحديد نوع MIME أكثر موثوقية. وحتى عندئذ، ما زلت أتعامل مع الملفات المرفوعة بحذر.
بالنسبة إلى الصور، يتمثل أحد إجراءات الحماية الإضافية في فك ترميز الصورة وإعادة ترميزها فعليًا. فبدلًا من الاحتفاظ بالبيانات الثنائية الأصلية التي رفعها المستخدم، يمكن للتطبيق تحميل الصورة في مكتبة لمعالجة الصور وإنشاء ملف JPEG أو WebP جديد. ويؤدي ذلك إلى إزالة قدر كبير من البيانات غير المتوقعة التي ربما أُدرجت في الملف المرفوع الأصلي.
لهذا النهج فائدة أخرى. فهو يوحّد أبعاد الصور وضغطها وتنسيقها، مع تحسين الأمان في الوقت نفسه.
استخدم حسابات قواعد البيانات بأقل قدر من الصلاحيات
خطأ شائع آخر هو منح مستخدم قاعدة بيانات الموقع صلاحيات أكثر مما يحتاج إليه التطبيق فعليًا. فعادةً لا يحتاج الموقع العام إلى القدرة على إنشاء مستخدمي قواعد بيانات جدد، أو منح الصلاحيات، أو إدارة خادم MySQL بالكامل.
ينبغي أن يكون للتطبيق حساب قاعدة بيانات مخصص خاص به، مع الصلاحيات المطلوبة من التطبيق فقط.
قد يكون المثال المبسّط كما يلي:
CREATE USER 'website_app'@'localhost'
IDENTIFIED BY 'strong-random-password';
GRANT SELECT, INSERT, UPDATE, DELETE
ON production_database.*
TO 'website_app'@'localhost';
يعتمد احتياج التطبيق إلى صلاحيات إضافية على طريقة عمله، لكنني أتجنب منح تطبيق متاح للعامة صلاحيات إدارية كاملة ما لم يوجد سبب محدد لذلك.
يحد مبدأ أقل قدر من الصلاحيات من نطاق الأضرار الناتجة عن اختراق التطبيق. فإذا سُرِق حساب قاعدة بيانات الموقع، فلن يحصل المهاجم إلا على الصلاحيات الممنوحة لذلك الحساب، بدلًا من السيطرة على كل قاعدة بيانات على الخادم.
استخدم العبارات المُعدّة في كل مكان
لا يزال حقن SQL أحد أهم ثغرات تطبيقات الويب، لأنه قد يحوّل المدخلات التي يتحكم بها المستخدم إلى SQL قابل للتنفيذ. والحل ليس محاولة تهريب الأحرف المشبوهة يدويًا، بل فصل بنية SQL عن بيانات المستخدم.
مع PDO، أستخدم العبارات المُعدّة.
<?php
$stmt = $pdo->prepare(
'SELECT id, username, email
FROM users
WHERE email = :email'
);
$stmt->execute([
':email' => $email
]);
$user = $stmt->fetch();
?>
النقطة المهمة هي أن $email يتم التعامل معه باعتباره بيانات، بدلاً من دمجه في أمر SQL.
أتجنب كتابة تعليمات برمجية مثل هذه:
<?php
$sql = "SELECT * FROM users WHERE email = '$email'";
?>
حتى عندما يعتقد المطورون أنهم يتحكمون في الإدخال، تتغير التطبيقات بمرور الوقت. ينبغي أن تكون العبارات المُحضَّرة هي النمط الافتراضي، بدلاً من استخدامها فقط مع الحقول التي يُنظر إليها على أنها خطرة.
مصادقة آمنة للمسؤولين
تستحق واجهة المسؤول حماية أقوى من الجانب المواجه للعامة في الموقع الإلكتروني. وإذا أمكن، أستخدم المصادقة متعددة العوامل لحسابات المسؤولين. وكحد أدنى، ينبغي أن تكون كلمات مرور المسؤولين طويلة وفريدة، وأن تُخزَّن باستخدام دوال تجزئة كلمات المرور في PHP.
ينبغي أن يبدو تخزين كلمات المرور شبيهاً بهذا:
<?php
$hash = password_hash($password, PASSWORD_DEFAULT);
?>
ثم تستخدم المصادقة ما يلي:
<?php
if (password_verify($password, $storedHash)) {
// Successful authentication.
}
?>
يجب ألا تُخزَّن كلمات المرور مطلقاً بنص صريح، أو تُشفَّر باستخدام مفتاح قابل للعكس، أو تُجزَّأ باستخدام خوارزميات قديمة مثل MD5.
كما أتجنب الكشف عمّا إذا كان اسم مستخدم أو عنوان بريد إلكتروني معين موجوداً. وبدلاً من قول: «لا يوجد حساب مرتبط بعنوان البريد الإلكتروني هذا»، يمكن للتطبيق عرض رسالة عامة تفيد بفشل المصادقة.
يمنع هذا صفحة تسجيل الدخول من التحول إلى أداة لاكتشاف الحسابات.
تعزيز أمان جلسات PHP
لا ينتهي أمان المصادقة عند قبول كلمة المرور. فبمجرد تسجيل دخول المستخدم، تصبح ملف تعريف ارتباط الجلسة فعلياً بيانات اعتماد مؤقتة للمستخدم.
أضبط ملفات تعريف ارتباط الجلسة لاستخدام سمات الأمان عندما يعمل الموقع عبر HTTPS.
<?php
session_set_cookie_params([
'httponly' => true,
'secure' => true,
'samesite' => 'Lax'
]);
?>
تجعل السمة HttpOnly من الصعب على JavaScript من جانب العميل الوصول إلى ملف تعريف الارتباط. وتخبر السمة Secure المتصفح بإرساله عبر HTTPS فقط. ويمكن أن تساعد السمة SameSite في الحد من بعض هجمات الطلبات عبر المواقع.
بعد تسجيل دخول ناجح، أعيد إنشاء معرّف الجلسة.
<?php
session_regenerate_id(true);
?>
يقلل ذلك من خطر هجمات تثبيت الجلسة.
يمكن للجلسات الإدارية أيضًا استخدام مهلات زمنية لعدم النشاط. فإذا ابتعد مسؤول عن متصفح مسجّل الدخول لعدة ساعات، فلا ينبغي بالضرورة أن تظل الجلسة نشطة إلى أجل غير مسمى.
احمِ كل طلب يغيّر الحالة باستخدام رموز CSRF
تخدع هجمة تزوير الطلبات عبر المواقع متصفح مستخدم موثّقًا لإرسال طلب غير مرغوب فيه. تخيّل أن مسؤولًا سجّل دخوله إلى نظام إدارة المحتوى أثناء زيارته لموقع ويب ضار آخر. إذا لم يتحقق نظام إدارة المحتوى من مصدر الطلبات الحساسة، فقد تتمكن الصفحة الضارة من تشغيل إجراءات باستخدام جلسة المسؤول الموثّقة.
بالنسبة إلى النماذج التي تنشئ المعلومات أو تحدّثها أو تحذفها، أستخدم رموز CSRF.
يمكن إنشاء رمز وتخزينه في الجلسة:
<?php
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
?>
يُوضَع الرمز بعد ذلك داخل النموذج:
<input type="hidden"
name="csrf_token"
value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
عند إرسال النموذج، يتحقق التطبيق منه.
<?php
if (
empty($_POST['csrf_token']) ||
!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])
) {
http_response_code(403);
exit('Invalid request.');
}
?>
لا يحل الرمز محل المصادقة، بل يضيف خطوة تحقق إضافية تثبت أن الطلب نشأ من نموذج أنشأه التطبيق.
اهرب المخرجات، وليس المدخلات فقط
يركز المطورون أحيانًا بالكامل على تنقية البيانات الواردة، لكن معالجة المخرجات مهمة بالقدر نفسه. فقد تُعرَض قيمة مخزنة في قاعدة بيانات لاحقًا داخل HTML أو JavaScript أو سمة أو عنوان URL. ولكل سياق متطلبات مختلفة للهروب.
بالنسبة إلى مخرجات HTML العادية، أستخدم:
<?php
echo htmlspecialchars(
$value,
ENT_QUOTES | ENT_SUBSTITUTE,
'UTF-8'
);
?>
وهذا مهم خصوصًا للتعليقات وأسماء المستخدمين وعناوين المقالات وعمليات إرسال النماذج وأي بيانات أخرى نشأت خارج التطبيق.
قد يكون البرمجة النصية عبر المواقع المخزنة خطيرة بشكل خاص، لأن الحمولة الخبيثة تظل في قاعدة البيانات. وإذا ظهرت تلك الحمولة داخل لوحة تحكم المسؤول، فقد تُنفَّذ في كل مرة يعرض فيها المسؤول السجل المتأثر.
لا تكشف أخطاء PHP التفصيلية للزوار
تكون أخطاء التطوير مفيدة أثناء البرمجة، لكن يجب ألا يتلقى مستخدمو الإنتاج تتبعات المكدس أو أخطاء قاعدة البيانات أو مسارات نظام الملفات أو تفاصيل الإعدادات.
ينبغي عمومًا أن تتجنب PHP في بيئة الإنتاج عرض الأخطاء الداخلية مباشرةً في المتصفح.
display_errors = Off
log_errors = On
يجب بدلاً من ذلك كتابة الأخطاء في ملف سجل محمي.
قد يكشف استثناء قاعدة بيانات مثل هذا:
SQLSTATE[HY000] [1045] Access denied for user...
أسماء مستخدمي قاعدة البيانات، وإصدارات البرامج، والمسارات الداخلية، وبنية التطبيق. ولا حاجة إلى عرض أي من ذلك لزائر مجهول.
يمكن للمستخدم تلقي رسالة عامة، بينما يُسجَّل الخطأ الحقيقي بشكل خاص لأغراض تصحيح الأخطاء.
أنشئ سجلاً أمنيًا مفيدًا فعلًا
تُعد سجلات الوصول القياسية ذات قيمة، لكنني أحب أيضًا تسجيل الأحداث الأمنية على مستوى التطبيق. يمكن للتطبيق تسجيل الأحداث المشبوهة مثل حالات فشل تسجيل الدخول المتكررة، وأنواع الرفع المحظورة، ومحاولات الوصول إلى نقاط النهاية المحمية، ورموز CSRF غير الصالحة، والطلبات التي تستهدف ملفات يُفترض ألا تكون موجودة للعامة إطلاقًا.
قد يتضمن إدخال أساسي في السجل الأمني الطابع الزمني، وعنوان IP، ومعرّف URI للطلب، ووكيل المستخدم، ومعرّف المستخدم المُصادَق عليه إن توفر، والقاعدة التي أدت إلى إطلاق التنبيه.
على سبيل المثال:
<?php
$entry = sprintf(
"[%s] IP=%s URI=%s EVENT=%s\n",
date('c'),
$_SERVER['REMOTE_ADDR'] ?? 'unknown',
$_SERVER['REQUEST_URI'] ?? '',
'Blocked sensitive-file probe'
);
file_put_contents(
'/home/account/logs/security.log',
$entry,
FILE_APPEND | LOCK_EX
);
?>
يجب ألا تُخزَّن السجلات الأمنية داخل دليل يمكن تنزيل محتوياته للعامة. كما ينبغي أن تتجنب تسجيل الأسرار مثل كلمات المرور، وملفات تعريف الارتباط الكاملة للجلسات، وترويسات المصادقة، أو مفاتيح API الخاصة.
اكتشف طلبات الاستطلاع الشائعة
غالبًا ما يفحص المهاجمون المواقع بحثًا عن ملفات وتطبيقات شائعة بغض النظر عمّا إذا كانت تلك التطبيقات موجودة فعليًا. وقد يستمر موقع PHP مخصص في تلقي طلبات إلى /wp-login.php و/wp-admin و/.env و/.git/config و/phpmyadmin.
لا تعني هذه الطلبات بالضرورة أن المهاجم يعرف أي شيء عن الموقع. فالكثير من عمليات الفحص هذه مؤتمتة. ومع ذلك، يمكن أن يظل تسجيل هذه الطلبات مفيدًا لأنه يكشف أنماط السلوك المشبوه.
يمكن لـ Apache رفض مسارات حساسة معينة فورًا.
RedirectMatch 404 (?i)/\.git
RedirectMatch 404 (?i)/\.env
أفضل عمومًا إرجاع استجابة خطأ عادية بدلًا من كشف معلومات غير ضرورية حول ما إذا كان الملف موجودًا من قبل.
تحديد معدل محاولات المصادقة
ينبغي ألا تسمح صفحة تسجيل الدخول بعدد غير محدود من محاولات المصادقة بسرعة آلية. وحتى كلمات المرور القوية تستفيد من تحديد المعدل، لأنه يقلل من هجمات التخمين الآلية وهجمات حشو بيانات الاعتماد.
يمكن لتطبيق أساسي تتبّع محاولات تسجيل الدخول الفاشلة بحسب معرّف الحساب وعنوان IP. وبعد عدة محاولات فاشلة، يمكنه فرض فترة تهدئة مؤقتة.
يمكن أن يستخدم التنفيذ MySQL أو Redis أو ذاكرة تخزين مؤقت محلية أو آلية تخزين أخرى. ويهم النظام المحدد بدرجة أقل من المبدأ الأساسي: ينبغي أن تكون لمحاولات المصادقة تكلفة.
أتجنب عمليات قفل الحساب الدائمة التي تُفعّل بسبب المحاولات الفاشلة وحدها، لأن مهاجمًا قد يتعمد منع المستخدمين الشرعيين من الوصول إلى حساباتهم. وعادةً ما يكون التقييد المؤقت تصميمًا أفضل.
لا تثق في وكيل المستخدم أو عنوان IP باعتبارهما وسيلة للمصادقة
يمكن أن تكون عناوين IP وسلاسل وكيل المستخدم في المتصفح مفيدة للتسجيل واكتشاف الحالات الشاذة، لكن لا ينبغي أن يعمل أيٌّ منهما كآلية أساسية لتحديد الهوية. قد ينتقل المستخدمون بين شبكات الهاتف المحمول وشبكات Wi-Fi وشبكات VPN وعناوين IP المتغيرة خلال يوم واحد.
ينبغي لأنظمة الأمان استخدام هذه الإشارات باعتبارها سياقًا، وليس دليلًا قاطعًا.
على سبيل المثال، قد يبرر تسجيل دخول مفاجئ لمسؤول من دولة جديدة طلبَ مصادقة إضافية، لكن تغيير عنوان IP وحده لا ينبغي أن ينهي بالضرورة حسابًا شرعيًا.
افصل بين المسارات العامة ومسارات الإدارة
أفضل الفصل بوضوح بين مسارات التطبيق الموجهة للعامة ووظائف الإدارة. يجب ألا تعيد واجهة الإدارة استخدام وحدات التحكم العامة التي تكشف عن وظائف أكثر مما هو مقصود، ولو عن طريق الخطأ.
على سبيل المثال، قد يستخدم نظام إدارة المحتوى:
/admin/posts
/admin/users
/admin/settings
يجب أن يُجري كل مسار إداري فحصًا للتفويض على الخادم.
إخفاء رابط التنقل ليس تفويضًا. وJavaScript الذي يزيل زر الإدارة ليس تفويضًا. يجب أن يتلقى المستخدم رفضًا من جهة الخادم إذا أدخل يدويًا عنوان URL إداريًا من دون إذن.
هذا التمييز مهم للغاية. فأي شيء يُفرض داخل المتصفح فقط يمكن تجاوزه من حيث المبدأ.
استخدم التفويض القائم على الأدوار
تجيب المصادقة عن السؤال: «من هو هذا المستخدم؟» أما التفويض فيجيب عن السؤال: «ما الذي يُسمح لهذا المستخدم بفعله؟»
ينبغي لنظام يضم مسؤولين ومحررين ومساهمين وعملاء أو حسابات تجارية أن يحدد تلك الصلاحيات صراحةً.
قد يُسمح للمحرر بتعديل المقالات، لكن لا يُسمح له بإنشاء حسابات مسؤولين. وقد يتمكن موظف الدعم من عرض جلسات العملاء، لكن لا يتمكن من تغيير إعدادات الفوترة. ويجب ألا يحصل المستخدم العادي على حق الوصول إلى الإدارة الداخلية لمجرد أنه اكتشف نقطة نهاية لواجهة برمجة التطبيقات.
ينبغي لكل عملية حساسة أن تتحقق من التفويض بشكل مستقل.
تحتاج النسخ الاحتياطية إلى الأمان أيضًا
تُعد النسخ الاحتياطية ضرورية بعد حدوث اختراق، لكنها قد تتحول أيضًا إلى مخاطر أمنية. ينشئ المطورون أحيانًا ملفات بأسماء مثل:
database-backup.sql
website-old.zip
public_html-backup.tar.gz
config.php.bak
ويتركونها داخل دليل الويب العام.
قد تحتوي هذه الملفات على الشيفرة المصدرية، وسجلات قاعدة البيانات، وتجزئات كلمات المرور، وعناوين البريد الإلكتروني، وبيانات اعتماد واجهات برمجة التطبيقات، وغيرها من المعلومات السرية.
ينبغي تخزين النسخ الاحتياطية خارج جذر المستندات كلما أمكن. ومن الناحية المثالية، ينبغي أيضًا تشفير النسخ الاحتياطية، وتقييد الوصول إليها، ونسخها إلى موقع تخزين مستقل.
والأهم من ذلك، ينبغي اختبار النسخ الاحتياطية فعليًا. فالنسخة الاحتياطية التي لم تُستعد من قبل ليست سوى افتراض.
راجع المهام المجدولة
من السهل نسيان مهام Cron أثناء الاستجابة للحوادث. قد ينشئ المهاجم الذي يحصل على صلاحيات وصول كافية عمليةً مجدولة تعيد إنشاء البرمجيات الخبيثة المحذوفة أو تنزّل حمولةً لاحقًا.
لذلك أراجع المهام المجدولة بعد حدوث اختراق خطير.
في Linux، قد يشمل ذلك:
crontab -l
وبحسب إعدادات الخادم، قد تحتاج أدلة Cron على مستوى النظام أيضًا إلى الفحص من جانب مسؤول الخادم.
لا يعني كون دليل الموقع نظيفًا بالضرورة أن النظام نظيف، إذا كانت هناك عملية أخرى قادرة على إعادة إصابته تلقائيًا.
تحقق من وجود حسابات مسؤول غير متوقعة
إذا كان نظام إدارة المحتوى يحتوي على جدول للمستخدمين، أراجع حسابات أصحاب الصلاحيات يدويًا. أريد معرفة من لديه صلاحيات المسؤول، ومتى أُنشئت تلك الحسابات، وما إذا كانت عناوين بريدهم الإلكتروني وأسماء مستخدميهم معروفة.
ينطبق المبدأ نفسه على حساب الاستضافة، ونظام إدارة قاعدة البيانات، ومستخدمي SSH، وحسابات FTP، ومستخدمي لوحة التحكم.
غالبًا ما يفضّل المهاجمون إنشاء وصول يبدو شرعيًا بدلًا من الاعتماد كليًا على البرمجيات الخبيثة. ويمكن لحساب مسؤول إضافي أن يبقى بعد تنظيف الشيفرة ويواصل منحهم إمكانية الوصول.
افحص قاعدة البيانات بحثًا عن محتوى مُدرج
لا تعيش كل برمجيات الموقع الخبيثة داخل الملفات. فقد يُدرج المهاجم روابط غير مرغوب فيها، أو نصوصًا برمجية، أو عمليات إعادة توجيه، أو مقالات زائفة، أو HTML ضارًا مباشرةً في سجلات قاعدة البيانات.
أبحث في محتوى قاعدة البيانات عن نطاقات مشبوهة، وعلامات <script> غير متوقعة، وعناصر iframe، وJavaScript مُرمّز، وكتل كبيرة من HTML غير مألوف.
وهذا مهم بشكل خاص لأنظمة CMS التي يُخزَّن محتوى صفحاتها أساسًا داخل MySQL. فقد يؤدي تنظيف نظام الملفات مع تجاهل قاعدة البيانات إلى إبقاء الاختراق قائمًا جزئيًا.
استخدم سياسة أمان المحتوى حيثما كان ذلك عمليًا
يمكن لسياسة أمان المحتوى، أو CSP، أن تحد من تأثير بعض هجمات البرمجة النصية عبر المواقع من خلال تقييد النصوص البرمجية والأنماط والإطارات والموارد الأخرى التي يُسمح للمتصفح بتحميلها.
تتطلب سياسة CSP شديدة الصرامة اختبارًا دقيقًا، لأن التطبيقات الحديثة تعتمد غالبًا على خدمات الجهات الخارجية، والتحليلات، وموفري الدفع، وشبكات CDN، والنصوص البرمجية المضمّنة. ومع ذلك، يمكن حتى لسياسة مصممة بعناية أن تقلل بشكل كبير عدد الأماكن التي يُقبل منها المحتوى القابل للتنفيذ.
قد تبدو نقطة بداية أساسية على النحو التالي:
Header always set Content-Security-Policy "default-src 'self'; object-src 'none'; frame-ancestors 'self';"
هذا المثال مبسّط عمدًا. وقد يحتاج التطبيق الفعلي إلى توجيهات إضافية لخدمات Stripe، والتحليلات، والخطوط، والصور، وواجهات API، أو الأصول الخارجية.
النقطة المهمة هي أنه ينبغي التعامل مع CSP باعتبارها حدًا أمنيًا إضافيًا في المتصفح، وليس بديلًا عن البرمجة الآمنة.
أضف ترويسات أمان مفيدة أخرى
يمكن للعديد من ترويسات استجابة HTTP أن تقلل سلوك المتصفح غير الضروري أو تعزز أمان التطبيق.
على سبيل المثال:
Header always set X-Content-Type-Options "nosniff"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set X-Frame-Options "SAMEORIGIN"
تعالج هذه الترويسات مشكلات مختلفة. تعمل X-Content-Type-Options على تثبيط المتصفحات عن تخمين أنواع المحتوى. وتتحكم Referrer-Policy في مقدار معلومات عنوان URL المُحيل التي تتم مشاركتها. ويمكن لـ X-Frame-Options تقليل هجمات التأطير في المتصفحات التي تدعمها.
قد تستخدم التطبيقات الحديثة توجيه CSP frame-ancestors بدلًا من X-Frame-Options أو بالإضافة إليه.
فرض استخدام HTTPS
ينبغي ألا يعمل نظام تسجيل الدخول عبر HTTP غير المشفّر. يحمي HTTPS بيانات الاعتماد وملفات تعريف ارتباط الجلسة وإرسالات النماذج والمعلومات الحساسة الأخرى أثناء انتقالها بين المتصفح والخادم.
أعيد توجيه حركة مرور HTTP العامة إلى HTTPS، وأتأكد من أن التطبيق نفسه ينشئ عناوين URL باستخدام HTTPS.
قد تبدو إعادة التوجيه الأساسية في Apache كما يلي:
RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
في الأنظمة التي تستخدم وكلاء عكسيين أو موازنات تحميل، قد يتطلب اكتشاف HTTPS إعدادًا مختلفًا، لذا يجب أخذ بنية الاستضافة في الاعتبار.
إبقاء التبعيات تحت السيطرة
التبعيات هي في الواقع شيفرة تابعة لجهات خارجية تعمل داخل تطبيقك. تصبح كل حزمة يتم تثبيتها عبر Composer أو npm أو أي مدير حزم آخر جزءًا من سطح الهجوم الخاص بالتطبيق.
أراجع التبعيات بشكل دوري وأزيل الحزم التي لم تعد ضرورية. فعدد أقل من التبعيات يعني مكونات أقل تحتاج إلى تحديث، وفرصًا أقل لبقاء تعليمات برمجية معرضة للثغرات دون ملاحظة.
في مشروعات PHP القائمة على Composer، يمكن أن يكون تدقيق التبعيات جزءًا من سير عمل الصيانة.
composer audit
لا تتمثل العادة التشغيلية المهمة في تثبيت التحديثات بشكل أعمى فحسب. ينبغي أن تعرف الحزم التي يعتمد عليها التطبيق، وما الذي تفعله، وما إذا كانت هناك حزم مهجورة لا تزال موجودة في بيئة الإنتاج.
حماية مفاتيح API من الواجهة الأمامية
يجب ألا تُضمَّن بيانات اعتماد API السرية داخل JavaScript العام. فأي شيء يُرسل إلى المتصفح يمكن للمستخدم فحصه في النهاية.
إذا كانت واجهة API تتطلب بيانات اعتماد سرية على الخادم، فينبغي عمومًا إجراء الطلب من الخادم.
على سبيل المثال، يجب أن يكون السر الخاص بخدمة OpenAI أو Stripe أو البريد الإلكتروني أو أي خدمة داخلية ضمن إعدادات الخادم، لا داخل حزمة JavaScript.
تختلف المفاتيح العامة عن ذلك. إذ يستخدم بعض مزودي الخدمات عمدًا مفاتيح قابلة للنشر وآمنة للمتصفح. ينبغي للمطورين فهم الفرق بين بيانات الاعتماد القابلة للنشر وبيانات الاعتماد السرية، بدلًا من افتراض أن كل مفتاح API يمكن أن يظهر بأمان في التعليمات البرمجية للواجهة الأمامية.
التحقق من خطافات Stripe على الويب
تستحق أنظمة الدفع اهتمامًا خاصًا، إذ لا ينبغي لمستخدم خبيث أن يتمكن من إخبار تطبيقك بأن عملية الدفع نجحت، بمجرد إرسال طلب HTTP خاص به إلى عنوان URL الخاص بخطاف الويب.
عند استخدام خطافات Stripe على الويب، ينبغي للتطبيق التحقق من توقيع الحدث باستخدام سر توقيع خطاف الويب قبل الوثوق بالحمولة.
تبدو الفكرة العامة كما يلي:
<?php
$event = \Stripe\Webhook::constructEvent(
$payload,
$signature,
$endpointSecret
);
?>
فقط بعد نجاح التحقق، ينبغي للتطبيق تحديث طلب، أو إتاحة محتوى مدفوع، أو وضع علامة على أن الفاتورة مدفوعة، أو منح خدمة.
يجب ألا تتعامل قاعدة بيانات تطبيقك مطلقًا مع إعادة توجيه المتصفح إلى صفحة «تم الدفع بنجاح» على أنها دليل على استلام الأموال فعليًا.
احمِ المحتوى المدفوع على الخادم
هذا مهم خصوصًا لمواقع العضوية والمحتوى المحمي بنظام الدفع. إن إخفاء المحتوى المدفوع باستخدام CSS أو JavaScript لا يُعد تحكمًا حقيقيًا في الوصول.
إذا تم إرسال المقالة الكاملة إلى المتصفح وكان JavaScript يخفيها فحسب، فيمكن لأي شخص فتح أدوات المطور وقراءة المحتوى الذي يُفترض أنه محمي.
ينبغي للخادم تحديد ما إذا كان الزائر مخولًا قبل إرجاع المواد المقيدة.
من الناحية المفاهيمية:
<?php
if (!$userHasAccess) {
echo $preview;
exit;
}
echo $fullArticle;
?>
التمييز المهم هو أن الزوار غير المخولين لا يتلقون مطلقًا الجزء المحمي من المقالة.
تنطبق القاعدة نفسها على التنزيلات المميزة. لا تضع ملف PDF على عنوان URL عام يمكن توقعه، ولا تفترض أن الأشخاص لن يصلوا إليه إلا عبر صفحة الشراء. ينبغي للتطبيق التحقق من صلاحية الوصول قبل إرسال الملف.
استخدم معالجات تنزيل آمنة
من الأفضل تخزين الملفات المحمية خارج دليل الويب العام. وعندما يطلب عميل مخول الملف، يمكن لـ PHP التحقق من الصلاحية وبث الملف إلى المتصفح.
قد يبدو موقع التخزين كما يلي:
/home/account/private-downloads/security-checklist.pdf
بدلًا من:
/public_html/downloads/security-checklist.pdf
يمنع هذا أي شخص من تجاوز تفويض التطبيق عبر اكتشاف عنوان URL المباشر.
راقب حركة مرور 404
تتمثل إحدى أسهل طرق رصد الفحص الآلي في مراقبة الطلبات المتكررة للملفات غير الموجودة. إذ يُنشئ الزائر العادي أحيانًا خطأ 404 بسبب رابط معطّل. أما أداة الفحص فقد تُنشئ مئات من طلبات 404 أثناء البحث عن إضافات WordPress، وملفات الإعدادات، وأرشيفات النسخ الاحتياطية، وواجهات الإدارة، وملفات البيئة، والبرامج النصية المعروفة بكونها عرضة للخطر.
لا أحظر تلقائيًا كل زائر يتسبب في خطأ 404، لكن الأنماط مهمة.
يعني طلب واحد لـ /wp-login.php القليل جدًا. أما خمسون طلبًا خلال بضع ثوانٍ لـ .env وphpmyadmin وxmlrpc.php و.git/config ونسخ قواعد البيانات الاحتياطية والإضافات القديمة، فتروي قصة مختلفة.
ينبغي أن تستند القرارات الأمنية إلى السلوك، لا إلى طلب واحد معزول.
كن حذرًا من حظر عناوين IP تلقائيًا
يمكن أن يكون الحظر التلقائي مفيدًا، لكن الأنظمة المفرطة في التشدد قد تتسبب في مشكلاتها الخاصة. فقد تُنشئ محركات البحث، وأدوات مراقبة وقت التشغيل، وأدوات فحص الأمان، والوكلاء المؤسسيون، وخدمات VPN، والمستخدمون الشرعيون أنماط طلب غير معتادة.
بدلًا من حظر عنوان IP نهائيًا بسبب عنوان URL واحد مريب، أفضل استخدام عتبات وحظر مؤقت.
فعلى سبيل المثال، قد يحظر التطبيق عميلًا مؤقتًا بعد محاولات متكررة للوصول إلى ملفات حساسة خلال فترة زمنية قصيرة.
يقلل هذا من الفحص الآلي دون اعتبار كل طلب غير معتاد عدوًا دائمًا.
سجّل التغييرات الإدارية
ينبغي لنظام إدارة محتوى جيد أن يحتفظ بسجل تدقيق للإجراءات المهمة. أريد أن أعرف متى يسجّل مستخدم الدخول، أو ينشئ صفحة، أو يحذف منشورًا، أو يغيّر صلاحيات مستخدم آخر، أو يحدّث إعدادات الدفع، أو يعدّل إعدادات الموقع، أو ينفّذ عملية أخرى عالية التأثير.
قد يسجل سجل التدقيق معرّف المسؤول، والإجراء، والسجل المتأثر، والطابع الزمني، وعنوان IP المصدر.
على سبيل المثال:
2026-08-29T21:44:12
USER=14
ACTION=delete_post
POST_ID=381
IP=203.0.113.10
تصبح سجلات التدقيق ذات قيمة كبيرة للغاية عند حدوث خطأ ما، لأنها تتيح لك إعادة بناء ما حدث بدلًا من الاعتماد بالكامل على الذاكرة.
إجراء نسخ احتياطي قبل التغييرات الرئيسية
قد يؤدي تعزيز الأمان نفسه إلى تعطيل موقع ويب إذا أُجريت التغييرات بإهمال. فقد تحظر قاعدة Apache مقيّدة المسارات المشروعة. وقد تمنع سياسة CSP جديدة تحميل JavaScript المطلوب. ويمكن أن تؤدي تغييرات أذونات الملفات إلى تعطيل عمليات رفع الملفات. كما قد تتداخل إعدادات الجلسات مع المصادقة.
قبل إجراء تغييرات أمنية رئيسية، أنشئ نسخة احتياطية من التطبيق وقاعدة البيانات.
لكن الفرق هو أنني لا أترك تلك النسخة الاحتياطية موجودة في الدليل العام بعد ذلك.
اختبر الموقع كما لو كنت لا تثق به
بمجرد اكتمال التعزيز الأساسي، أختبر التطبيق من منظور شخص يحاول إجباره على التصرف بطريقة غير صحيحة.
ماذا يحدث إذا غيّرت معرّف السجل في أحد الطلبات؟ هل يمكن لأحد العملاء الوصول إلى ملف عميل آخر؟ هل يمكن لحساب عادي طلب عنوان URL خاص بالمسؤول يدويًا؟ ماذا يحدث إذا رفعت ملفًا بامتداد مضلل؟ ماذا يحدث إذا أرسلت HTML في كل نموذج؟ ماذا يحدث إذا أرسلت طلبًا من دون رمز CSRF؟ هل يمكنني تشغيل إجراء إتمام الدفع من دون الدفع فعليًا؟
هذا النهج مفيد لأن العديد من الثغرات الخطيرة لا توجد في صياغة التعليمات البرمجية، بل في الافتراضات التي يضعها المطورون حول كيفية تصرف المستخدمين.
المستخدم العادي يتبع الواجهة. أما المهاجم فلا يفعل ذلك.
قائمة التحقق الخاصة بي بعد الاختراق
عندما أقيّم موقعًا إلكترونيًا مبنيًا بـ PHP بعد وقوع حادث أمني، أراجع البيئة بأكملها بدلًا من الاكتفاء بالصفحة التي ظهر فيها الاختراق. أفحص الملفات المعدّلة مؤخرًا، ومجلدات الرفع، وحسابات المسؤولين، ومحتوى قاعدة البيانات، ومهام cron، وسجلات الخادم، وبيانات اعتماد واجهات API، وملفات الإعداد، والنسخ الاحتياطية، وأذونات الملفات، وسلوك المصادقة.
أغيّر بيانات الاعتماد المكشوفة، وأنقل الأسرار خارج جذر المستندات حيثما أمكن، وأعطّل الوظائف غير الضرورية، وأمنع تنفيذ البرامج النصية داخل مجلدات الرفع، وأراجع أذونات قاعدة البيانات، وأتحقق من استخدام العبارات المُعدّة، وأعزّز الجلسات، وأطبّق الحماية من CSRF، وأتأكد من أن المحتوى المحمي محمي فعليًا بواسطة الخادم.
أتحقق أيضًا من وجود نسخ احتياطية في مكان منفصل عن موقع الإنتاج، ومن أن هذه النسخ قادرة فعلًا على استعادة التطبيق.
الهدف ليس جعل اختراق الموقع مستحيلًا من الناحية النظرية. فهذا ليس نموذجًا أمنيًا واقعيًا. الهدف هو إزالة نقاط الضعف الواضحة، وتقليل سطح الهجوم، والحد من قدرة المهاجم على إحداث الضرر إذا فشل أحد المكونات، وإنشاء قدر كافٍ من التسجيلات بحيث يصبح السلوك المريب مرئيًا.
يجب أن تكون الحماية الأمنية متعددة الطبقات
لا تكفي أبدًا وسيلة تحكم أمنية واحدة. فقد يفشل جدار الحماية. وقد يحتوي التطبيق على خطأ برمجي. وقد تُسرق كلمة مرور. وقد تظهر ثغرة في إحدى التبعيات. وقد تتم تهيئة الخادم بشكل خاطئ.
الحل هو الدفاع متعدد الطبقات.
إذا تمكن المهاجم من رفع ملف ضار، فينبغي لمجلد الرفع أن يرفض تنفيذه. وإذا اكتشف المهاجم كلمة مرور قاعدة البيانات، فينبغي أن يمتلك حساب قاعدة البيانات أذونات محدودة. وإذا سُرقت كلمة مرور أحد المسؤولين، فينبغي أن تنشئ المصادقة متعددة العوامل حاجزًا إضافيًا. وإذا وصلت JavaScript ضارة إلى قاعدة البيانات، فينبغي أن يحد ترميز المخرجات وسياسة أمان المحتوى من قدرتها على التنفيذ. وإذا حدث خطأ رغم ذلك، فينبغي أن تجعل التسجيلات والنسخ الاحتياطية اكتشاف المشكلة والتعافي منها أسهل.
هذه هي الفلسفة الكامنة وراء كل تدبير أمني تقريبًا في هذه المقالة.
الأمان ليس منتجًا يُثبَّت مرة واحدة ثم يُنسى. بل هو بنية، وعادة في التطوير، وعملية تشغيلية.
ما إن تبدأ في تصميم الأنظمة على افتراض أن المستخدمين سيتلاعبون بالطلبات، وأن أدوات الفحص ستتحرى عن ملفات غير موجودة، وأن الروبوتات ستهاجم صفحات تسجيل الدخول، وأن الأخطاء ستحدث في نهاية المطاف، حتى تصبح تطبيقاتك أصعب بكثير على الاختراق.
وبعد أن تنظف فعليًا موقعًا إلكترونيًا تعرض للاختراق مرة واحدة، فإنك تتوقف عمومًا عن اعتبار هذه الاحتياطات مبالغًا فيها.