Безопасность API
Риски уровня API — OWASP API Top 10, избыточная выдача данных, rate limiting, интроспекция и глубина в GraphQL, версионирование и shadow-эндпоинты, подпись webhook и утечки через ошибки.
12 вопросов
JuniorТеорияОчень частоЧем различаются API-ключи, access-токены OAuth2 и взаимный TLS (mTLS) как учётные данные API?
Чем различаются API-ключи, access-токены OAuth2 и взаимный TLS (mTLS) как учётные данные API?
Ключ — долгоживущая общая строка, называющая вызывающего; access-токен OAuth2 короткоживущий и ограничен одним разрешением; mTLS подтверждает клиента сертификатом. Ключ, зашитый в клиентскую сборку, извлекаем и ничего не доказывает.
Типичные ошибки
- ✗Считать секретом ключ, поставляемый клиенту, а не публичным идентификатором
- ✗Передавать ключи в URL, где их сохраняют прокси, история браузера и логи
- ✗Считать mTLS лишь шифрованием, упуская, что он аутентифицирует клиента
Уточняющие вопросы
- →Как ограничить область и ротировать ключ партнёра, не сломав его интеграцию?
- →Какой серверный контроль защищает вас, когда публичная строка ключа утекла?
JuniorТеорияОчень частоЧто такое OWASP API Security Top 10 и почему на первом месте нарушение авторизации на уровне объектов?
Что такое OWASP API Security Top 10 и почему на первом месте нарушение авторизации на уровне объектов?
Это рейтинг рисков, характерных для API, где преобладают ошибки авторизации. BOLA первый, потому что API отдаёт идентификаторы объектов напрямую, и каждый маршрут обязан проверять владельца на сервере.
Типичные ошибки
- ✗Считать его переставленным веб-рейтингом, а не отдельным списком рисков API
- ✗Полагать, что неугадываемые идентификаторы заменяют серверную проверку владельца
- ✗Проверять авторизацию объектов только на записи, оставляя чтение открытым
Уточняющие вопросы
- →Чем это отличается от нарушения авторизации на уровне функций на админском маршруте?
- →Почему устаревшие версии API часто сохраняют риск после исправления текущей версии?
JuniorТеорияЧастоПочему подробные ошибки API раскрывают внутренности и какой шаблон ответа их заменяет?
Почему подробные ошибки API раскрывают внутренности и какой шаблон ответа их заменяет?
Обработчики по умолчанию отдают текст исключения, кадры стека, фрагменты SQL или версии библиотек, раскрывая хранилище, пути и зависимости. Отвечайте общим сообщением с идентификатором корреляции, а подробности пишите в лог.
Типичные ошибки
- ✗Считать стек бесполезным для вызывающего, раз это неформатированный текст
- ✗Убирать подробности из ответа, нигде не сохраняя их в логе для дежурных
- ✗Полагаться на боевые умолчания фреймворка вместо проверки формы ошибки
Уточняющие вопросы
- →Как идентификатор корреляции позволяет поддержке разбираться, не раскрывая внутренности?
- →Почему чужой и несуществующий объект должны давать одинаковый ответ?
MiddleДебаггингЧастоВ логе доступа чтения заказов v1 идут по чужим владельцам, а v2 защищён — найдите и исправьте дефект
В логе доступа чтения заказов v1 идут по чужим владельцам, а v2 защищён — найдите и исправьте дефект
Проверка владельца существует только как фильтр шлюза на v2, поэтому устаревший маршрут v1 доходит до того же сервиса и отдаёт любой заказ по id. Перенесите проверку в сам сервис заказов и выведите v1 из эксплуатации.
Открыть задачу →Типичные ошибки
- ✗Считать фильтр шлюза средством авторизации для всех маршрутов за ним
- ✗Забывать, что устаревшая версия по-прежнему доходит до текущего сервиса
- ✗Чинить формат идентификатора или код статуса вместо отсутствующей проверки
Уточняющие вопросы
- →Как найти все маршруты, ещё доступные, но отсутствующие в текущей спецификации?
- →Какой алерт по этому логу выявил бы схему в первый же день?
MiddleТеорияЧастоЧто такое избыточная выдача данных, когда API возвращает объекты целиком, и как это исправить?
Что такое избыточная выдача данных, когда API возвращает объекты целиком, и как это исправить?
Обработчик сериализует запись целиком и оставляет фильтрацию клиенту, поэтому хеши паролей, внутренние флаги или чужие контакты едут в JSON. Формируйте ответ на сервере по явной схеме вывода для роли.
Типичные ошибки
- ✗Считать нераскрытым поле, которое интерфейс не отображает
- ✗Передавать выбор полей клиенту вместо сервера
- ✗Путать размер ответа и пагинацию с фильтрацией по принципу минимума
Уточняющие вопросы
- →Как по умолчанию не пускать новую колонку базы в ответ?
- →Какой тест поймает изменение сериализатора, начавшее отдавать внутренний флаг?
MiddleТеорияЧастоКак лишнее поле в теле запроса API записывает привилегированное свойство и что этому мешает?
Как лишнее поле в теле запроса API записывает привилегированное свойство и что этому мешает?
Привязка тела запроса прямо к модели пропускает неожиданное свойство вроде role или isVerified в колонку, закрытую для вызывающего. Привязывайте только разрешённый список полей маршрута и отклоняйте прочие тела.
Типичные ошибки
- ✗Считать, что владение объектом авторизует любое его свойство
- ✗Использовать запретный список полей, мимо которого тихо проходят новые колонки
- ✗Полагаться на опубликованную спецификацию как на ограничение для вызывающих
Уточняющие вопросы
- →Почему отклонять неизвестные свойства безопаснее, чем молча их отбрасывать?
- →Как поддерживать разрешающий список, когда сущность каждый спринт получает колонки?
MiddleТеорияЧастоПочему ограничения только по IP недостаточно и что ещё ограничивает потребление ресурсов API?
Почему ограничения только по IP недостаточно и что ещё ограничивает потребление ресурсов API?
За одним адресом может стоять целый офис за NAT, а злоупотребляющий дёшево меняет адреса, поэтому лимит по IP бьёт по общим пользователям и не задевает атакующего. Ограничивайте по учётной записи или ключу и ставьте потолки.
Типичные ошибки
- ✗Считать IP клиента устойчивой личностью вопреки NAT и смене адресов
- ✗Считать только запросы, тогда как один запрос вытягивает миллион строк
- ✗Ставить единый общий потолок, который одна шумная учётка исчерпывает для всех
Уточняющие вопросы
- →Как ограничить неаутентифицированный маршрут, не блокируя общий офис?
- →Какой ответ и заголовки подсказывают добросовестному клиенту снизить темп?
MiddleТеорияЧастоКак проверять входящий webhook, чтобы отклонять поддельную или повторно присланную доставку?
Как проверять входящий webhook, чтобы отклонять поддельную или повторно присланную доставку?
Пересчитайте HMAC по точному сырому телу с общим секретом и сравните с заголовком подписи функцией постоянного времени. Подписывайте метку времени, отклоняйте всё вне короткого окна и запоминайте идентификаторы доставок.
Типичные ошибки
- ✗Хешировать пересобранное тело вместо точных подписанных байтов
- ✗Сравнивать подписи проверкой равенства с ранним выходом
- ✗Проверять подпись уже после того, как обработчик применил изменение
Уточняющие вопросы
- →Почему сырое тело нужно сохранять до запуска middleware разбора JSON?
- →Как ротировать секрет webhook, не теряя доставки в пути?
MiddleТеорияИногдаКак защитить боевой эндпоинт GraphQL от интроспекции, глубоких запросов и батчинга?
Как защитить боевой эндпоинт GraphQL от интроспекции, глубоких запросов и батчинга?
Отключите интроспекцию и playground вне разработки, отклоняйте запросы выше бюджета глубины и сложности до выполнения, ограничьте размер батча и аргументы страниц. Авторизация живёт в каждом резолвере, а не только в корне.
Типичные ошибки
- ✗Считать, что один эндпоинт означает одно решение об авторизации на весь запрос
- ✗Оставлять интроспекцию и playground включёнными в бою
- ✗Мерить стоимость после выполнения, когда дорогой запрос уже отработал
Уточняющие вопросы
- →Почему отключение интроспекции не прячет схему от настойчивого вызывающего?
- →Как persisted-запросы меняют то, что нужно ограничивать на эндпоинте?
MiddleДизайнИногдаВаша платформа предоставляет публичный пишущий API, где POST /v1/orders и PATCH /v1/users
привязывают входящий JSON прямо к ORM-сущностям, общим для трёх сервисов. Разбор инцидента
показал, что клиент выставил недокументированное свойство discountPercent у заказа, а агент
поддержки тем же способом поднял себе тариф аккаунта.
Ограничения:
- сущности почти каждый спринт получают новые колонки, документацию никто не обновляет
- действующие интеграторы должны продолжать работать, ломать их тела без предупреждения нельзя
- шлюза, способного разбирать тела запросов, нет
- два сервиса из трёх поддерживает другая команда
Спроектируйте контракт записи, делающий такой дефект невозможным. Опишите, как задаётся тело
запроса, что происходит с необъявленным свойством, как по умолчанию ведёт себя колонка,
добавленная в следующем спринте, и чем вы докажете, что правило работает для нового маршрута.
Ваша платформа предоставляет публичный пишущий API, где POST /v1/orders и PATCH /v1/users привязывают входящий JSON прямо к ORM-сущностям, общим для трёх сервисов. Разбор инцидента показал, что клиент выставил недокументированное свойство discountPercent у заказа, а агент поддержки тем же способом поднял себе тариф аккаунта. Ограничения: - сущности почти каждый спринт получают новые колонки, документацию никто не обновляет - действующие интеграторы должны продолжать работать, ломать их тела без предупреждения нельзя - шлюза, способного разбирать тела запросов, нет - два сервиса из трёх поддерживает другая команда Спроектируйте контракт записи, делающий такой дефект невозможным. Опишите, как задаётся тело запроса, что происходит с необъявленным свойством, как по умолчанию ведёт себя колонка, добавленная в следующем спринте, и чем вы докажете, что правило работает для нового маршрута.
Дайте каждому маршруту схему ввода, называющую только принимаемые поля, и стройте сущность из неё, а не из сырого тела, чтобы новая колонка оставалась незаписываемой. Отклоняйте необъявленные свойства и проверяйте правило в CI.
Типичные ошибки
- ✗Выбирать запретный список, мимо которого тихо проходит каждая новая колонка
- ✗Полагаться на документацию или ревью вместо принудительной схемы
- ✗Ставить единственную проверку в один сервис, который не используют два других
Уточняющие вопросы
- →Как выкатить отклонение неизвестных полей, не сломав действующих интеграторов?
- →Что логировать в период предупреждения, чтобы оценить масштаб влияния?
SeniorДизайнИногдаШлюз GraphQL стоит перед четырьмя внутренними сервисами и обслуживает и собственный веб-клиент,
и платящих партнёров. Один партнёрский запрос вложил клиентов в заказы, заказы в позиции, позиции
в товары и держал узел базы одиннадцать минут; другой вернул закупочные цены поставщиков, которые
партнёрам видеть нельзя, добравшись до них через заказ, принадлежащий этому партнёру.
Ограничения:
- партнёры шлют произвольные запросы, поэтому persisted-запросы в этом квартале не вариант
- схема федеративная, и каждый сервис владеет своими резолверами
- веб-клиент на нескольких экранах законно шлёт глубокие запросы
- данные авторизации лежат в claims токена и в таблице владения каждого сервиса
Спроектируйте защиту. Опишите, как ограничивается стоимость запроса до выполнения, где
принимается решение об авторизации вложенного поля, как двум клиентам выдаются разные бюджеты и
что вы будете отслеживать, чтобы увидеть злоупотребление, укладывающееся во все лимиты.
Шлюз GraphQL стоит перед четырьмя внутренними сервисами и обслуживает и собственный веб-клиент, и платящих партнёров. Один партнёрский запрос вложил клиентов в заказы, заказы в позиции, позиции в товары и держал узел базы одиннадцать минут; другой вернул закупочные цены поставщиков, которые партнёрам видеть нельзя, добравшись до них через заказ, принадлежащий этому партнёру. Ограничения: - партнёры шлют произвольные запросы, поэтому persisted-запросы в этом квартале не вариант - схема федеративная, и каждый сервис владеет своими резолверами - веб-клиент на нескольких экранах законно шлёт глубокие запросы - данные авторизации лежат в claims токена и в таблице владения каждого сервиса Спроектируйте защиту. Опишите, как ограничивается стоимость запроса до выполнения, где принимается решение об авторизации вложенного поля, как двум клиентам выдаются разные бюджеты и что вы будете отслеживать, чтобы увидеть злоупотребление, укладывающееся во все лимиты.
Оценивайте каждый запрос по глубине и весам полей до выполнения и отклоняйте сверх бюджета, который задаёт класс клиента. Каждый сервис авторизует свои резолверы, ведь разрешённый родитель не должен открывать запретного потомка.
Типичные ошибки
- ✗Авторизовать корневое поле и позволять вложенным резолверам наследовать решение
- ✗Ограничивать стоимость после выполнения таймаутом или размером, а не до неё
- ✗Давать всем клиентам один бюджет, из-за чего строгий партнёрский лимит душит свой клиент
Уточняющие вопросы
- →Как взвесить поля, чтобы оценка стоимости отражала реальную работу базы?
- →Какой признак отличает тяжёлого, но законного партнёра от медленного перебора?
SeniorДизайнИногдаМультиарендный SaaS-API обслуживает администраторов клиентов, их конечных пользователей и
внутреннюю консоль поддержки одними и теми же обработчиками. Ответ формируется сериализацией
доменного объекта, а веб-клиент прячет то, что роли видеть не положено. За одну неделю пришли две
находки аудита: конечный пользователь прочитал телефон коллеги из JSON-ответа, а один арендатор
увидел идентификатор аккаунта другого в теле ошибки общего отчётного эндпоинта.
Ограничения:
- те же эндпоинты должны продолжать обслуживать все три аудитории
- доменные объекты регулярно получают поля и разделяются с конвейером событий
- поддержке действительно нужна более широкая видимость, чем любому клиенту
- ни один ответ не должен раскрывать существование объекта другого арендатора
Спроектируйте контракт ответа. Опишите, что определяет набор полей для вызывающего, как поведёт
себя поле, добавленное через месяц, как обеспечивается граница арендатора и как вы поймаете
регрессию.
Мультиарендный SaaS-API обслуживает администраторов клиентов, их конечных пользователей и внутреннюю консоль поддержки одними и теми же обработчиками. Ответ формируется сериализацией доменного объекта, а веб-клиент прячет то, что роли видеть не положено. За одну неделю пришли две находки аудита: конечный пользователь прочитал телефон коллеги из JSON-ответа, а один арендатор увидел идентификатор аккаунта другого в теле ошибки общего отчётного эндпоинта. Ограничения: - те же эндпоинты должны продолжать обслуживать все три аудитории - доменные объекты регулярно получают поля и разделяются с конвейером событий - поддержке действительно нужна более широкая видимость, чем любому клиенту - ни один ответ не должен раскрывать существование объекта другого арендатора Спроектируйте контракт ответа. Опишите, что определяет набор полей для вызывающего, как поведёт себя поле, добавленное через месяц, как обеспечивается граница арендатора и как вы поймаете регрессию.
Сериализуйте через явное представление вывода по роли вызывающего, а не доменный объект, чтобы поле, добавленное позже, оставалось невидимым. Ограничивайте выборку арендатором в слое данных и держите ошибки общими.
Типичные ошибки
- ✗Пускать новое доменное поле в ответы по умолчанию вместо того, чтобы прятать его
- ✗Держать границу арендатора выше слоя данных, где один запрос её обойдёт
- ✗Отличать чужой объект от несуществующего в ответе об ошибке
Уточняющие вопросы
- →Как дать поддержке более широкую видимость, не расширяя клиентские представления?
- →Что должен утверждать эталонный тест ответа, чтобы новое поле роняло сборку?