Содержание статьи
А в конце подумаем, как не задушить разработку и проверки авторизации в сервисах легким выбором по умолчанию с auth-директивами в GraphQL.
За помощь в подготовке материала благодарю Григория Прохорова и Андрея Кана.
Это во многом продолжение темы прошлогодней статьи, где было уделено много внимания ручному поиску проблем с IDOR через анализ логов и быстрые проверки в Burp, поиск проблемных функций в коде с помощью базовых правил Semgrep.
Также статья стала призером летнего Pentest Award 2025 и легла в основу одноименного доклада на прошедшем недавно ZeroNights 2025.
Проблема
Разработчик в реализации API-хендла забыл проверить принадлежность объекта текущему авторизованному пользователю при обращении к этому объекту по ID.
С идентификаторами типа int все совсем плохо. Просто добавляешь по единице к параметру запроса, и получишь чужой объект, а может, и сможешь его поменять. Но не многим лучше дела с идентификаторами UUID. Вроде бы в лоб их не подберешь, но есть кейсы, когда мы можем подобрать и их:
- бизнес‑сценарии (сокращатели URL, сложный ID в обмен на ИНН или телефон);
- внутренние логи сервисов и админки, отдающие ID объектов;
- страницы, попавшие в Internet Archive.

Поэтому одной случайной проверки недостаточно — нам нужна масштабируемая защита.
Но есть и вторая большая проблема, в которой живут, кажется, все аппсеки мира.
У нас тысячи хендлов, и десятки новых добавляются каждый месяц. Мы не можем прогнать все новые бизнес‑сценарии и вручную проверять в Burp Repeater каждый подозрительный запрос от другого аутентифицированного пользователя.

Или все‑таки можем?
Ищем в коде с Semgrep в join mode
Чтобы лучше зашла логика описанных далее правил, начнем с ландшафта, в котором мы решаем нашу задачу.
Запросы с клиентского браузера летят в серверный API (ReactJS, Svelte, что‑то на фронтендерском), там клиентский инпут перекладывается в запросы GraphQL и летит вместе с авторизационными куками в сервис GraphQL на Go, где выполняется проверка авторизации пользователя, после чего сервис запрашивает данные у внутренних сервисов по gRPC и возвращает клиенту типизированный ответ.

Как это выглядит в коде? Где‑то в папочке API лежат схемы GraphQL, именованные запросы и мутации («ручки» на языке GraphQL).

А вот вариант с типизированным вводом и выводом.

А вот резолвер на Go, реализующий бизнес‑логику каждой функции, проверку авторизации клиента (на доступ к объекту), поход во внутренние сервисы за данными или изменениями и возврат ответа.

И хорошо бы нам тут взять клиента из сессии и проверить на соответствие объекту сразу.

Или передать дальше вглубь, чтобы проверили там.

Мы хотим найти все места в нашем коде, где про такие конструкции забывают. Для этого насобираем semgrep., похожий на наш изначальный резолвер, только без вызова авторизации (через pattern-not на вызов сессии).
Нормальную конструкцию правило не трогает.

Но конструкцию без вызова сессии пользователя правило цепляет для нас.

Обрати внимание, что здесь имя резолвера и имя структуры ввода ложатся в так называемые метапеременные $FUNCNAME и $INPUT, доступные нам дальше.
Затем нам нужно в новом правиле выбрать только те структуры ввода, которые содержат что‑то похожее на идентификаторы.

Метапеременные второго правила тоже зацепили для нас конкретные значения — и инпута, и найденных полей.
Далее используется режим join в Semgrep. Он позволяет объединять срабатывания разных правил по общим метапеременным и анализировать их как единый набор данных, а не как независимые результаты отдельных правил.

Мы объединяем эти два набора данных по имени входной структуры, в одном будут все резолверы без авторизации, а во втором — одноименные инпуты с идентификаторами в структуре данных.

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

Намеренно тут опустим подробности о работе с рекурсией в типах инпутов (Semgrep вполне может в рекурсию), нагрузкой (в каких‑то больших проектах эта самая рекурсия оказалась не лучшей идеей), об особенностях парсера Semgrep.
И лучше перейдем ко второй части про поиск проблем в динамике.
Ищем в динамике с nuclei и артефактами от QA
Вернемся к общей схеме системы. В нее добавляется слой контроля качества (QA): в GitLab запускаются end-to-end-автотесты, которые от имени автоматически созданных тестовых пользователей обращаются к клиентским API на тестовом окружении. Все отправленные запросы и полученные ответы сохраняются в хранилище артефактов Allure.

Продолжение доступно только участникам
Материалы из последних выпусков становятся доступны по отдельности только через два месяца после публикации. Чтобы продолжить чтение, необходимо стать участником сообщества «Xakep.ru».
Присоединяйся к сообществу «Xakep.ru»!
Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее
