مشروع: تدقيق تطبيق وإصلاح ثغراته
اجمع ما تعلّمته: دقّق تطبيقًا حقيقيًّا، وأصلح ثغراته الثلاث، وتحقّق. ⏱ نحو 70 دقيقة.
- تطبّق منهجية تدقيق أمني منظّمة.
- تصنّف ثغرات تطبيق على
OWASP. - تصلح حقن
SQLوIDORوXSSفي تطبيق واحد. - تتحقق أن الهجمات فشلت والوظائف بقيت.
١منهجية التدقيق الأمني
هذا مشروع المقرر الذي يحقق ناتج التعلم: «يطبّق مبادئ الهجمات والدفاع… بحيث يعالج ثلاث ثغرات أمنية على الأقل». ستدقّق تطبيق متجر «رفيق» وتصلح ثغراته بمنهجية منظّمة:
| الخطوة | ماذا تفعل |
|---|---|
| ارسم سطح الهجوم | عدّد المسارات ومدخلاتها (الدرس 2) |
| مرّر على OWASP | لكل مسار: أيّ فئة قد ينطبق؟ (الدرس 3) |
| جرّب الاختراق | حوّل كل فئة إلى حالة اختبار سلبية |
| أصلح وأعد الاختبار | طبّق الدفاع، ثم تأكد أن الهجوم فشل والوظيفة بقيت |
٢التطبيق قيد التدقيق
أمامك خادم «رفيق» المصغّر، فيه ثلاث ثغرات من ثلاث فئات مختلفة. طبّق المنهجية لتجدها:
| المسار | الفئة (OWASP) | الثغرة |
|---|---|---|
GET /search | A03 الحقن | بحث يُدمج نصًّا في استعلام SQL |
GET /notes/:id | A01 التحكم بالوصول | IDOR: يُعرَض أي ملاحظة بمعرّفها |
GET /comments | A03 الحقن (XSS) | تعليق يُدمج في HTML بلا ترميز |
كل ثغرة منها درستَ إصلاحها: الاستعلام المعلَّم (الدرس 9)، وشرط الملكية (الدرس 11)، وترميز المخرجات (الدرس 10). المشروع يجمعها في تطبيق واحد — كما في العمل الحقيقي.
٣بعد المقرر
أتممت أمن الويب: من المثلث الأمني إلى تدقيق تطبيق كامل. ما يبقى هو العادة:
| المبدأ | يبقى معك |
|---|---|
| لا تثق بالمدخل | كل مدخل خارجي عدائي حتى يُفحص |
| افصل أو رمّز | البيانات لا تصير تعليمات |
| أقل صلاحية | لكل طرف ومفتاح أدنى ما يكفيه |
| دفاع بالعمق | طبقات متتالية، لا نقطة فشل واحدة |
| OWASP قائمة فحص | مرّر عليها قبل كل إطلاق |
والأمن مهنة تتجدّد: ثغرات جديدة كل عام، وقائمة OWASP تُحدَّث، فابقَ متعلّمًا. وما تعلّمته هنا للحماية؛ استخدمه لتبني أنظمة يثق بها الناس.
توقّع وتحقّق
أول خطوة في تدقيق أمني:
بحث يُدمج في SQL فئته:
عرض أي ملاحظة بمعرّفها فئته:
بعد الإصلاح يجب:
الخلاصة
- التدقيق دورة: ارسم سطح الهجوم، مرّر على
OWASP، جرّب الاختراق، أصلح وأعد الاختبار. - ثغرات المشروع الثلاث: حقن
SQL (A03)، وIDOR (A01)، وXSS (A03). - إصلاحها: استعلام معلَّم، وشرط ملكية مع 404، وترميز مخرجات.
- الأمن عادة ومهنة متجددة: لا تثق بالمدخل، افصل أو رمّز، أقل صلاحية، دفاع بالعمق.
تحدٍّ برمجي: دقّق وأصلح: ثلاث ثغرات
متقدمخادم «رفيق» فيه ثلاث ثغرات: حقن SQL في البحث، و IDOR في الملاحظات، و XSS في التعليقات. أصلح الثلاث دون كسر الوظائف. هذا مشروع المقرر.
- البحث: استعلام معلَّم (حقن SQL يفشل).
- الملاحظات: لصاحبها فقط، وغيرها 404 (IDOR يُسدّ).
- التعليقات: ترميز المخرجات (XSS يُسدّ).
- تبقى الوظائف الطبيعية الثلاث تعمل.
👀 اعرض الاستجابات المطلوبة
GET /search?q=' OR '1'='1GET /search?q=سماعةGET /notes/2🔑GET /notes/1🔑POST /comment{"text": "<b>هجوم</b>"}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=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[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);