📚 محتوى دفاعي بحت: الهدف منع الثغرة في كودك.
ما هي IDOR؟
Insecure Direct Object Reference: تحدث عندما يكشف التطبيق معرّفًا (ID) مباشرًا لسجلّ ما (فاتورة، طلب، ملف) في الرابط أو الطلب، دون أن يتحقّق الخادم أن المستخدم الحالي يملك هذا السجلّ فعلًا.
هذه من أكثر الثغرات انتشارًا في الواقع — تصنّفها OWASP كأخطر ثغرة في الـ APIs تحديدًا (Broken Object Level Authorization)، لأنها بسيطة الاستغلال وسهلة الوقوع فيها.
كيف تحدث (مفهوميًّا)
مستخدم مسجّل دخول يشاهد فاتورته:
GET /api/invoices/1042
يجرّب تغيير الرقم فقط:
GET /api/invoices/1043
لو تحقّق الخادم فقط من "هل المستخدم مسجّل دخول؟" دون التحقّق من "هل هذه الفاتورة له؟"، يحصل على فاتورة مستخدم آخر بالكامل.
نفس النمط يتكرّر مع أي مورد له معرّف: طلبات، رسائل، ملفات مرفوعة، حتى إعدادات حساب.
لماذا سهلة الوقوع فيها
لأن الكود "يعمل" ظاهريًّا — الاستعلام يُرجع بيانات صحيحة، ولا يوجد خطأ تشغيلي يلفت الانتباه أثناء التطوير. الثغرة تكمن في غياب شرط لا في كود خاطئ.
// ❌ خطر: يجلب أي فاتورة بأي رقم دون التحقّق من الملكية
app.get("/api/invoices/:id", async (req, res) => {
const invoice = await Invoice.findById(req.params.id);
res.json(invoice);
});
الدفاع: تحقّق من الملكية على كل طلب
القاعدة الذهبية: كل استعلام يجلب سجلًّا بمعرّف من المستخدم، يجب أن يتحقّق أن هذا السجلّ ملك المستخدم الحالي — لا أن يكتفي بالتحقّق من تسجيل الدخول فقط.
// ✅ آمن: الاستعلام نفسه يقيّد النتيجة بمالك الفاتورة
app.get("/api/invoices/:id", async (req, res) => {
const invoice = await Invoice.findOne({
_id: req.params.id,
ownerId: req.user.id, // شرط الملكية داخل الاستعلام نفسه
});
if (!invoice) return res.status(404).json({ error: "غير موجودة" });
res.json(invoice);
});
دمج شرط الملكية داخل الاستعلام نفسه (بدل جلب السجلّ ثم مقارنته بعد ذلك بشرط if) يقلّل احتمال نسيان التحقّق في مسار كود آخر يستخدم نفس الدالة لاحقًا.
معرّفات يصعب تخمينها ليست بديلًا عن التحقّق
استخدام UUID عشوائي بدل رقم متسلسل (1042) يمنع تخمين معرّفات أخرى، لكنه لا يمنع الثغرة إن سُرّب المعرّف بأي طريقة (رابط مُشارَك، سجلّ، طلب سابق). التحقّق من الملكية على الخادم شرط لا غنى عنه، بغضّ النظر عن شكل المعرّف.
قائمة تحقّق سريعة
- كل مسار يقبل معرّف سجلّ من المستخدم (
:idبالرابط أو بجسم الطلب) يحتاج شرط ملكية أو صلاحية صريح. - طبّق نفس المنطق على العمليات لا القراءة فقط: تعديل، حذف، رفع حالة.
- اكتب اختبارات تحديدًا لهذا: "مستخدم أ يحاول الوصول لسجلّ مستخدم ب" يجب أن يفشل دائمًا.
- راجع أي دالة "جلب بالمعرّف" عامة (generic) تُستخدم بأكثر من مسار — تأكّد أن شرط الملكية مضاف بكل استدعاء لها.
💡 IDOR جزء من عائلة أوسع تسمّى Broken Access Control (الدرس السابق عن التحكّم بالوصول) — لكنها تستحقّ انتباهًا خاصًّا لأنها الأسهل استغلالًا والأكثر تكرارًا في الثغرات المُبلَّغ عنها فعليًّا.
🎯 التالي: أمان JWT.