GraphQL заменяет десятки REST-эндпоинтов единой гибкой поверхностью запросов. Именно эта гибкость ломает предположения безопасности, которые команды переносят из REST. Один POST на /graphql может обходить произвольные графы объектов, запрашивать вложенные связи и упаковывать сотни операций в один запрос. Если авторизация, rate-limiting и контроль стоимости проектировались по эндпоинтам, здесь они не работают. Этот материал разбирает главные специфичные для GraphQL риски и серверные меры, которые реально их снижают.
Утечка интроспекции
GraphQL самоописателен. Один запрос интроспекции __schema возвращает каждый тип, поле, аргумент, мутацию и устаревшее поле. Для атакующего это бесплатная и достоверная карта всей поверхности атаки — включая внутренние администраторские мутации и поля, которые вы считали скрытыми.
Интроспекция — легитимный инструмент разработчика, но в проде она отдаёт разведку любому. Отключите её в продакшн-сборках (например introspection: false в Apollo Server) и отключите IDE Playground/GraphiQL в публичных развёртываниях. Помните: отключение интроспекции — это defense-in-depth, а не авторизация — инструменты вроде Clairvoyance восстанавливают схему по подсказкам полей, поэтому отключите и сообщения-подсказки и никогда не полагайтесь на секретность схемы как границу безопасности.
Глубина и сложность запросов: неаутентифицированный DoS
Поскольку форму запроса выбирает клиент, он может создавать глубоко вложенные циклические запросы, взрывающие работу сервера. Если у Author есть posts, а у Post есть author, атакующий рекурсивно спускается author→posts→author→posts на произвольную глубину, вызывая экспоненциальное выполнение резолверов и нагрузку на БД из одного небольшого запроса. Это соответствует OWASP API4:2023 Unrestricted Resource Consumption и является классическим асимметричным DoS.
Применяйте послойно:
- Ограничение глубины — отклоняйте запросы выше фиксированной вложенности (например graphql-depth-limit 7–10).
- Анализ сложности — назначьте стоимость каждому полю и отклоняйте запросы выше бюджета до выполнения (graphql-cost-analysis, graphql-query-complexity).
- Лимиты пагинации — принудительно ограничивайте first/last; никогда не допускайте безграничных списков.
- Таймауты и лимиты количества — ограничивайте время выполнения и число возвращаемых узлов.
- Persisted queries — разрешайте только утверждённый список документов запросов в проде, полностью устраняя произвольные формы.
Злоупотребление батчингом
GraphQL позволяет отправить массив операций в одном HTTP-запросе, а алиасинг позволяет клиенту повторить одно поле многократно под разными именами. Оба обходят наивный rate-limiting по запросам. Один запрос с 1000 алиасированных мутаций login обнуляет защиту от перебора:
mutation { a: login(pw:"1"){token} b: login(pw:"2"){token} ... }
Ограничивайте число операций на запрос, число алиасов для чувствительных полей и применяйте rate-limiting по стоимости операций, а не по числу HTTP-запросов. Применяйте объектный троттлинг к аутентификации и другим склонным к злоупотреблению мутациям.
BOLA / авторизация на уровне объекта в резолверах
Broken Object Level Authorization (OWASP API1:2023) — самая разрушительная и частая уязвимость GraphQL. Запрос { invoice(id: "1042") { total, customer { email } } } вернёт счёт чужого арендатора, если резолвер выбирает по ID без проверки владения. GraphQL усугубляет это, потому что авторизацию нужно применять в каждом резолвере на каждом пути — поле, достижимое через три разных запроса, требует проверки во всех трёх.
Не авторизуйте на уровне HTTP. Применяйте авторизацию на уровне поля и объекта внутри резолверов или выделенного слоя авторизации: проверяйте, что аутентифицированный субъект имеет доступ к конкретному экземпляру объекта, а не только к типу. Переносите идентичность в контексте и предпочитайте движок политик (graphql-shield, OSO или явные guard-ы), чтобы проверки были централизованы и тестируемы. Считайте каждое поле напрямую достижимым и по умолчанию отказывайте.
Инъекции через резолверы
GraphQL ничего не санитизирует — он передаёт аргументы прямо в резолверы, которые часто строят SQL, NoSQL или команды ОС. SQL-инъекция (CWE-89), NoSQL-инъекция и SSRF живут в коде резолверов. Поскольку язык запросов выглядит структурным, разработчики ошибочно считают ввод безопасным. Всегда используйте параметризованные запросы, валидируйте кастомные скаляры и считайте каждый аргумент недоверенным. Остерегайтесь аргументов filter/where, передающих сырые объекты операторов в запрос Mongo/ORM.
Ошибки и раскрытие информации
Многословные ошибки GraphQL раскрывают стек-трейсы, сообщения драйвера БД и внутренние пути полей. Маскируйте ошибки в проде, логируйте детали на сервере и возвращайте клиентам общие сообщения. Вместе с отключённой интроспекцией это лишает атакующего цикла разведки.
Практический чек-лист укрепления
- Отключите интроспекцию и IDE в проде; отключите подсказки полей.
- Применяйте лимиты глубины и сложности и пагинации до выполнения.
- Ограничьте батчинг и алиасы; rate-limiting по стоимости, а не по числу запросов.
- Авторизация на уровне объекта в каждом резолвере, отказ по умолчанию, централизованная политика.
- Параметризованные запросы и строгая валидация ввода в резолверах.
- Маскируйте ошибки и рассмотрите persisted/allow-listed queries для сильнейшей позиции.
GraphQL не является небезопасным по своей природе, но переносит границу безопасности с эндпоинта в граф резолверов. Смоделируйте этот граф, тестируйте каждый достижимый путь на авторизацию и стоимость и относитесь к схеме как к живой поверхности атаки, а не к документации. При оценке API GraphQL мы сопоставляем находки с CVSS и OWASP API Security Top 10, чтобы приоритизировать устранение по реальному бизнес-влиянию.