إيه هي ثغرة الـ CSRF ؟
تخيل إنك عامل لوجن في موقع بنك مثلا، وبعد ما دخلت حسابك، الموقع عملك كوكيز (Session Cookie) عشان يعرف إنك أنت المستخدم المصرح له، والكوكي دي بتتبعت أوتوماتيك مع كل ريكويست تروح للموقع ده طالما البروزر مفتوح.
الـ CSRF هي ثغرة بتحصل لما المهاجم يقدر يخلي المتصفح بتاعك يبعت ريكويست مهم (زي تحويل فلوس، تغيير إيميل، حذف حساب، إلخ) للموقع ده من غير ما تعرف، وباسمك أنت، لأن الكوكي بتاعتك موجودة أصلًا فى المتصفح.
المهاجم مش بيسرق الكوكي لانه مش محتاج يعمل كده لانك بالفعل هتفتح اللينك من عندك فى المتصفح بتاعك هو بس بيخدع المتصفح بتاعك إنه يبعت الريكويست ده لوحده.
مثال عشان نفهمها كويس:
فرضًا في موقع اسمه bank.example.com، ولما تعمل لوجن بيتم انشاء كوكي اسمها وليكن sessionid=abc123.
في الصفحة دي في فورم بتعمل تحويل فلوس ولكن اليوزر ميعرفش انها بتعمل كده لان كل اللى ظاهرله مجرد زرار بيقوله"Win Money" لان الـ inputs التانيه مخفيه واول ما يدوس عليه هينفذ الاكشن اللى موجود فى الـفورم:
<form action="https://bank.example.com/transfer" method="POST">
<input type="hidden" name="to_account" value="attacker_account">
<input type="hidden" name="amount" value="10000">
<input type="submit" value="Win Moneyyyyyyyyyyyyyyyy">
</form>
لو الموقع مش محمي كويس، المهاجم هيعمل صفحة Fake على موقع تاني وعتبقى بالشكل ده بأى عنوان يشد اليوزر علشان يدوس عليه فيتم تنفيذ الاكشن :
<html>
<body>
<h1>اى عنوان يشد اليوزر</h1>
<img src="https://bank.example.com/transfer?to_account=attacker_account&amount=10000">
<!-- أو لو POST -->
<form id="evilform" action="https://bank.example.com/transfer" method="POST">
<input type="hidden" name="to_account" value="attacker_account">
<input type="hidden" name="amount" value="10000">
</form>
<script>
document.getElementById("evilform").submit();
// هنا بيعمل ارسال الفورم اتوماتيك لانه بياخد الاى دى علطول من الفورم وينفذه
</script>
</body>
</html>
فدلوقتي لو أنت عامل لوجن في البنك، والمهاجم خدعك إنك تفتح الصفحة الـ Fake دي (عن طريق لينك في إيميل، أو إعلان، أو حتى صورة في اى مكان )، المتصفح بتاعك هيبعت الريكويست ده للبنك مع الكوكي بتاعتك، والبنك هيشوف إن الريكويست جاي منك فعلاً، فيقبل التحويل، وأنت مش هتعرف حاجة!
طيب خلينا نشوف إمتى الثغرة دى تكون موجودة؟
- المستخدم لازم يكون عامل لوجن والـكوكي موجودة.
- لو السيستم بيعمل check عن طريق الـ cookie بس
- الإكشن اللي عايزين نعمله لازم يكون POST أو GET بدون حماية إضافية.
- الموقع مش بيستخدم Anti-CSRF Tokens
- الريكويست لازم يكون predictable، يعني المهاجم يعرف الـ parameters بالضبط (زي amount و to_account) علشان يقدر يعمل الـ Form المناسبه.
إزاي بقا بيتم تأمين المواقع من الثغرة دى ؟
🔵CSRF Tokens (الطريقة الستاندرد):
- الموقع بيولد توكن عشوائي قوي (غالبًا 128-bit أو أكتر) لكل سيشن أو لكل فورم.
- التوكن ده بيتبعت في hidden field في الفورم، وبيتبعت كمان في هيدر (زي X-CSRF-Token).
- السيرفر بيشيك إن التوكن اللي جاي في الريكويست مطابق للي في السيشن.
- لو مش موجود أو غلط → يرفض الريكويست.
- ده بيخلي المهاجم مش قادر يحط التوكن الصح في صفحته الـ Fake لان التوكن بيكون بتسجل فى السيرفر وبيتم مقارنته باللى اتبعتله اذا كان فى الهيدر او الفورم نفسه.
خلينا نفترض اننا هنكتب كود يعمل انشاء للـ CSRF Token بالــ PHP
- الكود ده بيتأكد إن لو التوكن مش موجود في الجلسة، يبقى بيولد واحد جديد ومعنى كده بيتعمل فى كل جلسه واحد جديد
session_start();
if (!isset($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
- تانى صفحه هتكون فيها اختبار الـ process اللى بتحصل يعنى لو الريكوست اتبعت من صفحه تانيه هل الريكوست ده فيه التوكن اللى متسجله فى السيرفر للسيشن الحاليه ؟ طب هل مطابقه ولا لاء ؟ هل هى موجوده اصلا ؟؟
//process.php
<?php
session_start();
if (!isset($_POST['csrf_token']) || $_POST['csrf_token'] !== $_SESSION['csrf_token']) {
// التوكن مش متطابق أو مش موجود
die("Invalid CSRF token");
}
// لو التوكن متطابق، نكمل العملية
?>
- بعد كده تيجى الفورم نفسها بقا اللى بتاخد البيانات تروح بيها على صفحة الـ process اللى بيتم فيها هناك يشوف صحة التوكن اللى مبعوته
<?php session_start(); ?>
<form method=POST" action="process.php">
<!-- باقي الحقول -->
<input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token']; ?>">
<button type="submit">Send</button>
</form>
🔵SameSite Cookies :
ده اعداد بيتعمل اللى بيخلى الكوكيز تتبعت فى حالات معينه
- لما تعمل Set-Cookie، تضيف
SameSite=LaxأوSameSite=Strict.- الـ Strict: الكوكي مش هتتبعت خالص في ريكويستات من مواقع تانية يعنى الكوكيز هتتبعت بس لما الطلب يكون جاي من نفس الدومين بالظبط.
- الـ Lax: هتتبعت بس في navigation عادي (زي كليك على لينك)، مش في POST أو صور أو iframe.
- ده بيوقف معظم هجمات CSRF بدون ما تحتاج توكن، بس مش كلها . وهنا كود PHP بردو بيوضع ازاى بيتم اعداد الكوكي دى
session_set_cookie_params([
'lifetime' => 0, // مدة صلاحية الكوكيز للجلسة
'path' => '/',
'domain' => 'example.com',
'secure' => true, // الكوكيز هتتبعت بس عبر HTTPS
'httponly' => true, // الكوكيز مش هتكون متاحة للجافاسكريبت
'samesite' => 'Strict' // أو 'Lax' حسب الحاجة
]);
session_start();
لما يكون إعداد الكوكيز بصفة SameSite=Strict أو Lax، المتصفح هيتأكد قبل ما يبعت الكوكيز مع أي طلب إن الطلب ده جاي من نفس الدومين. بالتالي لو المهاجم حاول يعمل CSRF من موقع تاني، الكوكيز مش هتتبعت مع الطلب، والسيرفر مش هيقدر يتعرف على المستخدم أو ينفذ العملية لان مفيهاش كوكيز اصلا يعنى يعتبر كده الريكويست رايحله بدون ما يكون فيه عملية تسجيل دخول فميهاش اى خطر ساعتها هيتم تحويله لصفحة تسجيل الدخول.. والاكشن اللى موجود فى الفورم مش هيكون ليه اى لازمة
🔵Double Submit Cookie:
- التوكن بيتبعت في كوكي وفي هيدر/فورم، والسيرفر بيقارن بينهم.
🔵Custom Headers:
- الـ AJAX بيتبعت هيدر زي X-Requested-With، والسيرفر يرفض لو مش موجود (لأن المتصفح مش بيسمح لجافاسكريبت خارجي يضيف هيدرز custom في cross-origin).
🔵Re-authentication
للإكشنات المهمة (زي تغيير باسورد أو تحويل فلوس او تغير الاميل وهكذا).
إيه هو الـ Preflight Request وعلاقتة بالــ CSRF
لما بنتكلم عن CORS (Cross-Origin Resource Sharing)، ده آلية في المتصفحات عشان تحمي المواقع من ريكويستات من origins تانية (يعني domains مختلفة).
الـ Preflight Request هو ريكويست “استطلاعي” بيحصل قبل الريكويست الحقيقي في حالات معينة، عشان المتصفح يسأل السيرفر: “هل مسموح لي أبعت الريكويست ده من origin تاني؟”
الـ Preflight بيبقى ريكويست من نوع OPTIONS، وبيتبعت أوتوماتيك لما الريكويست الحقيقي يكون “non-simple”، يعني:
- الميثود مش GET أو HEAD أو POST (مثل PUT, DELETE, PATCH…).
- أو فيه custom headers (زي X-CSRF-Token, Authorization, Content-Type غير الـ simple ones).
- أو الـ Content-Type مش واحد من الثلاثة دول: application/x-www-form-urlencoded, multipart/form-data, text/plain.
مثال: لو عايز تبعت POST مع Content-Type: application/json و header X-CSRF-Token من موقع تاني → المتصفح هيبعت أولًا OPTIONS request كده:
OPTIONS /api/change-email HTTP/1.1
Host: target.com
Origin: https://evil.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-CSRF-Token, Content-Type
السيرفر لازم يرد بـ headers زي:
Access-Control-Allow-Origin: https://evil.com
Access-Control-Allow-Methods: POST
Access-Control-Allow-Headers: X-CSRF-Token, Content-Type
لو الرد مش صح → الريكويست الحقيقي ميتبعتش خالص.
علاقته بالـ CSRF إيه؟
الـ Preflight ده طبقة حماية إضافية ضد CSRF في بعض الحالات، خاصة في الـ modern APIs (JSON-based):
-
الحماية الإيجابية:
- هجمات CSRF الكلاسيكية (HTML form أو
أو auto-submit JS) مش بتسبب Preflight لأنها “simple requests”:
- ميثود POST أو GET.
- Content-Type عادي (urlencoded أو multipart).
- مفيش custom headers.
- يعني الـ CSRF الكلاسيكي بيعدي عادي، والسيرفر ميقدرش يعتمد على Preflight كحماية كاملة.
- هجمات CSRF الكلاسيكية (HTML form أو
-
لكن في الـ APIs اللي بتستخدم custom headers أو non-simple requests:
- كتير APIs بتطلب X-CSRF-Token في header (مش في body).
- لما المهاجم يحاول يبعت CSRF بـ fetch أو XMLHttpRequest من موقع تاني مع الـ header ده → هيحصل Preflight.
- لو السيرفر مش معموله CORS config صح (مش بيسمح للـ origin الشرير) → الـ Preflight هيفشل → الريكويست الحقيقي ميتبعتش → الهجوم يفشل.
يعني: في الـ SPA apps (React, Angular, Vue) اللي بتعتمد على JSON APIs + X-CSRF-Token header → الـ Preflight بيبقى حماية قوية جدًا ضد CSRF من خارج.
-
الجانب السلبي (بايباس):
- لو السيرفر معموله CORS config واسع (Access-Control-Allow-Origin: * أو يسمح لكل origins) → الـ Preflight هيعدي → الهجوم ينجح.
- أو لو الـ API بيقبل simple requests بس (urlencoded) بدون custom headers → مفيش Preflight → CSRF كلاسيكي يعدي عادي.
ريبورتـات
- https://hackerone.com/reports/3367292
- https://hackerone.com/reports/1668489
- https://hackerone.com/reports/2699029
- https://hackerone.com/reports/1890310
- https://hackerone.com/reports/2699029
- https://hackerone.com/reports/2712857
- https://hackerone.com/reports/2313478
- https://hackerone.com/reports/1964211
- https://hackerone.com/reports/1096128
- https://hackerone.com/reports/1096141
- https://hackerone.com/reports/2261600
- https://hackerone.com/reports/1961163
- https://hackerone.com/reports/2002352