Безопасность API: что проверять
Я построил матрицу доступа: два пользователя, два заказа, обычная проверка «человек залогинен и роль подходит». Из шести сочетаний два оказались дырой: Анна спокойно читает заказ Бориса, а Борис - заказ Анны. Роль у обоих правильная, токен настоящий, запрет не сработал ни разу.
Стержень: главная дыра API - доступ к чужому объекту своей же ролью, поэтому доступы проверяют матрицей, а не под одним пользователем.
// Формулировки: «что такое IDOR и как его тестировать?», «что такое лишние поля в запросе?», «как ловить утечки?».
Матрица доступа: где на самом деле дыра
Разберу ту матрицу. Наивная проверка спрашивает два вопроса: залогинен ли человек и подходит ли его роль. Оба ответа «да» - доступ открыт. Но не задан третий вопрос: а этот объект вообще его? Правильная проверка добавляет условие «владелец объекта совпадает с тем, кто спрашивает, либо это администратор» - и те же два сочетания сразу закрываются.
Такая дыра называется IDOR (insecure direct object reference) - небезопасная прямая ссылка на объект; в списке OWASP (Open Web Application Security Project) она же значится как BOLA (broken object level authorization), нарушенная проверка доступа на уровне объекта. Это самая частая уязвимость API, и живёт она не в строке «мне нельзя», а в ячейке «моя роль, чужой объект».
// Как проверять: завести двух пользователей, создать объект под первым, запомнить его идентификатор и запросить под вторым. Ожидаемый ответ - отказ, а не чужие данные. Прогонять надо все операции, включая изменение и удаление, и обязательно напрямую через API: в интерфейсе кнопки чужого объекта просто не показаны, и через него дыру не увидишь. И не надейтесь, что идентификаторы «никто не угадает» - они утекают в ссылках, письмах и логах.
- IDOR
- insecure direct object reference: чужой объект по подменённому идентификатору
- матрица доступа
- роль, ресурс, операция и обязательно «чужой объект своей роли»
Инъекции и лишние поля
Замер первый. В поле поиска ввожу anna' or '1'='1. Если сервер склеивает запрос к базе строками, кавычка закрывает условие и остаток моего ввода становится частью самой команды. Это называется инъекцией: чужой текст просочился внутрь запроса и выполнился как его часть. Мне вернулись обе строки таблицы, включая чужую запись «коды от сейфа». Тот же ввод в параметризованном запросе, где значение передаётся отдельно от текста команды, вернул ноль строк: он остался просто текстом.
Замер второй. Пользователь отправляет на обновление профиля {"name": "Анна Б", "is_admin": true}. Обработчик, который «просто применяет, что прислали», поднял человеку права администратора. С белым списком разрешённых полей - только имя и почта - права остались на месте. Это называется массовым присваиванием, и проверяется подстановкой привилегированных полей в каждый принимающий адрес.
// Третье - чужой скрипт в данных. Если API принял <script> и сохранил, а веб-клиент потом это нарисовал, скрипт выполнится у всех, кто откроет страницу. Проверять это в консольном клиенте бессмысленно: там ничего не рисуется. Смотреть надо в браузере, куда данные в итоге попадают.
- параметризованный запрос
- значение передаётся отдельно от текста команды и не может стать командой
- массовое присваивание
- сервер применяет лишние поля из запроса, включая привилегированные
Утечки: словами и секундомером
Есть очевидные утечки: стек ошибки в ответе (он рассказывает про версии и внутреннее устройство), внутренние адреса и идентификаторы в теле, токен (строка-пропуск, дающая доступ от вашего имени) прямо в адресе запроса - оттуда он попадает в логи сервера, в историю браузера и в статистику переходов.
А есть неочевидная - по времени. Замер: сервер на любой неверный вход отвечает одинаковым текстом «неверный логин или пароль». Но для существующего пользователя он тратит 52 миллисекунды на проверку пароля, а для несуществующего отвечает мгновенно, за доли миллисекунды. Разница в тысячи раз, и по ней логины перебираются с секундомером - текст ответа тут ничего не скрывает.
// Рядом живёт ограничение частоты запросов: у входа, отправки кодов и дорогих операций должен быть предел, после которого приходит отказ с кодом 429. Тестируют и сам предел, и обходы вокруг него: смена адреса, смена регистра логина, параллельные соединения. Общий чек-лист по всему этому - список OWASP для API.
- утечка по времени
- ответ одинаковый, а длительность выдаёт, есть ли такой пользователь
- ограничение частоты
- предел числа запросов, после которого приходит отказ 429
Как отвечать: «Что такое IDOR и как его тестировать?»
IDOR - это когда доступ к объекту проверяется только по идентификатору из запроса, без вопроса, а чей это объект. Роль тут не спасает. У меня легально есть роль обычного пользователя и доступ к чтению заказов - но если я подставлю чужой номер заказа и сервер отдаст его, потому что проверил лишь «залогинен и роль подходит», это дыра. Я строил такую матрицу: два пользователя, два заказа, шесть сочетаний - и два из них оказались открыты, каждый читал чужое. Поэтому тестирую я не «под своей ролью мне нельзя», а именно между объектами одной роли: завожу двух пользователей, создаю объект под первым, беру его идентификатор и запрашиваю под вторым. Ответ должен быть отказом, а не чужими данными. Прогоняю так все операции, включая изменение и удаление, и обязательно напрямую через API, а не через интерфейс - там кнопки чужого объекта просто не показаны, и дыру не увидишь. И не принимаю аргумент «идентификаторы никто не угадает»: они утекают в ссылках, письмах и логах, а последовательные номера просто перебираются.
Почему это сильный ответ: определение через механику проверки, наблюдение с матрицей, конкретный метод тестирования и отказ от защиты через непредсказуемость идентификаторов.
На чём валят
- −Проверять доступ только «мне нельзя»: в матрице открытыми оказались ячейки «своя роль, чужой объект».
- −Склеивать запрос к базе строками: ввод с кавычкой вернул всю таблицу вместо одной строки.
- −Применять присланные поля без белого списка - обычный пользователь стал администратором.
- −Считать одинаковый текст ответа защитой: разница во времени в тысячи раз выдаёт существующие логины.
- −Токен в адресе запроса - он уже в логах сервера, в истории браузера и в статистике переходов.
Проверьте себя
Пять вопросов из банка по этой подтеме. Всего их 12, остальные разбираются в тренажёре.
- Что такое XSS (межсайтовый скриптинг)?A)Подбор чужого пароля перебором комбинаций на форме входа приложенияB)Внедрение на страницу скрипта, который исполняется в браузере жертвыC)Перегрузка сервера потоком запросов, чтобы сайт стал недоступенD)Перехват незашифрованного трафика между клиентом и сервером в общей сети
показать ответ и разбор
+B)Внедрение на страницу скрипта, который исполняется в браузере жертвы// разбор: Атакующий вставляет скрипт (например, <script>...</script>) через поле или параметр, а приложение выводит этот ввод на страницу без экранирования. Скрипт исполняется в браузере другого пользователя от его имени: крадёт куки и сессию, шлёт запросы, подменяет контент. Причина — вывод пользовательских данных без нейтрализации. Тестировщик пробует безобидный payload вроде <script>alert(1)</script> и смотрит, экранируется ли вывод.
- Чем аутентификация отличается от авторизации?A)Аутентификация проверяет права на действие, а авторизация — подлинность личности пользователяB)Это синонимы: оба термина означают проверку логина и пароля при входеC)Аутентификация нужна только для API, а авторизация — только для веб-интерфейсаD)Аутентификация — кто ты (подтверждение личности), авторизация — что тебе можно
показать ответ и разбор
+D)Аутентификация — кто ты (подтверждение личности), авторизация — что тебе можно// разбор: Аутентификация (authentication) отвечает на вопрос «кто ты»: проверка логина/пароля, токена, 2FA. Авторизация (authorization) — «что тебе разрешено»: доступ к ресурсу или действию по роли и владению. Сначала первое, потом второе. Тестировщик проверяет обе стороны: вход под чужими данными (ломаем authn) и доступ к чужому или запретному ресурсу под валидной сессией (ломаем authz). Путаница этих слоёв — типовой источник дыр контроля доступа.
- Как быстрее всего проверить поле ввода на уязвимость к SQL-инъекции?A)Подставить одинарную кавычку и смотреть, не вернулась ли ошибка SQL или 500B)Ввести очень длинную строку и проверить, обрежет ли её приложение по лимиту поляC)Отправить пустое значение и убедиться, что сработала обязательность поляD)Ввести эмодзи и спецсимволы, проверив корректность кодировки в ответе
показать ответ и разбор
+A)Подставить одинарную кавычку и смотреть, не вернулась ли ошибка SQL или 500// разбор: Одинарная кавычка (') ломает синтаксис незащищённого запроса. Если в ответ прилетает ошибка СУБД, 500 или заметно меняется поведение выборки — поле, скорее всего, подставляет ввод в запрос напрямую. Дальше идут payload'ы вроде ' OR '1'='1 и проверка на tautology. Это негативный тест: цель не «работает ли поле», а «исполняется ли ввод как код». Защита — параметризованные запросы, тогда кавычка остаётся просто данными.
- Почему хранимая (stored) XSS обычно опаснее отражённой (reflected)?A)Stored XSS срабатывает даже при полностью выключенном в браузере JavaScriptB)Reflected XSS не получится эксплуатировать, не получив прямой доступ к серверу приложенияC)Stored-скрипт сохранён на сервере и бьёт каждого, кто открыл заражённую страницуD)Stored XSS сразу даёт атакующему полный доступ к базе данных сайта
показать ответ и разбор
+C)Stored-скрипт сохранён на сервере и бьёт каждого, кто открыл заражённую страницу// разбор: Reflected XSS живёт в параметре запроса: жертву надо заманить по специально собранной ссылке, скрипт нигде не сохраняется. Stored (persistent) XSS записывается на сервер — в комментарий, профиль, тикет — и исполняется у каждого, кто откроет заражённую страницу, без ссылок и социнженерии, потенциально массово. Поэтому severity у stored выше. Тестировщик проверяет все поля, чей ввод потом где-либо отображается другим пользователям.
- Пользователь меняет в URL /orders/1001 на /orders/1002 и видит чужой заказ. Что это за дефект?A)Ошибка кэширования: сервер отдал страницу из кэша другого пользователя вместо своейB)Нарушение контроля доступа (IDOR): сервер не проверил, чей это ресурсC)SQL-инъекция: подменённый id изменил условие выборки в запросе к базеD)Отражённая XSS: изменённый параметр URL исполнился в браузере пользователя
показать ответ и разбор
+B)Нарушение контроля доступа (IDOR): сервер не проверил, чей это ресурс// разбор: Это IDOR (Insecure Direct Object Reference) — частный случай Broken Access Control: приложение выдаёт объект по идентификатору, не проверяя, принадлежит ли он текущему пользователю. Смена id в URL, теле запроса или параметре открывает чужие данные. Аутентификация при этом в порядке (юзер залогинен) — сломана авторизация. Тестировщик под валидной сессией подставляет чужие и соседние id и проверяет, вернётся ли 403/404, а не чужой ресурс.
дальше
Теорию прочитали. Навык ставится повторением
В Сеньорчике эта подтема идёт в ежедневных сессиях: движок возвращает её, пока ответы не станут уверенными, и ведёт прогресс отдельно по каждой подтеме. Теория внутри тоже бесплатна, лимит только на количество вопросов в день.