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

الحماية من هجمات CSRF

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

فهم هجمات CSRF

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

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

مفهوم أساسي: تعمل هجمات CSRF لأن المتصفحات تقوم تلقائيًا بتضمين بيانات الاعتماد (ملفات تعريف الارتباط، مصادقة HTTP) مع الطلبات إلى نطاق معين، بغض النظر عن مصدر الطلب. هذا التضمين التلقائي لبيانات الاعتماد هو المشكلة الأمنية الأساسية التي تعالجها آليات الحماية من CSRF.

كيف تعمل هجمات CSRF

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

<!-- مثال: HTML ضار على evil.com -->\n<img src="https://bank.com/transfer?amount=1000&to=attacker" width="0" height="0">\n\n<!-- أو باستخدام نموذج يتم إرساله تلقائيًا -->\n<form action="https://bank.com/transfer" method="POST" id="csrf-form">\n <input type="hidden" name="amount" value="1000">\n <input type="hidden" name="to" value="attacker">\n</form>\n<script>document.getElementById('csrf-form').submit();</script>

يمكن تنفيذ هجمات CSRF من خلال ناقلات مختلفة بما في ذلك الصور ذات سمات src الضارة، وإرسال النماذج تلقائيًا، واستدعاءات XMLHttpRequest أو Fetch API، وحتى من خلال محتوى HTML في البريد الإلكتروني. لا يتطلب الهجوم JavaScript ويمكن أن يعمل مع عناصر HTML البسيطة، مما يجعله خطيرًا بشكل خاص.

مهم: يجب ألا تقوم طلبات GET أبدًا بإجراء عمليات تغيير الحالة (إنشاء، تحديث، حذف). استخدم دائمًا POST أو PUT أو PATCH أو DELETE للعمليات التي تعدل البيانات. استخدام GET لتغييرات الحالة يجعل هجمات CSRF سهلة التنفيذ من خلال علامات الصور البسيطة أو الروابط.

الوقاية القائمة على الرموز من CSRF

تقنية الوقاية الأكثر شيوعًا وفعالية من CSRF هي نمط رمز المزامنة. يولد هذا النهج رمزًا فريدًا لا يمكن التنبؤ به لكل جلسة مستخدم (أو حتى لكل طلب) ويتطلب تضمين هذا الرمز مع كل طلب لتغيير الحالة. يتحقق الخادم من أن الرمز في الطلب يطابق الرمز المخزن في جلسة المستخدم قبل معالجة الطلب.

يجب تضمين الرمز بطريقة لا ترسله المتصفحات تلقائيًا مع الطلبات. يتم ذلك عادةً عن طريق تضمين الرمز في نموذج HTML كحقل مخفي أو تضمينه في رؤوس HTTP مخصصة لطلبات AJAX. نظرًا لأن الموقع الضار لا يمكنه قراءة الرمز بسبب سياسة نفس المصدر، فلا يمكنه تضمينه في الطلبات المزورة.

<!-- ينشئ الخادم رمزًا ويضمنه في النماذج -->\n<form action="/transfer" method="POST">\n <input type="hidden" name="csrf_token" value="random_unpredictable_token_here">\n <input type="text" name="amount">\n <input type="text" name="to">\n <button type="submit">تحويل</button>\n</form>\n\n<!-- التحقق من جانب الخادم (مثال PHP) -->\nif (!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {\n die('فشل التحقق من رمز CSRF');\n}\n\n// معالجة الطلب...
أفضل ممارسة: استخدم دوال المقارنة ذات الوقت الثابت (مثل hash_equals() في PHP) عند التحقق من رموز CSRF لمنع هجمات التوقيت. يمكن أن تؤدي مقارنة السلسلة العادية إلى تسرب معلومات حول الرمز من خلال اختلافات التوقيت.

سمة ملف تعريف الارتباط SameSite

سمة ملف تعريف الارتباط SameSite هي ميزة أمان حديثة للمتصفح توفر حماية مدمجة من CSRF من خلال التحكم في موعد إرسال المتصفحات لملفات تعريف الارتباط مع الطلبات عبر المواقع. تحتوي هذه السمة على ثلاث قيم محتملة: Strict وLax وNone، كل منها يوفر مستويات مختلفة من الحماية والوظائف.

يوفر SameSite=Strict أقوى حماية من خلال منع المتصفح من إرسال ملف تعريف الارتباط مع أي طلب عبر الموقع. هذا يعني أنه إذا نقر المستخدم على رابط إلى موقعك من موقع آخر، فلن يتم مصادقته في تحميل الصفحة الأولى. يسمح SameSite=Lax (الافتراضي في المتصفحات الحديثة) بإرسال ملفات تعريف الارتباط مع التنقلات على المستوى الأعلى باستخدام طرق HTTP الآمنة (GET) ولكنه يحظرها لطلبات POST عبر المواقع وإدراج iframe. يسمح SameSite=None صراحةً بإرسال ملفات تعريف الارتباط عبر المواقع ولكنه يتطلب سمة Secure.

// PHP: تعيين سمة SameSite\nsetcookie('session_id', $value, [\n 'expires' => time() + 3600,\n 'path' => '/',\n 'domain' => '.example.com',\n 'secure' => true,\n 'httponly' => true,\n 'samesite' => 'Strict' // أو 'Lax' أو 'None'\n]);\n\n// JavaScript: التحقق من دعم SameSite\nif (document.cookie.includes('SameSite')) {\n console.log('المتصفح يدعم ملفات تعريف ارتباط SameSite');\n}
دعم المتصفح: يتم دعم ملفات تعريف ارتباط SameSite في جميع المتصفحات الحديثة (Chrome 80+، Firefox 69+، Safari 12.1+، Edge 80+). ومع ذلك، قد لا تدعم بعض المتصفحات القديمة وبعض متصفحات الهاتف المحمول هذه الميزة، لذا يجب استخدامها كدفاع عميق إلى جانب الحماية القائمة على الرموز.

نمط ملف تعريف الارتباط المزدوج

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

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

// جانب العميل: تعيين الرمز كملف تعريف ارتباط وتضمينه في الطلبات\nfunction generateCSRFToken() {\n const token = Array.from(crypto.getRandomValues(new Uint8Array(32)))\n .map(b => b.toString(16).padStart(2, '0'))\n .join('');\n \n document.cookie = `csrf_token=${token}; path=/; secure; samesite=strict`;\n return token;\n}\n\n// تضمين الرمز في طلبات AJAX\nfetch('/api/transfer', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'X-CSRF-Token': getCookieValue('csrf_token')\n },\n body: JSON.stringify({ amount: 100, to: 'recipient' })\n});\n\n// جانب الخادم: مقارنة رمز ملف تعريف الارتباط مع رمز الرأس\n$cookieToken = $_COOKIE['csrf_token'] ?? '';\n$headerToken = $_SERVER['HTTP_X_CSRF_TOKEN'] ?? '';\n\nif (!hash_equals($cookieToken, $headerToken)) {\n http_response_code(403);\n die('فشل التحقق من CSRF');\n}
اعتبار أمني: ملف تعريف الارتباط المزدوج عرضة للخطر إذا تمكن المهاجم من تعيين ملفات تعريف الارتباط على نطاقك (من خلال ثغرات النطاق الفرعي أو حقن ملفات تعريف الارتباط). للحصول على أقصى قدر من الأمان، اجمع هذا مع نطاق ملف تعريف الارتباط المناسب وفكر في الحماية القائمة على الرموز للعمليات الحرجة.

الحماية من CSRF في تطبيقات الصفحة الواحدة وواجهات برمجة التطبيقات

تتطلب تطبيقات الصفحة الواحدة (SPA) وواجهات برمجة التطبيقات REST اعتبارًا خاصًا للحماية من CSRF. لا يعمل تضمين الرمز التقليدي القائم على النماذج بشكل جيد مع التطبيقات الثقيلة في JavaScript التي تقوم بطلبات AJAX متكررة. تستخدم الأساليب الحديثة عادةً رؤوس HTTP مخصصة لنقل رموز CSRF، حيث لا يمكن تعيين هذه الرؤوس بواسطة نماذج HTML البسيطة أو طلبات عبر المصدر.

عند بناء تطبيقات SPA، يتم توفير رمز CSRF عادةً للتطبيق أثناء تحميل الصفحة الأولي (مضمن في HTML أو عبر نقطة نهاية API مخصصة) ثم يتم تخزينه في ذاكرة JavaScript أو sessionStorage. لكل طلب API لاحق، يتم تضمين الرمز في رأس مخصص مثل X-CSRF-Token. يتحقق الخادم من هذا الرأس في كل طلب لتغيير الحالة.

// استرجاع الرمز الأولي (مضمن في HTML)\n<script>\n window.csrfToken = '<?php echo $_SESSION["csrf_token"]; ?>';\n</script>\n\n// أو عبر نقطة نهاية API\nfetch('/api/csrf-token')\n .then(response => response.json())\n .then(data => {\n localStorage.setItem('csrfToken', data.token);\n });\n\n// تضمين في جميع الطلبات التي تغير الحالة\nconst apiRequest = async (url, method, data) => {\n const headers = {\n 'Content-Type': 'application/json',\n 'X-CSRF-Token': window.csrfToken || localStorage.getItem('csrfToken')\n };\n \n const response = await fetch(url, {\n method,\n headers,\n credentials: 'same-origin', // مهم لملفات تعريف الارتباط\n body: JSON.stringify(data)\n });\n \n return response.json();\n};\n\n// الاستخدام\napiRequest('/api/posts', 'POST', { title: 'منشور جديد', content: '...' });
تصميم API: للتطبيقات القائمة على API بشكل بحت بدون جلسات، فكر في استخدام المصادقة القائمة على الرموز (JWT) بدلاً من ملفات تعريف الارتباط. هذا يلغي ثغرة CSRF تمامًا حيث يجب تضمين الرموز بشكل صريح في الطلبات ولا يتم إرسالها تلقائيًا بواسطة المتصفحات.

الحماية من CSRF في Laravel

يوفر Laravel حماية شاملة مدمجة من CSRF من خلال وسيطة VerifyCsrfToken. تقوم هذه الوسيطة تلقائيًا بإنشاء رموز CSRF لكل جلسة مستخدم وتتحقق من الرموز في جميع طلبات POST وPUT وPATCH وDELETE. يسهل Laravel تضمين الرموز في النماذج وطلبات AJAX من خلال دوال المساعدة وتكامل JavaScript.

يولد توجيه Blade @csrf تلقائيًا حقل إدخال مخفي مع رمز CSRF. لطلبات AJAX، يتضمن Laravel تلقائيًا الرمز من علامة meta csrf-token في رؤوس طلبات Axios. يمكنك أيضًا الوصول يدويًا إلى الرمز باستخدام دالة المساعدة csrf_token() أو واجهة CSRF_TOKEN().

<!-- قالب Blade مع رمز CSRF -->\n<form method="POST" action="/profile">\n @csrf\n <input type="text" name="name">\n <button type="submit">تحديث الملف الشخصي</button>\n</form>\n\n<!-- علامة meta للوصول عبر JavaScript -->\n<meta name="csrf-token" content="{{ csrf_token() }}">\n\n<!-- يتضمن Axios تلقائيًا الرمز من علامة meta -->\n<script>\naxios.post('/api/profile', {\n name: 'محمد أحمد'\n}).then(response => {\n console.log('تم تحديث الملف الشخصي');\n});\n</script>\n\n// استثناء المسارات من حماية CSRF (استخدم بحذر)\nprotected $except = [\n 'webhook/*', // webhooks الخارجية ليس لديها رموز\n 'api/*' // إذا كنت تستخدم مصادقة رمز API\n];
تمرين تطبيقي: أنشئ تطبيق ويب بسيط مع نموذج تحويل الأموال. نفذ الحماية من CSRF باستخدام التحقق القائم على الرموز وملفات تعريف ارتباط SameSite. ثم، أنشئ صفحة ضارة منفصلة تحاول تنفيذ هجمات CSRF. اختبر أن آليات الحماية تحظر الهجمات بنجاح بينما تسمح بالطلبات الشرعية. جرب ناقلات هجوم مختلفة بما في ذلك الإرسال التلقائي للنماذج وعلامات الصور وطلبات AJAX.

استراتيجيات الدفاع المتعدد الطبقات

تعتمد الحماية الفعالة من CSRF على طبقات متعددة من الدفاع تعمل معًا. في حين أن رموز CSRF هي الدفاع الأساسي، فإن دمجها مع تدابير أمنية أخرى يوفر حماية قوية حتى إذا فشلت إحدى الطبقات. تتضمن استراتيجية الدفاع الشاملة الاستخدام الصحيح لطرق HTTP، وملفات تعريف ارتباط SameSite، ورؤوس الطلبات المخصصة، ومتطلبات تفاعل المستخدم، والمصادقة الإضافية للعمليات الحساسة.

استخدم دائمًا طرق HTTP المناسبة: GET لعمليات القراءة، POST/PUT/PATCH للتعديلات، وDELETE للإزالة. نفذ سمات ملف تعريف ارتباط SameSite لمنع نقل ملف تعريف الارتباط عبر الموقع. اطلب رؤوسًا مخصصة لطلبات AJAX، حيث لا يمكن تعيينها بواسطة نماذج HTML البسيطة. للعمليات الحرجة مثل تغييرات كلمة المرور أو التحويلات المالية الكبيرة، اطلب مصادقة إضافية مثل تأكيد كلمة المرور أو رموز MFA.

// تنفيذ شامل للحماية من CSRF\nclass CSRFProtection {\n // إنشاء رمز آمن من الناحية التشفيرية\n public static function generateToken() {\n if (empty($_SESSION['csrf_token'])) {\n $_SESSION['csrf_token'] = bin2hex(random_bytes(32));\n }\n return $_SESSION['csrf_token'];\n }\n \n // التحقق من الرمز من الطلب\n public static function validateToken($token) {\n if (empty($_SESSION['csrf_token']) || empty($token)) {\n return false;\n }\n \n // استخدام مقارنة بوقت ثابت\n return hash_equals($_SESSION['csrf_token'], $token);\n }\n \n // التحقق من طريقة الطلب\n public static function validateMethod($expected) {\n return $_SERVER['REQUEST_METHOD'] === strtoupper($expected);\n }\n \n // التحقق من طلب AJAX\n public static function isAjaxRequest() {\n return !empty($_SERVER['HTTP_X_REQUESTED_WITH']) &&\n strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest';\n }\n \n // التحقق الكامل للعمليات الحساسة\n public static function validateRequest() {\n // التحقق من طريقة HTTP\n if (!in_array($_SERVER['REQUEST_METHOD'], ['POST', 'PUT', 'PATCH', 'DELETE'])) {\n return true; // السماح بطلبات GET\n }\n \n // التحقق من رمز CSRF\n $token = $_POST['csrf_token'] ?? $_SERVER['HTTP_X_CSRF_TOKEN'] ?? '';\n if (!self::validateToken($token)) {\n http_response_code(403);\n die('فشل التحقق من CSRF');\n }\n \n return true;\n }\n}
تذكر: الحماية من CSRF هي مجرد مكون واحد من أمان تطبيقات الويب. يجب دمجها مع المصادقة المناسبة والترخيص والتحقق من الإدخال وتشفير الإخراج وتدابير أمنية أخرى لإنشاء تطبيق آمن حقًا. يعني الدفاع المتعدد الطبقات أنه إذا فشل أحد عناصر التحكم الأمنية، فستكون هناك عناصر أخرى في مكانها لمنع الاستغلال.

اختبار الحماية من CSRF

الاختبار الشامل للحماية من CSRF ضروري لضمان عملها بشكل صحيح وعدم تدخلها في الوظائف الشرعية. يجب أن يغطي الاختبار كلاً من الحالات الإيجابية (تنجح الطلبات الشرعية) والحالات السلبية (يتم حظر الطلبات المزورة). يمكن أن يساعد الاختبار الآلي في اكتشاف الانحدارات عند تغيير الكود.

// اختبار PHPUnit للحماية من CSRF\npublic function test_csrf_token_required_for_post_requests()\n{\n // محاولة POST بدون رمز CSRF - يجب أن تفشل\n $response = $this->post('/transfer', [\n 'amount' => 100,\n 'to' => 'recipient'\n ]);\n \n $response->assertStatus(419); // عدم تطابق رمز CSRF\n}\n\npublic function test_valid_csrf_token_allows_request()\n{\n // الحصول على رمز CSRF\n $token = csrf_token();\n \n // POST مع رمز صالح - يجب أن تنجح\n $response = $this->post('/transfer', [\n '_token' => $token,\n 'amount' => 100,\n 'to' => 'recipient'\n ]);\n \n $response->assertStatus(200);\n}\n\npublic function test_csrf_token_rotates_after_use()\n{\n $token = csrf_token();\n \n // استخدام الرمز\n $this->post('/transfer', ['_token' => $token]);\n \n // التحقق من تغيير الرمز\n $newToken = csrf_token();\n $this->assertNotEquals($token, $newToken);\n}

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