API

Полный доступ к API, который считали внутренним

Почти у каждого продукта есть API только для своих. Показываем, почему слово внутренний ничего не закрывает.

Image

Внутренний API

Собрали самое ценное

С чего всё началось

Что насторожило

Как открылось чужое

Где была дыра и как закрыли

Обычная проверка

Нас позвали посмотреть на бэкенд продукта перед большим релизом. Архитектура типовая: публичное API для фронтенда, за ним сервисы, а между сервисами своё, служебное API. Вот это служебное команда и называла внутренним. Через него ходили сами сервисы: забирали полные карточки пользователей, дёргали служебные операции, читали то, что наружу никогда не отдаётся. Считалось, что доступ к нему есть только у своих, потому что фронтенд туда не обращается и в публичной документации его нет. На этом и держалась вся его защита. Авторизацию на нём толком не проверяли, рассуждая просто: раз снаружи про него не знают и фронт туда не ходит, то и чужой не дойдёт. Именно это допущение мы и пошли проверять, потому что «о нём никто не знает» это не контроль доступа, а надежда.

Что насторожило

Первым делом мы стали смотреть не на публичное API, а на то, как сервисы общаются между собой, и сразу зацепились за пару вещей. Внутреннее API отвечало на запросы, которые приходили не из кластера. То есть оно было доступно по сети куда шире, чем предполагала команда, и его «внутренность» держалась только на том, что адрес нигде не светился. А раз так, безопасность всего этого добра свелась к секретности URL. Но URL это не секрет. Он утекает в логи, в историю браузера, в код фронтенда, в трафик, в чужие заметки. Достаточно один раз увидеть, как сервис обращается к соседу, и «внутренний» адрес перестаёт быть внутренним. Второе, что бросилось в глаза: на этих служебных запросах либо вообще не проверялось, кто их шлёт, либо проверка была формальной, рассчитанной на то, что чужой сюда просто не доберётся. Стало понятно, что граница тут проведена по знанию адреса, а не по правам, и эту границу надо проверять всерьёз.

Как открылось чужое

Чтобы показать клиенту, что это не теория, мы воспроизвели сценарий, не публикуя конкретных адресов и запросов, чтобы их нельзя было повторить как готовый рецепт. Суть в том, что мы обратились к служебному API так же, как это делает свой сервис, но снаружи, со стороны обычного клиента. Сетевая позиция оказалась неважна: эндпоинт отвечал кому угодно, кто до него дотянулся. Дальше вскрылось то, ради чего такие API и атакуют. Через него отдавались полные объекты, а не урезанные, как наружу: служебные поля, внутренние идентификаторы, данные, которых в публичном ответе никогда нет. А поскольку записи адресовались по простым последовательным идентификаторам, перебор давал доступ не к одной записи, а к чужим по очереди. Атакующему не нужно было ломать саму систему. Достаточно было разговаривать с ней на её же служебном языке, который команда считала приватным. Общий знаменатель тот же, что и в любой такой истории: защита стояла на предположении «сюда не дойдут», а не на проверке «а имеешь ли ты право».

Где была дыра и как закрыли

Корень был не в конкретном эндпоинте, и закрыть один адрес проблему не решало. Дыра была в самой модели: безопасность строилась на сетевой позиции и секретности, а не на правах. Мы зафиксировали три настоящие причины и под каждую внедрили меру вместе с командой. Первая, доступность по сети считалась защитой. Внутреннее API было достижимо оттуда, откуда не должно. Закрыли его на сетевом уровне так, чтобы до него доходили только те сервисы, которым это положено, а не любой, кто узнал адрес. Секретность URL перестала быть частью защиты, потому что секретность это не контроль. Вторая, не проверялось, кто обращается. Запрос от своего сервиса и запрос снаружи выглядели для API одинаково. Ввели обязательную проверку личности и прав на каждый служебный вызов: сервис теперь доказывает, кто он, а не предъявляет один лишь факт, что знает, куда стучаться. Нет валидной авторизации — нет ответа, независимо от того, откуда пришёл запрос. Третья, эндпоинт отдавал больше, чем нужно, и пускал к чужим записям. Урезали ответы до необходимого минимума, убрали из выдачи служебные поля, а доступ к конкретной записи стали проверять по тому, имеет ли запрашивающий на неё право, а не по тому, угадал ли он идентификатор. После внедрения мы прогнали тот же сценарий заново. Снаружи API просто перестало отвечать. Изнутри отвечало, но только проверенному сервису и только тем, что ему положено видеть. Перебор по идентификаторам упёрся в проверку прав, а не в удачу.

Related Blogs

Путь к защите

Что увидит атакующий, если начнёт изучать вашу инфраструктуру сегодня?

Аудит покажет реальный путь к вашим данным

Shape

Путь к защите

Что увидит атакующий, если начнёт изучать вашу инфраструктуру сегодня?

Аудит покажет реальный путь к вашим данным

Shape

Путь к защите

Что увидит атакующий, если начнёт изучать вашу инфраструктуру сегодня?

Аудит покажет реальный путь к вашим данным

Shape