![]() |
https://forum.antichat.xyz/attachmen...2908018805.png
API сегодня - это фактически основа большинства приложений. Мобильные клиенты, веб-интерфейсы, микросервисы внутри инфраструктуры - всё общается через HTTP-endpoint’ы. Поэтому уязвимости на уровне API обычно имеют прямой доступ к данным. Не через обходы интерфейса, а напрямую через запросы. И вот тут появляется одна из самых неприятных проблем API-безопасности - BOLA (Broken Object Level Authorization). Если коротко: система проверяет, что пользователь вошёл в систему, но не проверяет, имеет ли он право работать именно с этим объектом. С точки зрения сервера всё выглядит корректно. Есть валидный токен, пользователь авторизован, endpoint доступен. Запрос проходит. Но сама логика доступа к объектам оказывается слишком простой. Клиент передаёт идентификатор ресурса - сервер его принимает и возвращает данные, не проверяя принадлежность. В API-архитектуре это встречается постоянно. Endpoint получает ID ресурса, делает запрос в базу и отдаёт результат. Проверка владельца объекта либо отсутствует, либо реализована не везде. Иногда она есть в одном сервисе, но отсутствует в другом. В микросервисной среде такие расхождения появляются довольно легко. Поэтому BOLA занимает первое место в OWASP API Security Top 10. Не из-за сложности эксплуатации. Скорее наоборот - потому что её эксплуатация часто оказывается слишком простой. В тестировании API такие проблемы всплывают регулярно. Достаточно изменить идентификатор объекта в запросе и посмотреть, что вернёт сервер. Типичный пример API-запроса выглядит так: HTTP: Код:
GET /api/v1/orders/1254HTTP: Код:
GET /api/v1/orders/1255На практике последствия могут быть серьёзнее, чем кажется. Через BOLA часто происходят утечки персональных данных, доступ к чужим заказам, изменение чужих ресурсов или даже полный захват аккаунтов. Особенно если endpoint поддерживает методы PUT, PATCH или DELETE. Путаницу иногда вызывает терминология. В веб-безопасности давно существует понятие IDOR (Insecure Direct Object Reference) - небезопасная прямая ссылка на объект. BOLA фактически описывает ту же проблему, но применяется именно в контексте API. В API запросы обычно передают ID ресурса напрямую в URL или JSON-структуре, поэтому ошибка авторизации становится особенно заметной. Цитата:
BOLA редко появляется из-за одной конкретной ошибки. Чаще это результат упрощённой логики доступа к данным. Backend получает запрос, извлекает идентификатор объекта из URL или JSON и сразу делает запрос в базу. Если объект существует - возвращает результат. Проверка владельца в этот момент просто не выполняется. Типичная ситуация выглядит примерно так. Endpoint получает Код:
user_idКод:
order_idSQL: Код:
SELECTSQL: Код:
SELECTГоризонтальный и вертикальный доступ В практике тестирования API обычно выделяют два варианта BOLA. ТипЧто происходитHorizontalпользовате ль получает доступ к объектам других пользователейVerticalпользова тель получает доступ к объектам более привилегированной роли Горизонтальный сценарий встречается чаще. Например, обычный пользователь может читать чужие заказы или профили. Вертикальный вариант возникает, когда endpoint доступен нескольким ролям, но сервер не проверяет уровень привилегий. Тогда обычный пользователь может взаимодействовать с административными объектами. На уровне API это может выглядеть совершенно незаметно. Endpoint Код:
/api/users/42Где именно ломается проверка Если посмотреть на реальные backend-проекты, уязвимость обычно появляется в одном из трёх мест. Первое - отсутствие проверки владельца объекта. Endpoint извлекает ресурс напрямую по ID. Второе - разделение логики между сервисами. Один сервис проверяет права доступа, другой считает, что проверка уже выполнена. Третье - доверие к данным клиента. API принимает Код:
user_idТипичный пример выглядит так: HTTP: Код:
GET /api/v1/profile?user_id=512Код:
user_idBOLA в микросервисной архитектуре С появлением микросервисов ситуация стала заметно сложнее. В монолитном приложении авторизация обычно централизована. Один модуль проверяет доступ и управляет логикой прав. В микросервисной архитектуре каждый сервис часто реализует собственную проверку. API-шлюз проверяет токен, сервис пользователей проверяет профиль, сервис заказов проверяет доступ к заказам. Между ними появляется слой сетевых вызовов, и именно там иногда теряется проверка владельца объекта. Например: Код: Код:
API Gateway → Orders Service → DatabaseКод:
order_idhttps://forum.antichat.xyz/attachmen...2911659838.png Эксплуатация BOLA На практике BOLA ищется довольно приземлённо. Без магии. Без сложных payload’ов. Сначала берётся легитимный запрос от обычного пользователя, потом в нём меняется идентификатор объекта. Дальше смотрим, что ответит API. Если сервер возвращает чужие данные, даёт изменить чужой объект или удалить его - точка найдена. Обычно всё начинается с Burp Suite Repeater. Это самый удобный режим для ручной проверки API-запросов: берём запрос, отправляем его в Repeater, меняем ID и сравниваем ответы. В BOLA как раз важны не только коды ответа, но и сама разница в данных. Иногда сервер возвращает Код:
200 OKКод:
403Код:
200Самый базовый сценарий выглядит так: HTTP: Код:
GET /api/v1/orders/1254HTTP: Код:
GET /api/v1/orders/1255Следующий шаг - методы изменения состояния. Очень часто чтение защищено криво, но запись защищена ещё хуже. Или наоборот. Поэтому после Код:
GETКод:
PUTКод:
PATCHКод:
DELETEНапример: HTTP: Код:
PATCH /api/v1/users/842Отдельный момент - где именно лежит идентификатор. Не всегда это сегмент URL. В API объектный ID может быть в query-параметре, в JSON-теле, в заголовке, в nested-структуре. Иногда разработчики закрывают очевидный endpoint Код:
/users/{id}Код:
/profile/updateКод:
userIdПример такого запроса: HTTP: Код:
POST /api/v1/profile/updateКод:
userIdПеребор идентификаторов После ручной проверки довольно быстро встаёт вопрос: объекты доступны точечно или проблема массовая. И вот тут начинается перебор ID. Самый простой случай - последовательные идентификаторы. Если объекты идут как Код:
1001Код:
1002Код:
1003Код:
200Код:
403Код:
404Если идентификаторы выглядят как UUID, расслабляться рано. Да, UUID - универсальный уникальный идентификатор - сложнее перебрать лобово. Но уязвимость это не убирает. Просто меняется способ поиска объекта. UUID часто утекают через списковые endpoint’ы, логи фронтенда, websocket-события, mobile API, экспорт данных, вложенные ссылки. То есть проблема уже не в предсказуемости значения, а в том, что после получения чужого UUID система всё равно не проверяет право доступа к объекту. Иногда enumeration выглядит совсем тихо. Например, через список заказов API возвращает набор Код:
order_idКод:
order_idBurp Autorize и автоматизация проверки Для BOLA полезен Autorize - расширение Burp Suite, которое позволяет автоматически повторять запросы с другим токеном и сравнивать ответы. Смысл очень прикладной: мы берём легитимный запрос одного пользователя, а инструмент параллельно отправляет его от имени другого пользователя или с пониженными правами. Если ответы совпадают или отличаются недостаточно сильно, это явный сигнал проверить доступ руками. Такой подход особенно хорошо работает на больших API, где endpoint’ов много и ручная проверка начинает тонуть. Но полностью полагаться на Autorize не стоит. Он помогает быстро находить подозрительные места, а не доказывает BOLA сам по себе. Ответы надо смотреть внимательно: иногда код ответа одинаковый, но объект в теле чужой; иногда различается только одно поле; иногда API возвращает success, но фактически меняет не тот ресурс, который ожидается. Если хочется чуть больше автоматизации, в Intruder удобно делать не только перебор ID, но и замену токенов, ролей, nested-параметров. Особенно на endpoint’ах, где ID лежит в JSON-массиве или составном объекте. В таких местах BOLA часто сидит глубже обычного URL-паттерна. На что смотреть в ответах API В BOLA код ответа - полезный индикатор, но не основной. Код:
200 OKКод:
404 Not FoundКод:
403 ForbiddenКод:
403Код:
200Хорошая практика при эксплуатации - сравнивать не только статус, но и: Что сравниватьЗачемтело ответаесть ли чужие данныеразмер ответаскрытые различия между объектамивремя ответакосвенное подтверждение существования объектапобочные эффектыизменился ли ресурс после запроса Иногда API не возвращает данные прямо, но даёт понять, что объект существует. Например, по разному времени ответа, по ошибке валидации, по наличию связанных сущностей. Это уже не всегда полноценная эксплуатация, но хороший зацеп для дальнейшей проверки. https://forum.antichat.xyz/attachmen...2911684930.png Продвинутая эксплуатация: где BOLA прячется глубже обычного Если BOLA не находится на простых маршрутах вида Код:
/users/123И вот там уже становится интереснее. Вложенные объекты Маршруты вида Код:
/users/123/orders/456Код:
order_id = 456Код:
user_id = 123Пример запроса: HTTP: Код:
GET /api/v1/users/123/orders/456Код:
123Код:
124Ещё неприятнее выглядят маршруты вроде: HTTP: Код:
GET /api/v1/companies/77/users/123/invoices/456Пакетные endpoint’ы Пакетные операции - отдельная боль. API получает массив объектов или массив ID и обрабатывает их одной операцией. Такие endpoint’ы удобны для фронтенда и мобильных клиентов, но с точки зрения авторизации это постоянная зона риска. Например: HTTP: Код:
POST /api/v1/orders/batchС пакетным обновлением проблема ещё жёстче: HTTP: Код:
PATCH /api/v1/users/batchBOLA в GraphQL https://forum.antichat.xyz/attachmen...2911726362.png С GraphQL история ещё тоньше. GraphQL - это язык запросов к API, где клиент сам описывает, какие поля и какие объекты хочет получить. С точки зрения разработки это удобно. С точки зрения авторизации - местами очень скользко. Проблема обычно сидит в резолвер - функции, которая обрабатывает конкретное поле или объект GraphQL-запроса. Один резолвер может корректно проверять доступ к объекту, а соседний - просто доставать данные по ID. Например, запрос может выглядеть так: Код: Код:
query {Код:
order(id)Код:
customerВ GraphQL BOLA часто не бросается в глаза, потому что маршрут один - обычно Код:
/graphqlЕсть ещё одна неприятная деталь. В GraphQL часто используются introspection и schema-aware клиенты. Introspection - это возможность получить описание схемы GraphQL API. Если она доступна, пентестер быстрее видит названия типов, полей и аргументов, где потенциально можно искать object-level access bugs. А дальше уже начинается системный перебор: какие сущности принимают ID, какие возвращают nested-объекты, где нет нормальной проверки контекста. Где искать в первую очередь Если отбросить шум, самые жирные BOLA чаще всплывают в трёх местах: МестоЧто обычно ломаетсяnested-маршрутысвязь дочернего объекта с владельцемbatch-endpoint’ыпроверка прав не на каждый элементGraphQL-резолверынепоследовательн ая авторизация на уровне полей и объектов И вот здесь уже видно главное. BOLA - это редко один дефект в одном endpoint’е. Чаще это системная проблема авторизационной модели. Где-то права проверяются на маршруте, где-то в сервисе, где-то в ORM-запросе, где-то вообще нигде. Пока всё идёт по ожидаемому сценарию, приложение живёт спокойно. Стоит начать менять объектный контекст - и появляются дыры. Цитата:
С BOLA почти всегда одна и та же ошибка восприятия. Пока уязвимость выглядит как доступ к одному чужому объекту, её легко недооценить. Особенно если речь идёт не о явной админке, а о каком-нибудь профиле, заказе, тикете, файле или настройке. На практике impact определяется не самим фактом чтения чужого объекта, а тем, что ещё разрешено сделать с этим объектом дальше. Если endpoint только читает данные, это уже нарушение разграничения доступа. Но реальный урон обычно начинается там, где объект можно менять, связывать с другими сущностями или использовать как точку опоры для следующего запроса. BOLA хорошо цепляется за бизнес-логику. Именно поэтому такие баги редко заканчиваются на одном скриншоте из Repeater. Сценарий первый: захват аккаунта через управление профильными объектами Самый неприятный вариант - когда object-level доступ затрагивает сущности, связанные с идентичностью пользователя. Email, телефон, recovery-контакты, MFA-настройки, список доверенных устройств, параметры SSO, резервные коды. Если хотя бы часть этих объектов доступна через BOLA, дальше уже собирается цепочка до account takeover. Например, API даёт менять email чужого пользователя: HTTP: Код:
PATCH /api/v1/users/842Фактически - это контроль над каналом восстановления доступа. Дальше типовая цепочка выглядит так: ШагЧто делает атакующийРезультат1меняет email у чужого объектаподменяет recovery channel2инициирует reset passwordссылка уходит атакующему3задаёт новый парольполучает полный доступ к аккаунту И это ещё мягкий сценарий. Если API отдельно управляет MFA или trusted devices, можно обойтись даже без сброса пароля. Иногда хватает изменить номер телефона для OTP, иногда - отключить фактор через endpoint настроек безопасности, если тот тоже не проверяет владельца объекта. Здесь BOLA уже нельзя оценивать как medium только потому, что она начинается с подмены ID. По факту это прямой путь к захвату учётной записи. Сценарий второй: массовая эксфильтрация через бизнес-объекты Вторая частая траектория - не takeover, а сбор данных. Причём не одного объекта, а целого класса сущностей: заказы, счета, обращения, документы, вложения, медицинские записи, экспортные отчёты, payroll-данные, CRM-карточки. Если ID перечисляются или доступны через список, BOLA быстро превращается из локального бага в механизм массовой выгрузки. Обычно это выглядит так: HTTP: Код:
GET /api/v1/invoices/900144Код:
customer_idКод:
file_idКод:
attachment_urlКод:
tenant_idКод:
export_job_idОтдельно неприятны endpoint’ы, которые возвращают ссылки на скачивание файлов. Например, invoice сам по себе может выглядеть безобидно, но внутри ответа лежит pre-signed URL. Pre-signed URL - это временная ссылка на прямой доступ к файлу в объектном хранилище. Если такая ссылка выдаётся без корректной проверки владельца, BOLA перестаёт быть только про API и уходит уже в прямую утечку документов. С технической стороны это особенно опасно в двух случаях. Первый - если API поддерживает batch-операции и отдаёт несколько объектов за один запрос. Второй - если через один разрешённый endpoint можно получать ID для следующего. Тогда эксфильтрация становится связанной и почти конвейерной. Сценарий третий: повышение привилегий через административные объекты Самый тяжёлый вариант - когда через BOLA можно не просто читать или менять чужие данные, а управлять объектами, связанными с более привилегированной ролью. Это уже не горизонтальный доступ, а вертикальный переход. Обычно под удар попадают объекты вроде:
HTTP: Код:
PUT /api/v1/teams/77/members/842В multi-tenant системах - системах, где одна платформа обслуживает несколько изолированных клиентов - такие ошибки особенно опасны. Один неверно защищённый tenant-bound object иногда позволяет выйти за границы своего tenant’а и начать работать с настройками другой организации. Это уже не просто privilege escalation, а фактически cross-tenant compromise. Почему impact часто оценивают неправильно В triage BOLA часто недооценивают из-за слишком узкого взгляда на endpoint. Смотрят только на текущий ответ и думают: ну ок, виден чужой объект. Но для нормальной оценки нужно смотреть шире: Что анализироватьЧто это даётможно ли менять объектпереход от disclosure к takeoverесть ли связанные сущностирасширение поверхности атакииспользуются ли объектом security-sensitive полявлияние на аутентификацию и ролиможно ли пройтись массово по IDмасштаб эксфильтрациинаходится ли объект в чужом tenant’ериск cross-tenant impact То есть вопрос всегда один и тот же: что дальше можно сделать с этим объектом. Не что он возвращает сейчас, а какие процессы в приложении от него зависят. В зрелых приложениях именно это и определяет реальный severity. BOLA редко выглядит эффектно в моменте. Но если объект встроен в цепочку аутентификации, биллинга, ролей, файлового доступа или tenant-изоляции, один неверный ownership check превращается в полноценный компромисс. И вот на этом месте разговор уже идёт не о баге в одном endpoint’е, а о том, что модель авторизации в приложении работает непоследовательно. Защита: как закрывать BOLA системно, а не латать по одному endpoint’у С BOLA плохо работает подход в духе сейчас добавим ещё одну проверку вот сюда. Иногда это спасает конкретный маршрут, но не решает саму проблему. Если модель авторизации размазана по контроллерам, сервисам и кускам бизнес-логики, баги будут всплывать снова. В одном месте ownership check - проверка, что объект принадлежит текущему пользователю - есть. В соседнем endpoint’е его уже забыли. В третьем он есть, но проверяет не тот объект. И поехало. Надёжная защита начинается с довольно неприятного, но полезного вывода: ID из запроса нельзя считать достаточным контекстом для доступа. Сервер не должен отвечать на вопрос существует ли объект с таким ID. Сервер должен отвечать на другой вопрос: имеет ли текущий субъект право выполнить это действие над этим объектом. Не прочитать объект вообще, а прочитать этот объект. Не обновить профиль как функцию, а обновить свой профиль. В BOLA всё крутится вокруг этой разницы. Проверка владельца объекта в middleware и сервисном слое Самая базовая защита - проверять связь между объектом и текущим пользователем до выполнения действия. Не после, не в отдельном валидаторе где-нибудь внизу, а в явной и переиспользуемой точке. Часто это делают в middleware или в сервисном слое. Плохой вариант выглядит примерно так: JavaScript: Код:
constНормальный вариант уже связывает объект с субъектом из токена: JavaScript: Код:
constКод:
404Но и тут есть нюанс. Такой подход хорош для простых кейсов владелец → объект. Как только в системе появляются команды, роли, tenant’ы, shared-ресурсы и делегированный доступ, ручные проверки начинают плодиться и расходиться по коду. Сначала всё ок. Потом один разработчик проверяет Код:
userIdКод:
accountIdАвторизация на основе политик Когда логика доступа становится сложнее, лучше уходить от ручных if-ов к policy-based authorization - авторизации на основе политик. Policy-based authorization - это подход, где правило доступа описывается отдельно от бизнес-кода и принимает решение на основе субъекта, действия и объекта. То есть вопрос формулируется явно:
Код:
user:123 can update order:456 if order.owner_id == user.idЭто можно реализовать через встроенные механизмы фреймворка или вынести в отдельный policy engine - движок принятия решений по правилам доступа. Из инструментов здесь часто всплывают OPA (Open Policy Agent) и Casbin. OPA - это policy engine, в котором правила описываются отдельно и применяются к запросам приложения. Casbin - библиотека для авторизации, поддерживающая role-based и policy-based модели доступа. Их плюс не в модности, а в том, что они заставляют описывать доступ централизованно. Не каждый endpoint по-своему, а через общую модель. Для BOLA это особенно полезно, потому что уязвимость чаще всего возникает не из-за отсутствия авторизации как таковой, а из-за непоследовательной авторизации. Если упростить, хороший policy-check звучит примерно так: текущий пользователь может читать заказ только если он владелец заказа или имеет роль support_agent внутри того же tenant’а. И это правило должно жить не в одном контроллере, а в одной точке правды. Защита на уровне базы: row-level security Когда система становится большой, часть контроля можно и иногда нужно уводить на уровень базы данных. Здесь появляется row-level security - построчная защита данных. Смысл в том, что сама база ограничивает, какие строки доступны текущему контексту. Даже если приложение ошиблось и отправило слишком широкий запрос, база не вернёт чужие записи. Для BOLA это очень сильный слой защиты, особенно в multi-tenant архитектуре. Если у приложения есть tenant context или user context, можно настроить политику так, чтобы запросы по умолчанию видели только разрешённые строки. Примерно в такой логике: SQL: Код:
CREATEКод:
SELECT * FROM orders WHERE id = 1255Код:
user_idКонечно, RLS - row-level security - не заменяет нормальную авторизацию на уровне приложения. Она не знает бизнес-контекста целиком, не всегда удобно ложится на сложные shared-ресурсы и требует аккуратной настройки connection context. Но как страховка от целого класса BOLA-багов это очень мощный слой. Особенно там, где доступ к объектам строится по tenant_id, owner_id или organisation_id. Что реально работает на практике Если собрать защиту в цельную модель, она обычно выглядит так: УровеньЧто должен делатьendpoint / middlewareизвлекать субъект из токена, а не из client inputservice / policy layerпринимать решение о доступе к объектуdatabase layerограничивать видимость строк через RLS там, где это уместноarchitectureне размазывать авторизацию по нескольким сервисам без единой модели И вот здесь самая важная мысль. BOLA нельзя стабильно закрыть только код-ревью или только тестированием. Если в архитектуре нет единого места, где принимается решение о доступе к объекту, ошибки будут повторяться. Один endpoint забудут. Один batch-обработчик напишут мимо общей логики. Один GraphQL резолвер вытащит объект напрямую. И всё начнётся заново. Поэтому хорошая защита от BOLA - это не набор локальных проверок. Это дисциплина: субъект берётся только из доверенного контекста, доступ к объекту проверяется централизованно, а база не отдаёт больше, чем должна, даже если приложение ошиблось. Цитата:
BOLA редко выглядит в коде как явная дыра. Обычно это вполне нормальный endpoint, который проходит ревью, отдаёт корректный JSON и не вызывает вопросов, пока кто-то не начинает двигать идентификаторы вручную. В этом и подлость проблемы: баг часто живёт не в отсутствии защиты, а в том, что защита стоит рядом, но не там, где реально принимается решение по объекту. В типичном backend-проекте всё снаружи выглядит прилично. Пользователь аутентифицирован, маршрут закрыт, роль проверена, middleware отработал. После этого код спокойно берёт Код:
idПоэтому хороший код против BOLA строится не вокруг общей идеи пользователь авторизован. Он строится вокруг более жёсткой логики: объект должен попадать в обработку только в том случае, если текущий субъект действительно имеет право с ним работать. Не после дополнительной проверки где-то ниже. Сразу. И тут уже многое зависит от стека. Где-то объектную авторизацию удобно встраивать в выборку данных, где-то - в policy-слой, где-то - в queryset или middleware. Ошибка обычно одна и та же, а способы не словить её - разные. Поэтому дальше не теория, а более приземлённые шаблоны для популярных фреймворков. Spring Security В Spring Security ошибка чаще всего появляется не в самой аутентификации, а в том, что авторизация проверяется слишком рано. Например, endpoint доступен роли Код:
USERКод:
orderIdПлохой вариант обычно выглядит так: Java: Код:
@GetMappingНормальная версия уже привязывает выборку к субъекту из security context - контекста безопасности, где Spring хранит данные текущего пользователя: Java: Код:
@GetMappingКод:
ownerUsername = currentUserПример через Код:
@PreAuthorizeJava: Код:
@PreAuthorizeExpress.js В Express.js BOLA часто появляется из-за простоты самого фреймворка. Он не навязывает строгую модель авторизации. Это удобно, пока проект маленький. Потом логика доступа расползается по middleware, роутам и сервисам, и где-то одна проверка просто не доезжает. Плохой пример обычно выглядит так: JavaScript: Код:
appБолее взрослый подход - не только валидировать токен, но и фильтровать объект сразу по trusted context - доверенному контексту, полученному после проверки токена: JavaScript: Код:
appПоэтому в Express.js полезно выносить проверку в отдельный middleware или policy-layer. Но не общий middleware уровня пользователь авторизован, а конкретный объектный: JavaScript: Код:
asyncDjango С Django есть отдельный подвох. Многие разработчики рассчитывают, что встроенные permissions уже решают задачу. Но базовые Django permissions в основном отвечают на вопрос может ли пользователь вообще делать Код:
changeКод:
viewКод:
deleteТо есть permission Код:
view_orderКод:
OrderПлохой пример: Python: Код:
defКод:
login_requiredНормальный подход - сужать queryset до разрешённых объектов: Python: Код:
fromКод:
permission_classesКод:
get_queryset()Python: Код:
fromНо и тут есть ловушка. Как только появляются staff-роли, support-доступ, tenant-границы или nested-ресурсы, простого Код:
filter(user=request.user)Код:
if request.user.is_staffГде всё равно ошибаются Даже при нормальной базе ошибок хватает. Обычно они повторяются в одних и тех же местах: ОшибкаЧто в итоге ломаетсяобъект выбирается по ID до проверки доступачужая запись уже попала в кодпроверка доступа живёт только в контроллереbatch, nested и internal endpoints остаются без неёберётся Код:
user_idИ вот тут видно главное различие между просто защищённым endpoint’ом и реально устойчивой моделью. В хорошем коде доступ к объекту не является отдельной дополнительной проверкой. Он встроен в сам способ выборки, обновления и удаления. То есть приложение не сначала находит объект, а потом думает, можно ли с ним работать. Оно изначально строит запрос так, чтобы чужой объект просто не попал в область видимости. Где на самом деле ломается доступ BOLA неприятна не потому, что её сложно найти. Наоборот. Она часто находится слишком просто. Один изменённый ID, один лишний объект в ответе, одна забытая проверка в PATCH или batch-endpoint’е - и вся аккуратная аутентификация перестаёт что-то значить. В API этого достаточно, чтобы из нормального бизнес-флоу внезапно вылез доступ к чужим данным, чужим действиям и чужим правам. Особенно в системах, где авторизация живёт кусками: чуть-чуть в gateway, чуть-чуть в сервисе, чуть-чуть в ORM, а местами вообще на доверии к клиенту. Хорошая защита от BOLA начинается не с запрета менять ID и не с косметических проверок по пути. Она начинается с более жёсткой дисциплины: объект не должен существовать для запроса сам по себе, пока система не доказала право текущего субъекта работать именно с ним. Если эта логика встроена в выборку, policy-слой и модель доступа целиком, BOLA становится заметно сложнее пропихнуть в прод. Если нет - она всё равно вернётся, просто в другом endpoint’е, в nested-ресурсе, в GraphQL резолвере или в новом микросервисе, который писали уже после первого инцидента. И вот здесь остаётся вопрос, который для любой команды разработки и API security звучит довольно жёстко: ваша система действительно проверяет доступ к объекту - или просто надеется, что клиент не полезет туда, куда его не звали? |
| Время: 02:07 |