الأمان والأداء

التحقق من صحة المدخلات وتنظيفها

18 دقيقة الدرس 9 من 35

مقدمة في التحقق من صحة المدخلات وتنظيفها

التحقق من صحة المدخلات وتنظيفها هما ممارستان أمنيتان حاسمتان تحمي تطبيقات الويب الخاصة بك من البيانات الضارة والهجمات. يجب التحقق من صحة وتنظيف كل جزء من البيانات التي تدخل تطبيقك من مصادر خارجية - سواء من المستخدمين أو واجهات برمجة التطبيقات أو قواعد البيانات - قبل معالجتها أو تخزينها.

المبدأ الأساسي: لا تثق أبدًا في مدخلات المستخدم. تحقق دائمًا من البيانات ونظفها على جانب الخادم، حتى لو كان لديك تحقق من جانب العميل. يمكن للمهاجمين تجاوز التحقق من جانب العميل.

يضمن التحقق من صحة المدخلات أن البيانات تتوافق مع التنسيقات والقواعد المتوقعة، بينما يزيل التنظيف أو يشفر الأحرف الضارة المحتملة. معًا، يشكلان طبقة دفاع حاسمة ضد هجمات حقن SQL وهجمات XSS وثغرات أمنية أخرى.

التحقق من جانب الخادم: خط الدفاع الأول

التحقق من جانب الخادم إلزامي للأمان لأنه يمكن تجاوز التحقق من جانب العميل بسهولة. يمكن للمهاجمين تعطيل JavaScript أو تعديل طلبات HTTP أو استخدام أدوات آلية لإرسال بيانات ضارة مباشرة إلى خادمك.

<?php
// التحقق من جانب العميل وحده ليس كافيًا
// يجب التحقق من هذا دائمًا على الخادم
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // التحقق من البريد الإلكتروني
    $email = $_POST['email'] ?? '';
    if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        die('عنوان بريد إلكتروني غير صالح');
    }
    
    // التحقق من العمر
    $age = $_POST['age'] ?? '';
    if (!is_numeric($age) || $age < 18 || $age > 120) {
        die('عمر غير صالح');
    }
    
    // التحقق من اسم المستخدم
    $username = $_POST['username'] ?? '';
    if (!preg_match('/^[a-zA-Z0-9_]{3,20}$/', $username)) {
        die('تنسيق اسم المستخدم غير صالح');
    }
}
?>

تتضمن المبادئ الأساسية للتحقق من جانب الخادم فحص أنواع البيانات والتحقق من الأطوال وفرض متطلبات التنسيق والتأكد من أن القيم تقع ضمن نطاقات مقبولة. لا تعتمد أبدًا على التحقق من جانب العميل فقط للأمان.

القوائم البيضاء مقابل القوائم السوداء

عند التحقق من صحة المدخلات، يمكنك استخدام نهجين: القوائم البيضاء (السماح فقط بالقيم الجيدة المعروفة) أو القوائم السوداء (حظر القيم السيئة المعروفة). القوائم البيضاء بشكل عام أكثر أمانًا لأنه من المستحيل توقع جميع نواقل الهجوم المحتملة.

أفضل ممارسة: فضل دائمًا القوائم البيضاء على القوائم السوداء. حدد ما هو مسموح به بدلاً من محاولة حظر ما هو محظور.
<?php
// نهج القائمة البيضاء (موصى به)
$allowed_colors = ['red', 'green', 'blue', 'yellow'];
$color = $_POST['color'] ?? '';

if (in_array($color, $allowed_colors, true)) {
    // اللون صالح
    echo "اللون المحدد: " . htmlspecialchars($color);
} else {
    // لون غير صالح
    echo "اختيار لون غير صالح";
}

// نهج القائمة السوداء (أقل أمانًا)
$forbidden_words = ['script', 'alert', 'onerror'];
$input = $_POST['comment'] ?? '';

foreach ($forbidden_words as $word) {
    if (stripos($input, $word) !== false) {
        die('تم اكتشاف محتوى محظور');
    }
}
// المشكلة: يمكن للمهاجمين إيجاد طرق للتحايل على القوائم السوداء
?>

تعمل القوائم البيضاء من خلال تحديد القيم أو التنسيقات أو الأنماط المقبولة بشكل صريح. على سبيل المثال، إذا كنت تتوقع رمز بلد، يمكنك التحقق من صحته مقابل مصفوفة من رموز البلدان ISO الصالحة. بالنسبة لتنسيقات أسماء المستخدمين، يمكنك استخدام التعبيرات العادية للسماح فقط بالأحرف الأبجدية الرقمية والشرطات السفلية.

تحذير: يمكن تجاوز القوائم السوداء بسهولة. يمكن للمهاجمين استخدام الترميز أو التعتيم أو الاختلافات التي ليست في قائمتك السوداء. على سبيل المثال، حظر "script" لن يمنع "<ScRiPt>" أو "scr\x69pt".

التحقق من نوع البيانات

التأكد من أن البيانات تطابق النوع المتوقع أمر أساسي للتحقق من صحة المدخلات. توفر PHP عدة دوال للتحقق من النوع والتحويل.

<?php
// التحقق من الأعداد الصحيحة
$user_id = $_POST['user_id'] ?? '';
if (filter_var($user_id, FILTER_VALIDATE_INT) === false) {
    die('معرف مستخدم غير صالح');
}
$user_id = (int)$user_id; // تحويل النوع

// التحقق من الأعداد العشرية
$price = $_POST['price'] ?? '';
if (filter_var($price, FILTER_VALIDATE_FLOAT) === false) {
    die('سعر غير صالح');
}
$price = (float)$price;

// التحقق من القيم المنطقية
$subscribe = $_POST['subscribe'] ?? '';
$subscribe = filter_var($subscribe, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
if ($subscribe === null) {
    die('قيمة منطقية غير صالحة');
}

// التحقق من البريد الإلكتروني
$email = $_POST['email'] ?? '';
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
    die('بريد إلكتروني غير صالح');
}

// التحقق من عنوان URL
$website = $_POST['website'] ?? '';
if (!filter_var($website, FILTER_VALIDATE_URL)) {
    die('عنوان URL غير صالح');
}

// التحقق من عنوان IP
$ip = $_POST['ip'] ?? '';
if (!filter_var($ip, FILTER_VALIDATE_IP)) {
    die('عنوان IP غير صالح');
}
?>

تحقق دائمًا من أن الحقول الرقمية تحتوي فعليًا على أرقام قبل استخدامها في العمليات الحسابية أو استعلامات قاعدة البيانات. استخدم عوامل المقارنة الصارمة عند فحص نتائج التحقق لتجنب مشاكل تغيير النوع.

تنظيف مدخلات المستخدم

يزيل التنظيف أو يشفر الأحرف الخطرة المحتملة من مدخلات المستخدم. بينما يرفض التحقق المدخلات السيئة، ينظف التنظيف المدخلات لجعلها آمنة للاستخدام في سياقات مختلفة.

<?php
// تنظيف إخراج HTML
$user_comment = $_POST['comment'] ?? '';
$safe_comment = htmlspecialchars($user_comment, ENT_QUOTES, 'UTF-8');
echo "<p>" . $safe_comment . "</p>";

// إزالة علامات HTML تمامًا
$name = $_POST['name'] ?? '';
$clean_name = strip_tags($name);

// إزالة المسافات الزائدة
$text = $_POST['text'] ?? '';
$text = trim($text);
$text = preg_replace('/\s+/', ' ', $text);

// التصفية لقاعدة البيانات (استخدم العبارات المعدة بدلاً من ذلك)
$search = $_POST['search'] ?? '';
$search = filter_var($search, FILTER_SANITIZE_STRING);

// ترميز URL للاستخدام في عناوين URL
$param = $_POST['param'] ?? '';
$safe_param = urlencode($param);
$url = "https://example.com/search?q=" . $safe_param;

// تنظيف البريد الإلكتروني
$email = $_POST['email'] ?? '';
$email = filter_var($email, FILTER_SANITIZE_EMAIL);
if (filter_var($email, FILTER_VALIDATE_EMAIL)) {
    // البريد الإلكتروني صالح بعد التنظيف
}
?>
السياق مهم: تعتمد طريقة التنظيف على المكان الذي ستستخدم فيه البيانات. يتطلب إخراج HTML دالة htmlspecialchars()، وتحتاج عناوين URL إلى urlencode()، ويتطلب SQL عبارات معدة.

التحقق من تحميل الملفات

تعد تحميلات الملفات خطيرة بشكل خاص وتتطلب تحققًا واسعًا. لا تثق أبدًا في امتداد الملف أو نوع MIME الذي يوفره العميل - يمكن تزوير كليهما بسهولة.

<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['upload'])) {
    $file = $_FILES['upload'];
    
    // التحقق من أخطاء التحميل
    if ($file['error'] !== UPLOAD_ERR_OK) {
        die('فشل التحميل برمز خطأ: ' . $file['error']);
    }
    
    // التحقق من حجم الملف (5 ميجابايت كحد أقصى)
    $max_size = 5 * 1024 * 1024;
    if ($file['size'] > $max_size) {
        die('الملف كبير جدًا');
    }
    
    // التحقق من نوع MIME
    $allowed_types = ['image/jpeg', 'image/png', 'image/gif'];
    $finfo = finfo_open(FILEINFO_MIME_TYPE);
    $mime = finfo_file($finfo, $file['tmp_name']);
    finfo_close($finfo);
    
    if (!in_array($mime, $allowed_types, true)) {
        die('نوع ملف غير صالح');
    }
    
    // التحقق من امتداد الملف
    $allowed_ext = ['jpg', 'jpeg', 'png', 'gif'];
    $ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
    
    if (!in_array($ext, $allowed_ext, true)) {
        die('امتداد ملف غير صالح');
    }
    
    // إنشاء اسم ملف آمن
    $new_filename = bin2hex(random_bytes(16)) . '.' . $ext;
    $upload_path = 'uploads/' . $new_filename;
    
    // نقل الملف المحمل
    if (!move_uploaded_file($file['tmp_name'], $upload_path)) {
        die('فشل نقل الملف المحمل');
    }
    
    echo "تم تحميل الملف بنجاح: " . htmlspecialchars($new_filename);
}
?>
أمان حاسم: لا تستخدم أبدًا اسم الملف الأصلي الذي يوفره المستخدم. قم بإنشاء اسم ملف عشوائي جديد لمنع هجمات اجتياز الدليل والكتابة فوق الملفات.

تشمل إجراءات الأمان الإضافية لتحميل الملفات تخزين الملفات المحملة خارج جذر الويب، وتطبيق فحص الفيروسات، واستخدام أذونات الملفات المناسبة (644 للملفات، 755 للأدلة)، وتسجيل جميع محاولات التحميل لمراقبة الأمان.

التعبيرات العادية للتحقق

توفر التعبيرات العادية (regex) مطابقة أنماط قوية لسيناريوهات التحقق المعقدة. إنها ضرورية للتحقق من التنسيقات مثل أرقام الهواتف والرموز البريدية وهياكل البيانات المخصصة.

<?php
// التحقق من اسم المستخدم (أبجدي رقمي وشرطة سفلية، 3-20 حرفًا)
$username = $_POST['username'] ?? '';
if (!preg_match('/^[a-zA-Z0-9_]{3,20}$/', $username)) {
    die('تنسيق اسم المستخدم غير صالح');
}

// التحقق من قوة كلمة المرور
$password = $_POST['password'] ?? '';
// 8 أحرف على الأقل، حرف كبير واحد، حرف صغير واحد، رقم واحد، حرف خاص واحد
if (!preg_match('/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/', $password)) {
    die('كلمة المرور ضعيفة جدًا');
}

// التحقق من رقم الهاتف (تنسيق أمريكي)
$phone = $_POST['phone'] ?? '';
if (!preg_match('/^\(?\d{3}\)?[\s.-]?\d{3}[\s.-]?\d{4}$/', $phone)) {
    die('رقم هاتف غير صالح');
}

// التحقق من بطاقة الائتمان (فحص التنسيق الأساسي)
$card = $_POST['card'] ?? '';
$card = preg_replace('/[\s-]/', '', $card); // إزالة المسافات والشرطات
if (!preg_match('/^\d{13,19}$/', $card)) {
    die('تنسيق رقم البطاقة غير صالح');
}

// التحقق من التاريخ (YYYY-MM-DD)
$date = $_POST['date'] ?? '';
if (!preg_match('/^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/', $date)) {
    die('تنسيق تاريخ غير صالح');
}

// التحقق من رمز اللون السداسي
$color = $_POST['color'] ?? '';
if (!preg_match('/^#[0-9a-fA-F]{6}$/', $color)) {
    die('رمز لون غير صالح');
}

// التحقق من URL slug
$slug = $_POST['slug'] ?? '';
if (!preg_match('/^[a-z0-9]+(?:-[a-z0-9]+)*$/', $slug)) {
    die('URL slug غير صالح');
}
?>
نصيحة للأداء: قم بتجميع وتخزين أنماط regex المستخدمة بشكل متكرر مؤقتًا. استخدم المجموعات غير الملتقطة (?:) عندما لا تحتاج إلى استخراج سلاسل فرعية متطابقة.

مكتبات وأطر التحقق

توفر أطر ومكتبات PHP الحديثة أنظمة تحقق قوية تبسط التحقق من صحة المدخلات مع الحفاظ على الأمان. توفر هذه الأدوات مدققات مبنية مسبقًا ومعالجة الأخطاء وقواعد تحقق قابلة للتخصيص.

<?php
// مثال على التحقق في Laravel
use Illuminate\Support\Facades\Validator;

$validator = Validator::make($_POST, [
    'name' => 'required|string|max:255',
    'email' => 'required|email|unique:users,email',
    'age' => 'required|integer|min:18|max:120',
    'password' => 'required|string|min:8|confirmed',
    'phone' => 'nullable|regex:/^[0-9]{10}$/',
    'website' => 'nullable|url',
    'avatar' => 'nullable|image|max:2048',
]);

if ($validator->fails()) {
    return response()->json($validator->errors(), 422);
}

$validated = $validator->validated();

// مثال على التحقق في Symfony
use Symfony\Component\Validator\Validation;
use Symfony\Component\Validator\Constraints as Assert;

$validator = Validation::createValidator();
$violations = $validator->validate($email, [
    new Assert\NotBlank(),
    new Assert\Email(['mode' => 'strict']),
]);

if (count($violations) > 0) {
    foreach ($violations as $violation) {
        echo $violation->getMessage();
    }
}
?>
فوائد الأطر: توفر أطر التحقق رسائل خطأ متسقة وقواعد تحقق قابلة لإعادة الاستخدام والتحقق من قاعدة البيانات (فحوصات التفرد) ودعم التدويل.

دوال التحقق المخصصة

يساعد إنشاء دوال تحقق قابلة لإعادة الاستخدام في الحفاظ على الاتساق عبر تطبيقك ويجعل الكود أكثر قابلية للصيانة.

<?php
class InputValidator {
    public static function validateUsername($username) {
        if (strlen($username) < 3 || strlen($username) > 20) {
            return false;
        }
        return preg_match('/^[a-zA-Z0-9_]+$/', $username) === 1;
    }
    
    public static function validatePassword($password) {
        return strlen($password) >= 8 &&
               preg_match('/[A-Z]/', $password) &&
               preg_match('/[a-z]/', $password) &&
               preg_match('/[0-9]/', $password) &&
               preg_match('/[@$!%*?&]/', $password);
    }
    
    public static function sanitizeHTML($input) {
        return htmlspecialchars($input, ENT_QUOTES | ENT_HTML5, 'UTF-8');
    }
    
    public static function validateDate($date, $format = 'Y-m-d') {
        $d = DateTime::createFromFormat($format, $date);
        return $d && $d->format($format) === $date;
    }
    
    public static function validateCreditCard($number) {
        // خوارزمية Luhn
        $number = preg_replace('/[^0-9]/', '', $number);
        $sum = 0;
        $length = strlen($number);
        for ($i = $length - 1; $i >= 0; $i--) {
            $digit = (int)$number[$i];
            if (($length - $i) % 2 === 0) {
                $digit *= 2;
                if ($digit > 9) $digit -= 9;
            }
            $sum += $digit;
        }
        return $sum % 10 === 0;
    }
}

// الاستخدام
$username = $_POST['username'] ?? '';
if (!InputValidator::validateUsername($username)) {
    die('اسم مستخدم غير صالح');
}
?>
تمرين: قم بإنشاء فئة تحقق لنموذج تسجيل المستخدم يتحقق من اسم المستخدم والبريد الإلكتروني وكلمة المرور والعمر ورقم الهاتف. قم بتضمين طرق التحقق والتنظيف. اختبر مع مدخلات صالحة وغير صالحة متنوعة.

ملخص أفضل الممارسات

يتطلب التحقق الفعال من صحة المدخلات وتنظيفها نهجًا متعدد الطبقات. تحقق دائمًا على جانب الخادم، ولا تثق أبدًا في التحقق من جانب العميل وحده. استخدم القوائم البيضاء على القوائم السوداء كلما أمكن ذلك. تحقق من أنواع البيانات بشكل صريح قبل استخدام القيم في العمليات أو الاستعلامات.

نظف الإخراج بناءً على السياق - استخدم htmlspecialchars() لإخراج HTML والعبارات المعدة لـ SQL والترميز المناسب لعناوين URL وJavaScript. بالنسبة لتحميلات الملفات، تحقق من أنواع MIME باستخدام الكشف من جانب الخادم، وتحقق من أحجام الملفات، وقم بإنشاء أسماء ملفات جديدة لمنع الهجمات.

استخدم أطر التحقق عند توفرها للاستفادة من الكود المختبر جيدًا ومعالجة الأخطاء المتسقة. قم بإنشاء دوال تحقق قابلة لإعادة الاستخدام للأنماط الشائعة في تطبيقك. سجل فشل التحقق لمراقبة الأمان وتصحيح الأخطاء.

طبقات الأمان: التحقق من صحة المدخلات هو طبقة واحدة من الدفاع. اجمعها مع العبارات المعدة وترميز الإخراج ورموز CSRF وتحديد المعدل للحصول على أمان شامل.

تذكر أن قواعد التحقق يجب أن تكون صارمة قدر الإمكان مع السماح بحالات الاستخدام المشروعة. راجع وحدث منطق التحقق بانتظام مع ظهور نواقل هجوم جديدة. وثق قواعد التحقق بوضوح حتى يفهم المطورون المدخلات المقبولة ولماذا توجد قيود معينة.