طَبِّقْهاتعلّم المبدأ، وطبّقه بيدكمقرر 243 · أمن الويب
الدرس 28 · الوحدة 12: التحقق من الإدخال والوصول المصرح به

مشروع: تدقيق تطبيق وإصلاح ثغراته

اجمع ما تعلّمته: دقّق تطبيقًا حقيقيًّا، وأصلح ثغراته الثلاث، وتحقّق. ⏱ نحو 70 دقيقة.

بعد هذا الدرس ستستطيع أن:
  • تطبّق منهجية تدقيق أمني منظّمة.
  • تصنّف ثغرات تطبيق على OWASP.
  • تصلح حقن SQL و IDOR و XSS في تطبيق واحد.
  • تتحقق أن الهجمات فشلت والوظائف بقيت.

١منهجية التدقيق الأمني

هذا مشروع المقرر الذي يحقق ناتج التعلم: «يطبّق مبادئ الهجمات والدفاع… بحيث يعالج ثلاث ثغرات أمنية على الأقل». ستدقّق تطبيق متجر «رفيق» وتصلح ثغراته بمنهجية منظّمة:

ارسم سطح الهجوم

امرر على OWASP

جرّب الاختراق

أصلح وأعد الاختبار

دورة التدقيق: ارسم، مرّر على OWASP، جرّب الاختراق، أصلح وأعد الاختبار.
الخطوةماذا تفعل
ارسم سطح الهجومعدّد المسارات ومدخلاتها (الدرس 2)
مرّر على OWASPلكل مسار: أيّ فئة قد ينطبق؟ (الدرس 3)
جرّب الاختراقحوّل كل فئة إلى حالة اختبار سلبية
أصلح وأعد الاختبارطبّق الدفاع، ثم تأكد أن الهجوم فشل والوظيفة بقيت

٢التطبيق قيد التدقيق

أمامك خادم «رفيق» المصغّر، فيه ثلاث ثغرات من ثلاث فئات مختلفة. طبّق المنهجية لتجدها:

المسارالفئة (OWASP)الثغرة
GET /searchA03 الحقنبحث يُدمج نصًّا في استعلام SQL
GET /notes/:idA01 التحكم بالوصولIDOR: يُعرَض أي ملاحظة بمعرّفها
GET /commentsA03 الحقن (XSS)تعليق يُدمج في HTML بلا ترميز

كل ثغرة منها درستَ إصلاحها: الاستعلام المعلَّم (الدرس 9)، وشرط الملكية (الدرس 11)، وترميز المخرجات (الدرس 10). المشروع يجمعها في تطبيق واحد — كما في العمل الحقيقي.

٣بعد المقرر

أتممت أمن الويب: من المثلث الأمني إلى تدقيق تطبيق كامل. ما يبقى هو العادة:

المبدأيبقى معك
لا تثق بالمدخلكل مدخل خارجي عدائي حتى يُفحص
افصل أو رمّزالبيانات لا تصير تعليمات
أقل صلاحيةلكل طرف ومفتاح أدنى ما يكفيه
دفاع بالعمقطبقات متتالية، لا نقطة فشل واحدة
OWASP قائمة فحصمرّر عليها قبل كل إطلاق

والأمن مهنة تتجدّد: ثغرات جديدة كل عام، وقائمة OWASP تُحدَّث، فابقَ متعلّمًا. وما تعلّمته هنا للحماية؛ استخدمه لتبني أنظمة يثق بها الناس.

توقّع وتحقّق

أول خطوة في تدقيق أمني:

بحث يُدمج في SQL فئته:

عرض أي ملاحظة بمعرّفها فئته:

بعد الإصلاح يجب:

الخلاصة

تحدٍّ برمجي: دقّق وأصلح: ثلاث ثغرات

متقدم

خادم «رفيق» فيه ثلاث ثغرات: حقن SQL في البحث، و IDOR في الملاحظات، و XSS في التعليقات. أصلح الثلاث دون كسر الوظائف. هذا مشروع المقرر.

المطلوب:
  • البحث: استعلام معلَّم (حقن SQL يفشل).
  • الملاحظات: لصاحبها فقط، وغيرها 404 (IDOR يُسدّ).
  • التعليقات: ترميز المخرجات (XSS يُسدّ).
  • تبقى الوظائف الطبيعية الثلاث تعمل.
👀 اعرض الاستجابات المطلوبة
✓ التحقق الآلي: يشغّل المختبر خادمك من جديد ويرسل إليه هذه الطلبات بالترتيب ثم يفحص الردود
  1. GET /search?q=' OR '1'='1
  2. GET /search?q=سماعة
  3. GET /notes/2 🔑
  4. GET /notes/1 🔑
  5. POST /comment {"text": "<b>هجوم</b>"}
  6. GET /comments
  • الخادم يعمل ويرد على كل الطلبات بلا أخطاء غير معالَجة
  • حقن البحث لا يسرّب كل المنتجات
  • البحث العادي يعمل (سماعة)
  • سارة لا ترى ملاحظة خالد ← 404 (IDOR مسدود)
  • سارة ترى ملاحظتها
  • حفظ التعليق ← 201
  • التعليق الخطر مُرمَّز (XSS مسدود)
  • لا وسم b فعّال في التعليقات
  • استعلام معلَّم في البحث
  • شرط ملكية الملاحظة

💡 تلميح 1
البحث: "… WHERE name LIKE ?" مع ['%'+q+'%'] (الدرس 9).
💡 تلميح 2
الملاحظات: if (!n || n.userId !== req.userId) return res.status(404)… (الدرس 11).
💡 تلميح 3
التعليقات: دالة esc على كل تعليق قبل دمجه في HTML (الدرس 10).

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

const express = require('express');const app = express();app.use(express.json());app.use(express.urlencoded({ extended: false })); const sqlite3 = require('sqlite3');const db = new sqlite3.Database(':memory:');db.serialize(() => {  db.run(`CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, pass TEXT, role TEXT, email TEXT)`);  db.run(`INSERT INTO users (name, pass, role, email) VALUES    ('سارة', 's@rah123', 'customer', 'sara@mail.sa'),    ('خالد', 'kh@led456', 'customer', 'khaled@mail.sa'),    ('المدير', 'Adm!n#2025', 'admin', 'admin@mail.sa')`);  db.run(`CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT, price INTEGER)`);  db.run(`INSERT INTO products (name, price) VALUES    ('سماعة', 120), ('لوحة مفاتيح', 90), ('كاميرا ويب', 210)`);}); function esc(s){return String(s).replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));}const SESSIONS = { 'tok-sara': 1, 'tok-khaled': 2 };function auth(req, res, next) {  const id = SESSIONS[(req.headers.authorization || '').replace('Bearer ', '')];  if (!id) return res.status(401).json({ error: 'سجّل الدخول' });  req.userId = id; next();} // إصلاح 1: استعلام معلَّمapp.get('/search', (req, res) => {  db.all("SELECT id, name, price FROM products WHERE name LIKE ?", ['%' + (req.query.q || '') + '%'],    (e, rows) => e ? res.status(500).json({ error: String(e.message) }) : res.json(rows));}); // إصلاح 2: شرط الملكية + 404const notes = [{ id: 1, userId: 1, text: 'ملاحظة سارة' }, { id: 2, userId: 2, text: 'ملاحظة خالد' }];app.get('/notes/:id', auth, (req, res) => {  const n = notes.find(x => x.id === Number(req.params.id));  if (!n || n.userId !== req.userId) return res.status(404).json({ error: 'غير موجود' });  res.json(n);}); // إصلاح 3: ترميز المخرجاتlet comments = [];app.post('/comment', (req, res) => { comments.push(req.body.text || ''); res.status(201).json({ ok: true }); });app.get('/comments', (req, res) => {  res.type('html').send('<ul>' + comments.map(c => '<li>' + esc(c) + '</li>').join('') + '</ul>');}); app.listen(3000);
انتقل إلى تقييم الدرساختيار من متعدد، وصح وخطأ، وأكمل الفراغ، وتتبّع الناتج.ابدأ التقييم ←

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