الفكرة في سطر
Change the IDرقم 1042 بقى 1043
no ownership checkالسيرفر شاف إنه logged in وخلاص
Someone's Dataفاتورة عميل تاني
الـ IDOR هو أشهر شكل من Broken Access Control، وده رقم ١ في قايمة OWASP Top 10. والسبب بسيط: الكود بيسأل “إنت مين؟” وبينسى يسأل “وده بتاعك؟”.
🪪 Authentication
إنت مين؟ login و session و JWT. بيتعمل مرة في الـ middleware.
🔑 Authorization
مسموحلك بإيه؟ لازم يتعمل مع كل record بتقراه أو بتعدّله.
الكود اللي فيه الثغرة
invoices.ts
// ❌ أي user عامل login يقرا أي فاتورةapp.get("/invoices/:id", requireAuth, async (req, res) => { const invoice = await prisma.invoice.findUnique({ where: { id: req.params.id } }); res.json(invoice);});الحل: اربط الـ query بصاحبها
invoices.ts
app.get("/invoices/:id", requireAuth, async (req, res) => { const invoice = await prisma.invoice.findUnique({ where: { id: req.params.id } }); const invoice = await prisma.invoice.findFirst({ where: { id: req.params.id, ownerId: req.user.id }, }); if (!invoice) return res.sendStatus(404); // 404 مش 403: متأكدش إن الـ ID موجود res.json(invoice);});الـ query نفسها هي اللي فيها الفلتر، مش if بعدها ممكن يتنسي. نفس الكلام مع update و delete: استخدم updateMany أو deleteMany بنفس شرط الـ ownerId واتأكد إن العدد مش صفر.
أماكن بتتنسي
- الـ body مش الـ URL بس:
PATCH /profileوفيه{ "userId": 5, "role": "admin" }. متاخدش الـuserIdمن الـ body، خده من الجلسة. وحدد الحقول المسموح تتعدل (allow-list) عشانroleميعديش. ده اسمه mass assignment. - الـ nested resources:
/teams/1/members/99اتأكد إن العضو 99 تبع team 1، وإن الـ user عضو في team 1. - الـ frontend مش حماية: إخفاء زرار “حذف” في React مش authorization. أي حد يقدر يبعت الـ request من Postman.
- الـ multi-tenant: كل query لازم فيها
tenantId. فكّر في Row-Level Security في Postgres كطبقة أخيرة.
خلّصت الفصل لما
🧠 اختبر نفسك
١) الـ IDs عندنا UUID، يبقى مفيش IDOR؟
لأ. الـ UUID بيصعّب التخمين بس. الفحص على السيرفر هو الحماية.
٢) خبّيت زرار الحذف عن غير الـ admin. كده محمي؟
لأ. الـ API لازم تتحقق من الصلاحية، الـ UI مش حماية.
٣) ليه 404 أحسن من 403 هنا؟
عشان متأكدش للمهاجم إن الـ record ده موجود أصلًا.