BOLA больше не ловится одиночным сканером: нужен multi-identity replay
Read-only replay: owner и viewer видят разные границы объекта, а finding появляется только после validator proof.
Коротко: Главная проблема BOLA/IDOR в том, что один аккаунт не показывает границу доступа. Сканер видит URL и статус 200, а пентестер проверяет владение объектом, роль, контекст и доказательство утечки.

Проблема

API-команды всё чаще строят сервис как набор внутренних и публичных интерфейсов: мобильное приложение, SPA, партнёрские интеграции, webhooks, админские действия. Документация быстро отстаёт, а старые версии API продолжают жить ради совместимости.

В такой архитектуре самая дорогая ошибка - не SQL-инъекция, а неверная авторизация объекта. Пользователь авторизован, токен валиден, метод безопасный, но сервер не проверяет, принадлежит ли объект именно этому пользователю или роли.

Решение

Проверка должна начинаться с API inventory: OpenAPI, HAR, browser network log, GraphQL schema, sitemap и реальные redirect chains. Затем каждому endpoint присваиваются признаки: account, profile, billing, upload, invite, admin, object-id.

Для production безопасный первый слой - только read-only replay: GET/HEAD/OPTIONS, без тела запроса, без массового перебора и без мутаций. Две тестовые личности с разными ролями выполняют одинаковые чтения, а validator сравнивает статус, redirect, content-type, hash, размер и self-markers.

Что проверить руками

  • Есть ли минимум две owned test identities: обычный пользователь, менеджер, админ или viewer.
  • Все ли object identifiers из HAR/OpenAPI попали в карту: id, userId, accountId, profileId, orderId, UUID в path/query.
  • Отличаются ли ответы между owner и non-owner не только статусом, но и смысловым маркером объекта.
  • Не превращает ли CDN/WAF блок-страницу в ложный вывод о безопасности приложения.

Как это должно попадать в отчёт

  • Finding создаётся только если есть role-differential evidence: запрос владельца, запрос другой роли, сравнение ответа и критерий ретеста.
  • Если есть только подозрительный endpoint, но нет двух ролей или HAR, это coverage gap, а не уязвимость.
  • Для бизнеса вывод формулируется через данные и процесс: какие объекты могли быть раскрыты, кто владелец риска, какой SLA и как доказать исправление.

Безопасный пример: извлечь object endpoints из HAR без сохранения cookie

import json
import re
from pathlib import Path
from urllib.parse import urlparse, parse_qs

OBJECT_KEYS = {"id", "userId", "accountId", "profileId", "orderId"}
SECRET_HEADERS = {"authorization", "cookie", "set-cookie", "x-api-key"}

def redacted_headers(headers):
    result = {}
    for item in headers or []:
        name = item.get("name", "").lower()
        result[name] = "<redacted>" if name in SECRET_HEADERS else item.get("value", "")
    return result

def extract_inventory(har_path):
    har = json.loads(Path(har_path).read_text(encoding="utf-8"))
    operations = []
    for entry in har.get("log", {}).get("entries", []):
        req = entry.get("request", {})
        method = req.get("method", "GET").upper()
        if method not in {"GET", "HEAD", "OPTIONS"}:
            continue
        parsed = urlparse(req.get("url", ""))
        params = set(parse_qs(parsed.query))
        path_ids = set(re.findall(r"/([0-9a-fA-F-]{8,}|[0-9]{2,})", parsed.path))
        if params & OBJECT_KEYS or path_ids:
            operations.append({
                "method": method,
                "host": parsed.netloc,
                "path": parsed.path,
                "object_params": sorted(params & OBJECT_KEYS),
                "headers": redacted_headers(req.get("headers")),
            })
    return operations

print(json.dumps(extract_inventory("session.har"), indent=2, ensure_ascii=False))
Этическая рамка: материал предназначен для defensive security, легального аудита собственных систем, подготовки remediation и обучения в контролируемой среде.