الهوية · فصل ٥

IDOR — غيّر الرقم في الـ URL تشوف داتا غيرك

Insecure Direct Object Reference · broken access control · ownership check · scoped queries
🎯 الهدف: تفرّق الـ authentication عن الـ authorization، وتتأكد من ملكية كل record على السيرفر في كل request.

الفكرة في سطر

الـ 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 ده موجود أصلًا.