VULN · آسیب‌پذیری‌ها کنترل دسترسی بالا

IDOR — ارجاع مستقیم ناامن به شیء

Insecure Direct Object Reference

IDOR زمانی رخ می‌دهد که برنامه از شناسه‌ای که کاربر تعیین می‌کند برای ارجاع به یک شیء استفاده کند و بررسی نکند که آن شیء متعلق به همان کاربر است — رایج‌ترین و پرکشف‌ترین زیرمجموعه‌ی کنترل دسترسی شکسته.

تیم فنی پی‌هانتر

IDOR چیست؟

IDOR مخفف Insecure Direct Object Reference است: «ارجاع مستقیم ناامن به شیء». مکانیزم آن در یک جمله خلاصه می‌شود — برنامه یک شناسه از ورودی کاربر می‌گیرد، آن را مستقیماً به لایه‌ی داده می‌دهد، و هیچ‌جا بررسی نمی‌کند که کاربرِ درخواست‌دهنده مجاز به دسترسی به آن شیء هست یا نه.

در سطح کد، تفاوت آسیب‌پذیر و امن معمولاً یک شرط است:

// vulnerable
invoice = db.query("SELECT * FROM invoices WHERE id = ?", id);

// safe
invoice = db.query(
  "SELECT * FROM invoices WHERE id = ? AND owner_id = ?",
  id, session.userId);

نکته‌ی مهم این است که IDOR یک زیرمجموعه‌ی کنترل دسترسی شکسته (A01:2025) است، نه یک دسته‌ی مستقل در OWASP Top 10. در راهنمای آزمون WSTG، IDOR یکی از چهار آزمون بخش مجوزدهی (WSTG-ATHZ) است.

یک بدفهمی رایج در نام‌گذاری هم وجود دارد: «Direct» به‌معنای «عدد ترتیبی» نیست. هر چیزی که به یک شیء ارجاع می‌دهد و از سمت کاربر می‌آید می‌تواند وکتور IDOR باشد — عدد افزایشی، UUID، هش، نام فایل، شماره‌ی سفارش، آدرس ایمیل، یا حتی یک ادعای داخل توکن JWT که سرور بدون راستی‌آزمایی به آن اعتماد می‌کند.

انواع IDOR و آسیب‌پذیری‌های خویشاوند

  • IDOR خواندنی (افقی): دیدن داده‌ی کاربر دیگر — فاکتور، پیام، پرونده‌ی پزشکی، فایل آپلودشده. رایج‌ترین حالت و معمولاً پرحجم‌ترین از نظر داده‌ی افشاشده.
  • IDOR نوشتنی: تغییر یا حذف داده‌ی کاربر دیگر. اثر به‌مراتب سنگین‌تر است و اغلب فراموش می‌شود، چون تیم توسعه فقط GET را محافظت کرده است.
  • IDOR کور (Blind): پاسخ داده‌ای برنمی‌گرداند اما عملیات انجام می‌شود — مثلاً POST /api/subscriptions/9812/cancel که با کد ۲۰۴ پاسخ می‌دهد. برای اثبات باید اثر جانبی را از مسیر دیگری مشاهده کرد.
  • IDOR در فایل و منابع ایستا: مسیرهایی مثل /uploads/2026/03/contract-1042.pdf که مستقیماً توسط وب‌سرور سرو می‌شوند و هرگز از لایه‌ی مجوزدهی برنامه عبور نمی‌کنند.

سه خویشاوند نزدیک که در گزارش باید از IDOR تفکیک شوند:

عنوانماهیت
BOLAهمان IDOR، با نامی که OWASP API Security Top 10 (نسخه‌ی ۲۰۲۳) برایش انتخاب کرده و رتبه‌ی یک آن فهرست است
BFLAدسترسی به یک عملیات غیرمجاز، نه یک شیء — مثلاً اجرای DELETE روی منبعی که فقط GET آن مجاز بود
BOPLA / Mass Assignmentخواندن یا نوشتن ویژگی‌هایی از شیء که نباید در دسترس باشند — مثل ارسال {"role":"admin"} در بدنه‌ی به‌روزرسانی پروفایل

سناریوی واقعی

یک سامانه‌ی مدیریت قرارداد، فایل‌های امضاشده را با آدرسی شامل UUID سرو می‌کند: /files/9f1c7d3a-…-b41e/download. تیم توسعه معتقد است چون شناسه غیرقابل حدس است، بررسی مالکیت لازم نیست.

در تست نفوذ، سه مسیر نشت شناسه پیدا می‌شود. اول، اندپوینت GET /api/projects/{id}/files فهرست فایل‌های یک پروژه را با UUID کامل برمی‌گرداند و خودش کنترل مجوز کافی ندارد. دوم، خروجی گزارش اکسل که برای همه‌ی اعضای سازمان قابل دانلود است، ستون شناسه‌ی فایل را دارد. سوم، صفحه‌ی پیش‌نمایش سند یک اسکریپت تحلیل ثالث بارگذاری می‌کند و UUID در هدر Referer به دامنه‌ی بیرونی ارسال می‌شود.

نتیجه: «غیرقابل حدس بودن» شناسه اصلاً به آزمون گذاشته نشد، چون مهاجم نیازی به حدس زدن نداشت. این دقیقاً همان چیزی است که در بند بعد توضیح داده می‌شود.

اشتباه رایج: «از UUID استفاده می‌کنیم پس IDOR نداریم»

باور غلط رایج

«شناسه‌های ما UUID هستند، پس IDOR نداریم.» شناسه‌ی غیرقابل حدس فقط هزینه‌ی کشف را بالا می‌برد؛ هیچ بررسی مجوزی اضافه نمی‌کند. اگر مهاجم به هر شکلی شناسه را به‌دست آورد، برنامه همچنان داده را تحویل می‌دهد. UUID یک کنترل دسترسی نیست، یک فضای نام است.

مسیرهایی که UUID از آن‌ها نشت می‌کند، در عمل فراوان‌اند:

  • پاسخ اندپوینت‌های دیگر — به‌ویژه اندپوینت‌های فهرست‌کننده، جست‌وجو و پیشنهاد خودکار.
  • خروجی گزارش‌ها و فایل‌های CSV/Excel که برای همه‌ی اعضای سازمان قابل دسترس‌اند.
  • هدر Referer هنگام بارگذاری منابع ثالث، و لاگ‌های سرور و CDN.
  • پروفایل‌های عمومی، آدرس‌های اشتراک‌گذاری، ایمیل‌های اعلان و پیوست‌ها.
  • ساختار خود شناسه: UUID نسخه‌ی ۱ مهر زمانی و شناسه‌ی سخت‌افزاری (MAC) را در خود کدگذاری می‌کند و بنابراین اصلاً تصادفی نیست. اگر شناسه‌ها با v1 تولید می‌شوند، پیش‌بینی‌پذیری واقعی روی میز است.

نتیجه‌ی عملی برای گزارش‌نویسی: در یافته‌ی IDOR هرگز ننویسید «به‌دلیل ترتیبی بودن شناسه‌ها». علت واقعی نبود بررسی مالکیت است و راهکار هم تغییر نوع شناسه نیست.

چگونه در تست نفوذ کشف می‌شود

روش استاندارد و قابل تکرار، مقایسه‌ی دو حساب هم‌سطح است:

  1. دو حساب هم‌سطح بگیرید (کاربر A و کاربر B، هر دو با نقش یکسان). بدون حساب دوم، آزمون افقی معنا ندارد و شما فقط دارید حدس می‌زنید.
  2. با حساب A همه‌ی جریان‌های کاری را کامل اجرا کنید و با حساب B هم همین کار را انجام دهید تا تاریخچه‌ی پروکسی برای هر دو کامل شود.
  3. شناسه‌ها را استخراج کنید: شناسه‌ی رکوردهای B را از تاریخچه‌ی B بردارید و در درخواست‌های A جایگذاری کنید. جای شناسه فقط مسیر URL نیست — پارامتر Query، بدنه‌ی JSON، فرم چندبخشی، کوکی، هدرهای سفارشی و ادعاهای توکن را هم بررسی کنید.
  4. پاسخ‌ها را تفاضلی مقایسه کنید. کد وضعیت به‌تنهایی کافی نیست؛ به طول پاسخ، زمان پاسخ و تفاوت محتوا نگاه کنید. پاسخ 404 در برابر 403 خودش یک نشت اطلاعات است (وجود یا عدم وجود رکورد).
  5. متدها را جابه‌جا کنید: اگر GET محافظت شده است، PUT، PATCH، DELETE و هدر X-HTTP-Method-Override را امتحان کنید.
  6. اندپوینت‌های دسته‌ای و GraphQL: اندپوینت‌هایی که آرایه‌ای از شناسه می‌گیرند (ids=[1041,1042]) یا کوئری‌های تودرتوی GraphQL، اغلب بررسی مجوز را فقط روی گره‌ی ریشه انجام می‌دهند.

ابزار کمکی: افزونه‌های بازپخش متقاطع در Burp Suite، امکانات معادل در Caido برای مقایسه‌ی خودکار پاسخ‌ها، و ffuf برای پیمایش دامنه‌ی شناسه‌ها وقتی شناسه‌ها ترتیبی هستند. اما تصمیم نهایی همیشه دستی است — ابزار نمی‌داند کاربر A باید رکورد ۱۰۴۲ را ببیند یا نه. امنیت برنامه‌ی وب در این نقطه به قضاوت انسانی وابسته است.

نمونه‌ی فنی و ارتباط با OWASP و CWE

شکل ساده و غیرتهاجمی اثبات مفهوم در گزارش:

GET /api/orders/8841 HTTP/1.1
Host: shop.example.com
Cookie: session=<session-of-user-A>

HTTP/1.1 200 OK
{"order":8841,"customer":"user-B","phone":"09xx…"}

در شواهد گزارش باید سه چیز دیده شود: نشست متعلق به کاربر A، شناسه‌ی متعلق به کاربر B، و داده‌ی برگشتی که مالکیت B را ثابت می‌کند. اثبات باید حداقلی باشد — یک رکورد کافی است و استخراج انبوه داده‌ی واقعی مشتریان در یک تست مجاز، غیرحرفه‌ای و از نظر قراردادی پرریسک است.

نگاشت به استانداردها

  • OWASP Top 10:2025: زیرمجموعه‌ی A01 — Broken Access Control.
  • CWE-639: Authorization Bypass Through User-Controlled Key — نزدیک‌ترین شناسه به IDOR، که در فهرست ۲۰۲۵ CWE Top 25 رتبه‌ی ۲۴ را دارد. CWE-862 (نبود مجوزدهی) رتبه‌ی چهارم همان فهرست است.
  • WSTG-ATHZ: آزمون IDOR یکی از چهار آزمون بخش مجوزدهی راهنمای آزمون OWASP.
  • OWASP API Security Top 10 (2023): با نام BOLA، رتبه‌ی یک.

پرسش‌های متداول

آیا استفاده از UUID جلوی IDOR را می‌گیرد؟

خیر. UUID فقط حدس زدن شناسه را دشوار می‌کند و هیچ بررسی مجوزی اضافه نمی‌کند. شناسه‌ها از پاسخ سایر اندپوینت‌ها، خروجی گزارش‌ها، هدر Referer، لاگ‌ها و آدرس‌های اشتراکی نشت می‌کنند. ضمناً UUID نسخه‌ی ۱ مهر زمانی و آدرس MAC را در خود دارد و تصادفی نیست. راه‌حل واقعی، بررسی مالکیت در سمت سرور برای هر درخواست است.

چطور IDOR را در برنامه‌ی خودم تست کنم؟

دو حساب هم‌سطح بسازید، با هر دو همه‌ی جریان‌های کاری را اجرا کنید، سپس شناسه‌های حساب دوم را در درخواست‌های حساب اول جایگذاری کنید و پاسخ‌ها را مقایسه کنید. جای شناسه فقط URL نیست؛ بدنه‌ی JSON، کوکی، هدرهای سفارشی و اندپوینت‌های دسته‌ای را هم بررسی کنید. متدهای PUT و DELETE را جدا از GET بیازمایید.

شدت IDOR در گزارش تست نفوذ چقدر است؟

به داده و عملیات بستگی دارد. خواندن داده‌ی هویتی یا مالی کاربران دیگر معمولاً بالا تا بحرانی امتیاز می‌گیرد؛ IDOR نوشتنی که امکان تغییر یا حذف داده‌ی دیگران را می‌دهد تقریباً همیشه بحرانی است. امتیازدهی را با CVSS انجام دهید و نسخه‌ی مورد استفاده را در گزارش ذکر کنید.

تفاوت IDOR و BOLA چیست؟

عملاً یک چیزند. BOLA (Broken Object Level Authorization) نامی است که OWASP در فهرست API Security Top 10 نسخه‌ی ۲۰۲۳ برای همین مفهوم انتخاب کرده و رتبه‌ی یک آن فهرست است. اگر گزارشی برای تست API می‌نویسید، اصطلاح BOLA دقیق‌تر و قابل ارجاع‌تر است.

پیشگیری / رفع

به ترتیب اثربخشی:

  1. مجوزدهی را داخل کوئری ببرید. به‌جای واکشی سپس بررسی، شرط مالکیت را جزئی از خود پرس‌وجو کنید: WHERE id = ? AND owner_id = ? یا معادل آن در ORM/موتور سیاست. این کار «فراموش کردن بررسی» را تقریباً غیرممکن می‌کند.
  2. رد پیش‌فرض. هر منبع تا وقتی صراحتاً عمومی اعلام نشده، ممنوع است.
  3. ارجاع غیرمستقیم مقیدشده به نشست جایی که عملی است: نگاشتی که فقط در محدوده‌ی نشست فعلی معنا دارد، به‌جای افشای کلید اصلی پایگاه‌داده.
  4. محدودسازی فیلدهای قابل نوشتن با DTO یا فهرست سفید، برای بستن مسیر Mass Assignment.
  5. یکسان‌سازی پاسخ‌ها: برای رکورد ناموجود و رکورد غیرمجاز یک پاسخ یکسان برگردانید تا اندپوینت به شمارشگر وجود رکورد تبدیل نشود.
  6. محافظت از فایل‌های ایستا: فایل‌های کاربر را از مسیر برنامه سرو کنید یا از آدرس‌های امضاشده‌ی کوتاه‌عمر استفاده کنید — نه از دایرکتوری عمومی وب‌سرور.
  7. تست خودکار منفی در CI: برای هر اندپوینتی که شناسه می‌گیرد، تستی که با نشست کاربر دیگر اجرا شود و انتظار 403 داشته باشد.
  8. تغییر شناسه‌ی ترتیبی به UUID فقط یک اقدام تکمیلی است و هرگز جایگزین موارد بالا نیست.

اگر می‌خواهید بدانید کدام اندپوینت‌های شما این بررسی را ندارند، تست نفوذ وب با چند حساب هم‌سطح دقیقاً همین را می‌سنجد؛ برای بررسی سطح کد هم بازبینی کد امن مکمل مؤثری است.

→ بازگشت به دانشنامه
PUT IT TO THE TEST

امنیت سامانه‌ی شما را
به مهاجمان واگذار نکنید

تیم پی‌هانتر با دیدِ یک مهاجم واقعی، این آسیب‌پذیری‌ها و ده‌ها مورد دیگر را روی دارایی‌های شما می‌سنجد.