طَبِّقْهاتعلّم المبدأ، وطبّقه بيدكمقرر 243 · أمن الويب
الدرس 13 · الوحدة 6: تقنيات الدفاع والاستجابة للهجمات

تحديد المعدل وكشف التخمين

كيف نمنع آلة تجرّب ألف كلمة مرور في الدقيقة من اقتحام الحسابات؟ ⏱ نحو 40 دقيقة.

بعد هذا الدرس ستستطيع أن:
  • تشرح هجوم التخمين عبر الشبكة وأنواعه.
  • تطبّق تحديد المعدل وتردّ 429.
  • توازن بين القفل والتأخير وأثرهما على المستخدم الحقيقي.
  • تتجنّب كشف أيّ حقل خطأ في رسائل الفشل.

١هجوم التخمين

كلمة المرور المجزّأة بـ bcrypt (الدرس 11) تقاوم التخمين دون اتصال، لكن يبقى التخمين عبر الشبكة: برنامج يجرّب آلاف كلمات المرور على صفحة الدخول. إن سمح الخادم بمحاولات بلا حدّ، فالوقت وحده يفصل المهاجم عن الحساب.

نوع التخمينالوصفالدفاع
القوة الغاشمةتجربة كل الاحتمالاتكلمات مرور طويلة + تحديد المعدل
القاموستجربة كلمات شائعةمنع الكلمات الضعيفة
حشو بيانات الاعتمادتجربة كلمات مسرَّبة من مواقع أخرىتحديد المعدل + مصادقة ثنائية

الدفاع الأساسي: تحديد المعدل — حدّ لعدد المحاولات في فترة زمنية. فحتى لو كانت كلمة المرور ضعيفة، لا يملك المهاجم إلا محاولات قليلة قبل أن يُوقَف.

٢تحديد المعدل عمليًّا

نحسب محاولات كل مصدر في نافذة زمنية، فإن تجاوزت الحدّ نردّ 429 (طلبات كثيرة). جرّب: المحاولتان الأوليان تُفحصان، والثالثة فصاعدًا تُمنع:

const express = require('express');const app = express();app.use(express.json());app.use(express.urlencoded({ extended: false })); const hits = {};                        // عدّاد المحاولات لكل مفتاح (IP مثلًا)const LIMIT = 3, WINDOW = 60000;        // 3 محاولات في الدقيقة function rateLimit(req, res, next) {  const key = req.ip || 'test';  const now = Date.now();  hits[key] = (hits[key] || []).filter(t => now - t < WINDOW);  if (hits[key].length >= LIMIT)    return res.status(429).json({ error: 'محاولات كثيرة، حاول لاحقًا' });  hits[key].push(now);  next();} app.post('/login', rateLimit, (req, res) => {  const ok = req.body.pass === 's@rah123';  res.status(ok ? 200 : 401).json({ granted: ok });}); app.listen(3000);

أول محاولتين رُدّتا 401 (خطأ لكن مسموح)، والثالثة 429 (مُنعت)، والرابعة — ولو بكلمة صحيحة — مُنعت أيضًا لأن النافذة امتلأت. المهاجم خُنق قبل أن يصل.

التقنيةالفكرةملحوظة
تحديد المعدلحدّ محاولات لكل مفتاح/نافذةالمفتاح: IP أو حساب أو كليهما
القفل المؤقتإيقاف الحساب بعد N فشلاحذر قفل مستخدمين حقيقيين
التأخير التصاعديكل فشل يزيد زمن الانتظاريبطئ الآلة دون قفل
المصادقة الثنائيةعامل ثانٍ غير كلمة المرورأقوى دفاع ضد الحشو

٣اعتبارات التطبيق

تحديد المعدل أداة حادة؛ طبّقها بحكمة:

وفي الإنتاج تُستخدم مكتبات جاهزة مثل express-rate-limit بدل الكتابة اليدوية، لتضبط النوافذ والتخزين الموزّع.

توقّع وتحقّق

رمز الحالة لتجاوز حدّ المحاولات:

تحديد المعدل يحمي أساسًا من:

خطر قفل الحساب نهائيًّا:

أقوى دفاع ضد حشو بيانات الاعتماد:

الخلاصة

تحدٍّ برمجي: حدّد معدل تسجيل الدخول

متقدم

هذا الخادم يقبل محاولات دخول بلا حدّ. أضف تحديد معدل يسمح بثلاث محاولات فقط، ثم يردّ 429.

المطلوب:
  • أضف وسيطًا يسمح بـ 3 محاولات ثم يردّ 429 للرابعة.
  • المحاولات الثلاث الأولى تُفحص عاديًّا (200/401).
  • طبّق الوسيط على مسار الدخول.
👀 اعرض الاستجابات المطلوبة
✓ التحقق الآلي: يشغّل المختبر خادمك من جديد ويرسل إليه هذه الطلبات بالترتيب ثم يفحص الردود
  1. POST /login {"pass": "a"}
  2. POST /login {"pass": "b"}
  3. POST /login {"pass": "c"}
  4. POST /login {"pass": "secret"}
  • الخادم يعمل ويرد على كل الطلبات بلا أخطاء غير معالَجة
  • المحاولة 1 تُفحص ← 401
  • المحاولة 2 تُفحص ← 401
  • المحاولة 3 تُفحص ← 401
  • المحاولة 4 تُمنع ← 429
  • يردّ 429 عند التجاوز

💡 تلميح 1
احفظ أوقات المحاولات في مصفوفة، واحذف ما خرج من النافذة.
💡 تلميح 2
إن بلغ العدد 3: return res.status(429)… قبل فحص الكلمة.
💡 تلميح 3
ضع الوسيط قبل معالج الدخول: app.post('/login', rateLimit, (req,res)=>…).

حاول بنفسك أولًا؛ للمسألة أكثر من حلّ صحيح.

const express = require('express');const app = express();app.use(express.json());app.use(express.urlencoded({ extended: false })); let attempts = 0;const hits = [];const LIMIT = 3, WINDOW = 60000;function rateLimit(req, res, next) {  const now = Date.now();  while (hits.length && now - hits[0] >= WINDOW) hits.shift();  if (hits.length >= LIMIT)    return res.status(429).json({ error: 'محاولات كثيرة' });  hits.push(now);  next();}app.post('/login', rateLimit, (req, res) => {  attempts++;  const ok = req.body.pass === 'secret';  res.status(ok ? 200 : 401).json({ granted: ok, attempts });});app.get('/attempts', (req, res) => res.json({ attempts }));app.listen(3000);
انتقل إلى تقييم الدرساختيار من متعدد، وصح وخطأ، وأكمل الفراغ، وتتبّع الناتج.ابدأ التقييم ←

📝 معلم؟ أرسل ملاحظة للمؤلف عن هذا الدرس