الهاكر سمع عن CSRF فأضاف رمزا سريا يتحقق منه مع طلب POST لتغيير البريد لكنه نسي أن نفس التغيير متاح أيضا عبر طلب GET قديم تركت فيه خانة الرمز بلا فحص الزعيم ما زال مسجلا دائما
نفس لوحة تحكم الغربان لكن المبرمج أضاف رمز CSRF على طلب POST ولا تقدر قراءته من موقعك لكن بقي مسار قديم يغير البريد عبر GET بلا فحص رمز كوكي الجلسة بلا SameSite فيرفق دائما أنت تختار طريقة توصيل الطلب المزور من صفحة المهاجم نموذج يرسل تلقائيا POST أو صورة تحمل رابطا GET أو رابط يفتحه الزعيم بنفسه GET والتطبيق يوضح هل أرفق المتصفح كوكي الجلسة ولماذا
لماذا نجح الحل
الحماية كانت ناقصة الرمز يفحص على طلب POST فقط بينما نفس الفعل متاح عبر GET بلا فحص المهاجم يختار الطريق غير المحمي القاعدة: كل طلب يغير حالة يجب أن يكون POST (أو فعلا غير آمن) ويجب التحقق من رمز CSRF على كل مسار يغير حالة بلا استثناء وأفعال القراءة فقط هي من تترك لـ GET وأضف SameSite على الكوكي
السبب الجذري
الحماية طبقت على فعل واحد POST وتركت مسارا قديما يغير الحالة عبر GET بلا رمز والأفعال التي تغير الحالة يجب ألا تكون متاحة عبر GET أصلا
كيف يصلح الكود
ثغرة شائعة: الحماية على مسار وتنسى على مسار ثان يؤدي نفس الفعل راجع كل المسارات التي تغير حالة لا مسارا واحدا
الكود المصاب
app.post('/settings', requireToken, (req, res) => { /* protected */ });
// Left over from an older version, and it changes state with no token check:
app.get('/settings', (req, res) => {
if (!req.session.user) return res.status(401).end();
db.setRecoveryEmail(req.session.user, req.query.email);
res.end('updated');
});
الكود الآمن
// State changes are POST only, and the token is checked everywhere.
app.post('/settings', requireToken, (req, res) => {
db.setRecoveryEmail(req.session.user, req.body.email);
res.end('updated');
});
// GET /settings only READS; it never changes anything.
app.get('/settings', (req, res) => res.json({ email: db.recoveryEmail(req.session.user) }));
الأثر في الواقع
ثغرات CSRF كثيرا ما تعود من مسار قديم أو بديل نسي المبرمج حمايته فيلتف المهاجم حول الرمز بالكامل
كيف يكتشفها المدافعون
المدافع يفرض الرمز على كل فعل يغير الحالة ويمنع GET من تغيير أي شيء ويرصد المسارات القديمة غير المحمية