تزوير الطلبات عبر المواقع (CSRF)

تزوير الطلبات عبر المواقع (Cross-Site Request Forgery / CSRF) هجوم يجعل متصفح مستخدم مسجّل الدخول يرسل طلباً يغيّر الحالة دون أن يقصده المستخدم. ولأن المتصفح يرفق ملف تعريف ارتباط الجلسة (session cookie) تلقائياً، فإن التطبيق الذي يثق بملف تعريف الارتباط وحده لا يفرّق بين الطلب المزوَّر والطلب الحقيقي. مثال: بينما يكون المستخدم مسجّل الدخول في متجر، ترسل صفحة على موقع آخر نموذجاً خفيةً إلى رابط تغيير البريد في المتجر؛ فإذا فحص المتجر ملف تعريف الارتباط فقط، تغيّر عنوان البريد.

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

لماذا يهم

ينفّذ CSRF الناجح تغييراً للبيانات أو إجراءً باسم الضحية: فتغيير البريد أو كلمة المرور قد يؤدي إلى الاستيلاء على الحساب، وقد تُحوَّل الأموال أو تتبدّل الإعدادات دون موافقة. والمستخدم لم يفعل شيئاً خاطئاً ولا يرى شيئاً، ما يجعل اكتشاف الأمر لاحقاً صعباً.

تنجح هذه العيوب في اجتياز كل اختبار وظيفي، لأن الرحلة المعتادة تعمل. ولا تظهر إلا في اختبارات مكتوبة لها، وهي اختبارات رخيصة: طلب ملتقَط، وحساب اختبار، وأداة لإعادة إرساله. واكتشاف رمز مفقود أو غير مفحوص قبل الإصدار يسدّ طريقاً إلى الاستيلاء على الحسابات لا يستطيع المستخدمون حماية أنفسهم منه.

كيف يعمل الطلب المزوَّر

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

لا يخترق المهاجم الجلسة ولا يرى الاستجابة أبداً. بل تدفع صفحةٌ يزورها الضحية — إعلان، أو منشور في منتدى، أو رسالة بريد تحمّل محتوى من خادم خارجي — متصفحَ الضحية إلى إصدار الطلب نحو التطبيق الهدف. فيرفق المتصفح، متّبعاً قواعده المعتادة، ملفات تعريف الارتباط الخاصة بذلك الموقع. ويستقبل الخادم طلباً سليم البنية وموثّقاً، فينفّذه ما لم يطلب أكثر من ملف تعريف الارتباط. لهذا تستطيع صفحة على موقع آخر، لا تقدر على قراءة شيء من التطبيق الهدف، أن تُحدث فيه إجراءً.

  • النموذج المخفي الذي يرسل POST تلقائياً هو الحامل الكلاسيكي؛ وقد يحمل عنصر <img> أو رابطٌ طلب GET.
  • لا يحتاج المهاجم إلى XSS ولا إلى سكربت على الهدف: يستغلّ CSRF سلوك المتصفح نفسه، وهذا ما يميّزه عن البرمجة النصية عبر المواقع (XSS) في الدرس السابق.
  • ولأن المهاجم لا يستطيع قراءة الاستجابة، فإن CSRF يفرض إجراءً ولا يسرق بيانات — وهذا التمييز يحدّد شكل كل اختبار.
طلب مزوَّر لتغيير البريد يستغلّ ملف تعريف ارتباط الجلسة الذي يرفقه المتصفح من تلقاء نفسه؛ والرمز المرتبط بالجلسة الذي لا تستطيع الصفحة الخارجية قراءته هو ما يجعل الخادم يرفضه.
  • يُفحص ملف تعريف الارتباط فقط

    • متصفح الضحية مسجّل الدخول في app.example
    • صفحة على موقع آخر attacker.example
    • التطبيق الهدف app.example
    1. 1 من متصفح الضحية إلى التطبيق الهدف: سجّل الدخول؛ وتضبط الاستجابة ملف تعريف ارتباط الجلسة Set-Cookie: sid=9f8a…
    2. 2 من متصفح الضحية إلى صفحة على موقع آخر: افتح صفحة على موقع آخر: إعلاناً أو منشوراً في منتدى
    3. 3 من صفحة على موقع آخر إلى متصفح الضحية: نموذج مخفي يرسل نفسه إلى app.example ردّ POST /account/email
    4. 4 من متصفح الضحية إلى التطبيق الهدف: الطلب المزوَّر، ويرفق المتصفح ملف تعريف الارتباط من تلقاء نفسه Cookie: sid=9f8a…
    5. 5 من التطبيق الهدف إلى متصفح الضحية: يُفحص ملف تعريف الارتباط فقط: يتغيّر البريد ردّ لا يرى المهاجم هذه الاستجابة أبداً ولا يحتاج إليها 200 OK فشل

    النتيجة: أثبت ملف تعريف الارتباط من هو، ولم يثبت شيء من أين جاء الطلب. فشل

  • يُفحص الرمز في الخادم

    • متصفح الضحية مسجّل الدخول في app.example
    • صفحة على موقع آخر attacker.example
    • التطبيق الهدف app.example
    1. 1 من متصفح الضحية إلى التطبيق الهدف: سجّل الدخول؛ يُضبط ملف تعريف الارتباط، وتحمل نماذج التطبيق نفسه رمزاً مرتبطاً بالجلسة sid=9f8a… token=7bQ2…
    2. 2 من متصفح الضحية إلى صفحة على موقع آخر: افتح صفحة على موقع آخر: إعلاناً أو منشوراً في منتدى
    3. 3 من صفحة على موقع آخر إلى متصفح الضحية: نموذج مخفي بلا رمز: الموقع الآخر لا يستطيع قراءته ردّ POST /account/email
    4. 4 من متصفح الضحية إلى التطبيق الهدف: الطلب المزوَّر، ويرفق المتصفح ملف تعريف الارتباط من تلقاء نفسه Cookie: sid=9f8a…
    5. 5 من التطبيق الهدف إلى متصفح الضحية: الرمز مفقود: يُرفض قبل أن يتغيّر أي شيء ردّ وسمة SameSite على ملف تعريف الارتباط وفحص Origin طبقتان إضافيتان 403 Forbidden نجاح

    النتيجة: يثبت ملف تعريف الارتباط من هو المستخدم؛ ويثبت الرمز أن الطلب جاء من صفحة التطبيق نفسه. نجاح

الدفاعات الثلاثة: الرموز وSameSite والأصل

الدفاع الأساسي التقليدي هو رمز مكافحة التزوير (anti-CSRF token): قيمة لا يمكن توقّعها يربطها الخادم بجلسة المستخدم ويضعها في نماذج كل صفحة. فالنموذج الحقيقي يعيدها، والصفحة الخارجية لا تستطيع قراءتها فلا تستطيع تضمينها. ونمط الرمز المتزامن (synchronizer token) يحفظ القيمة المتوقَّعة في الخادم؛ ونمط الإرسال المزدوج (double-submit) يرسلها في ملف تعريف ارتباط وفي الطلب معاً ويقارنهما، فلا يحتاج إلى حالة في الخادم لكن يجب أن يستعمل قيمة يوقّعها الخادم ويربطها بالجلسة، حتى لا يستطيع مهاجمٌ تمكّن من زرع ملف تعريف ارتباط أن يصنع زوجاً متطابقاً. وفي الحالتين يجب أن يرفض الخادم الطلب عند غياب الرمز أو خطئه — فالرمز الموجود دون فحص لا يحمي شيئاً.

وسمة SameSite على ملف تعريف ارتباط الجلسة هي الطبقة الثانية. فالقيمة Strict أو Lax تمنع المتصفح من إرفاق ملف تعريف الارتباط بمعظم الطلبات التي تبدأ من موقع آخر، ما يوقف النموذج المزوَّر البسيط. عاملها دفاعاً في العمق (defence in depth) لا بديلاً عن الرموز: فـLax ما زالت ترسل ملف تعريف الارتباط في التنقل على المستوى الأعلى بطريقة GET من موقع آخر، وتختلف القيم الافتراضية بين المتصفحات، ولهذا يتوقّع درس ملفات تعريف الارتباط والجلسات في HTTP أن تُضبط السمة صراحةً.

والطبقة الثالثة هي ترويسة Origin، مع Referer بديلاً: عند طلب يغيّر الحالة يتأكد الخادم أن الأصل أصله هو. فالمتصفحات تضيف Origin إلى الطلبات عبر الأصول ولا تستطيع الصفحة تغييرها، فعدم التطابق سبب قوي للرفض؛ لكن الترويسة قد تغيب أو تأتي بالقيمة null، فعلى الخادم أن يقرّر ما يحدث عندئذ أيضاً. وتضع ورقة OWASP المرجعية لمنع تزوير الطلبات هذا الفحص، مثل SameSite، ضمن الدفاع في العمق. وترسل المتصفحات الحالية أيضاً Sec-Fetch-Site (مثل cross-site)، ويقبلها OWASP فحصاً رئيسياً للمتصفحات الحديثة حين تكون Origin هي البديل الاحتياطي. ولا شيء من هذا هو CORS التي تحكم ما يُسمح للصفحة بقراءته: فإرسال نموذج بسيط من موقع آخر يصل إلى الخادم أياً كانت ترويسات CORS، ولهذا قال درس CORS إنها ليست دفاعاً ضد CSRF.

جرّبها: محلّل ملفات تعريف الارتباط (Cookies)

مثال تطبيقي: اختبار إجراء تغيير البريد

خذ تطبيقاً تجريبياً تملكه أو لديك إذن مكتوب باختباره. سجّل الدخول بحساب اختبار والتقط الطلب الذي يغيّر بريد الحساب، بوكيل اعتراضي (intercepting proxy) كما في درس الوكيل الاعتراضي، أو من أدوات المطوّر في المتصفح. والطلب الملتقَط أدناه يحمل ملف تعريف ارتباط جلسة ورمزاً.

الآن أعِد إرساله مع تغيير شيء واحد في كل مرة، بميزة إعادة الإرسال في الوكيل أو بإعادة بنائه في أداة REST API Tester أو في أمر cURL، وراقب الاستجابة والبريد المخزَّن. كل إعادة إرسال حالة اختبار بنتيجة متوقَّعة واضحة. وإعادة الإرسال تختبر ما يفحصه الخادم، ولا تُظهر ما كان المتصفح سيرسله، ولهذا تُفحص SameSite على حدة في ترويسة Set-Cookie.

  • احذف X-CSRF-Token وأعِد الإرسال: توقّع رفضاً (مثل 403) وبقاء البريد دون تغيير. فإن نجح، فلا فحص للرمز في الإجراء.
  • استبدل بالرمز رمزاً صالحاً من جلسة حساب اختبار ثانٍ وأعِد الإرسال: توقّع الرفض. فإن نجح، فالرمز غير مرتبط بالجلسة.
  • أبقِ الرمز الصالح، وغيّر Origin إلى موقع آخر وأعِد الإرسال: الرفض يُظهر أن الخادم يفحص الأصل أيضاً؛ والقبول يعني أن الرمز هو الطبقة الوحيدة، فتسجّله ملاحظةً لا نتيجةً فاشلة.
  • الصق ترويسة Set-Cookie من تسجيل الدخول في أداة Cookie Analyzer، وتأكّد أن ملف تعريف ارتباط الجلسة يضبط SameSite صراحةً.
  • ابحث عن أي إجراء يمكن بلوغه عبر GET ويغيّر الحالة: فذلك عيب بذاته، لأن SameSite=Lax ترسل ملف تعريف الارتباط عند اتباع رابط.
POST /account/email HTTP/1.1
Host: app.example
Origin: https://app.example
Cookie: sid=9f8aUq0Zr8Ty2Nc6
X-CSRF-Token: 7bQ2mK9rLp4XwZ1
Content-Type: application/x-www-form-urlencoded

email=sara%40app.example

جرّبها: مختبِر REST API جرّبها: مولّد أوامر cURL جرّبها: فاحص ترويسات الطلب

اختبار CSRF قابل للتكرار في كل إصدار

وسّع المثال التطبيقي إلى قائمة فحص. اكتب كل طلب يغيّر الحالة في الميزة؛ وسجّل لكلٍّ: هل يُشترط رمز، وهل يتعذّر توقّعه وهل هو مرتبط بالجلسة، وهل يفحصه الخادم، وهل يُتحقَّق من Origin أو Referer. ويجب أن ينتقل الرمز في جسم الطلب أو في ترويسة، لا في سلسلة الاستعلام (query string) حيث يتسرّب عبر ترويسة Referer وسجلّات الخادم ومحفوظات المتصفح.

انتبه للفحوص التي تجري فقط في JavaScript الصفحة نفسها، إذ لا يمرّ بها الطلب المزوَّر أبداً، وللرموز التي تُعاد دون فحص، ولنقاط النهاية (endpoints) التي تستقبل GET وتغيّر الحالة خفيةً. فمعيار RFC 9110 يعرّف GET طريقةً آمنة (safe method) دلالتها في جوهرها قراءة فقط، لذا فتغيير الحالة خلف GET عيب حتى قبل النظر في CSRF. وسجّل الطلب بالضبط وما غيّرته والاستجابة، ليعيد المطوّر إنتاج النتيجة بخطوة واحدة.

جرّبها: محلّل ترويسات HTTP

أخطاء شائعة

  • اختبار أن الطلب الصالح يعمل فقط، دون إعادة إرسال طلب بعد حذف الرمز أو تغييره.
  • عدّ وجود الرمز نتيجةً ناجحة — فالاختبار هو هل يرفض الخادم طلباً برمز مفقود أو خاطئ.
  • الثقة بـSameSite وحدها، ونسيان أن Lax ما زالت ترسل ملف تعريف الارتباط في التنقل على المستوى الأعلى بطريقة GET من موقع آخر.
  • الخلط بين CSRF وXSS وCORS: فـXSS يشغّل سكربتاً في أصل الموقع نفسه ويستطيع قراءة الرمز، وCORS تحكم فقط ما يُسمح للصفحة بقراءته لا ما إذا كان الإجراء ينفَّذ.
  • وضع رمز CSRF في عنوان URL حيث يتسرّب عبر ترويسة Referer وسجلّات الخادم ومحفوظات المتصفح.
  • ترك الإجراءات التي تغيّر الحالة على طلبات GET، وهو ما لن يحميه أي انضباط في رموز النماذج.

من منظور ضمان الجودة

في الفريق، CSRF سؤال مراجعة قبل أن يكون اختباراً. فعند ظهور نموذج أو نقطة نهاية جديدة، يسأل المختبِر كيف تُحمى — رمز، أم SameSite، أم فحص أصل — ويتفق مع المطوّر على الرفض المتوقَّع، فيكون للاختبار مرجع واضح. وتُكتب الإجابات في حالة الاختبار لا في الذاكرة.

والاختبار دائماً على نظام تملكه أو لديك إذن مكتوب باختباره، بحساب اختبار وقيم غير مدمِّرة؛ فأنت تؤكّد وجود دفاع وتطبيقه، لا تهاجم أحداً. والنتيجة تذكر الطلب، والتغيير الوحيد الذي قُبِل، والاستجابة؛ ويُصنَّف عادةً الإجراء الذي يغيّر الحساب دون فحص رمز عامل بخطورة عالية، لأنه قد يؤدي إلى الاستيلاء على الحساب.

تمرين عملي

قرّر أي الطلبات معرَّض لـCSRF

المطلوب

فيما يلي أربعة طلبات من تطبيق مصرفي تجريبي، والمستخدم مسجّل الدخول بملف تعريف ارتباط جلسة. قرّر لكلٍّ هل هو معرَّض لـCSRF كما هو، وسمِّ الدفاع المفقود، واكتب النتيجة المتوقَّعة لإعادة إرساله من أصل آخر.

افترض أن ملف تعريف ارتباط الجلسة يُرسل تلقائياً، وأنه لا يوجد فحص Origin ما لم يُظهره الطلب.

المادة
1  GET /transfer?to=ACME&amount=500 HTTP/1.1
   Cookie: sid=Zc4Hn8Tq1Wy6Bd3F

2  POST /transfer HTTP/1.1
   Cookie: sid=Zc4Hn8Tq1Wy6Bd3F
   Content-Type: application/x-www-form-urlencoded

   to=ACME&amount=500

3  POST /transfer HTTP/1.1
   Cookie: sid=Zc4Hn8Tq1Wy6Bd3F
   X-CSRF-Token: Lm8Vb3Nk7Xr5Tq2

   to=ACME&amount=500

4  POST /profile/theme HTTP/1.1
   Cookie: sid=Zc4Hn8Tq1Wy6Bd3F

   theme=dark
الخطوات
  1. لكل طلب: هل الإجراء يغيّر الحالة؟
  2. هل يحمل رمزاً لا يمكن توقّعه، مرتبطاً بالجلسة، يفحصه الخادم؟
  3. هل يستطيع موقع آخر أن يُحدث هذا الطلب مع إرفاق ملف تعريف الارتباط؟
  4. اكتب النتيجة المتوقَّعة بعد تطبيق الدفاع.
إظهار الإجابة النموذجية
الإجابة النموذجية

الطلب 1 هو الأسوأ: يغيّر الحالة عبر GET، فأي رابط أو عنصر <img> على أي موقع يطلقه، وحتى SameSite=Lax ترسل ملف تعريف الارتباط عند اتباع رابط. انقله إلى POST وأضف رمزاً؛ النتيجة المتوقَّعة بعد الإصلاح: رفض من أصل آخر.

الطلب 2 يغيّر الحالة عبر POST لكنه بلا رمز، فينجح نموذج مخفي يرسل تلقائياً من موقع آخر. أضف رمزاً مرتبطاً بالجلسة يفحصه الخادم؛ النتيجة المتوقَّعة: 403 دون رمز صالح.

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

الطلب 4 يغيّر إعداداً منخفض المخاطر لكنه معرَّض لـCSRF تماماً كالطلب 2؛ فيمكن قلب السمة لأي مستخدم مسجّل الدخول يفتح صفحة المهاجم. الخطورة أقل لا منعدمة. احمه بالطريقة نفسها، ولاحظ أن انخفاض الأثر يخصّ الخطورة لا الحاجة إلى الدفاع.

أسئلة شائعة

هل ما زال CSRF مهمّاً بعد أن صارت بعض المتصفحات تضبط ملفات تعريف الارتباط افتراضياً على SameSite=Lax؟

نعم. تختلف القيم الافتراضية بين المتصفحات وإصداراتها، وما زالت Lax تسمح بالتنقل على المستوى الأعلى بطريقة GET، وتضبط بعض التطبيقات SameSite=None لحاجات مشروعة عبر المواقع. وما زالت ورقة OWASP المرجعية تطلب فحصاً في الخادم، هو الرمز عادةً، دفاعاً أساسياً، وتعدّ SameSite دفاعاً في العمق.

ما الفرق بين CSRF وXSS؟

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

هل تحتاج واجهات REST التي تستعمل رمز حامل (bearer token) في ترويسة إلى حماية من CSRF؟

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

هل أستطيع اختبار CSRF على أي موقع لأتعلّم؟

لا. اختبر فقط أنظمة تملكها أو لديك إذن مكتوب باختبارها، بحساب اختبار وقيم غير مدمِّرة. وتزوير الطلبات ضد تطبيق شخص آخر هجوم مهما كانت النية.

المراجع

  • OWASP Cheat Sheet Series: Cross-Site Request Forgery PreventionOWASP Foundationدفاعات CSRF: رموز المزامنة وملفات تعريف الارتباط SameSite وفحص المصدر.
  • OWASP Web Security Testing Guide (WSTG)OWASP Foundationالمنهجية المرجعية لاختبار تطبيقات الويب وواجهات API بحثاً عن نقاط الضعف الأمنية، اختباراً تلو الآخر.
  • Using HTTP cookies — MDNMDN Web Docs (Mozilla)عمر ملف تعريف الارتباط ونطاقه وسمات Secure/HttpOnly/SameSite وآثارها الأمنية.
  • RFC 9110: HTTP SemanticsIETF · 2022معاني أساليب HTTP وحقول الترويسات ورموز الحالة والتفاوض على المحتوى، بغضّ النظر عن إصدار البروتوكول.

تدرّب بأدوات متقن

افحص ترويسات الطلب التي يرسلها متصفحك أو عميلك، مع شرح لكل منها.

اختبار واجهات API

تحليل ترويسات طلبات واستجابات HTTP الخام إلى جدول منظّم مع الشروحات.

الويب

اختبر نقاط نهاية REST بتحكم كامل في الطريقة والترويسات وجسم JSON، ثم أضف تأكيدات على الحالة والترويسات ومسارات JSON.

اختبار واجهات API

إنشاء أوامر cURL من الطريقة وعنوان URL والترويسات والمتن وخيارات المصادقة.

الويب

للتعمّق أكثر

تقدّمك في هذا الموضوع

جزء من مسار تعلم ضمان الجودة