Статья по безопасности GraphQL (GraphQL Cheat Sheet)
Введение
GraphQL — открытый язык запросов (изначально Facebook) для построения API как альтернативы REST и SOAP. Популярен с 2012 года за гибкость для разработчиков и клиентов. Есть серверы и клиенты на разных языках. Многие компании используют GraphQL, в том числе GitHub, Credit Karma, Intuit и PayPal.
Эта статья охватывает ключевые темы безопасности GraphQL:
- Строгая валидация входных данных.
- Ограничение «дорогих» запросов против отказа в обслуживании (DoS).
- Корректный контроль доступа.
- Отключение небезопасных настроек по умолчанию (избыточные ошибки, интроспекция, GraphiQL и т.д.).
Типичные атаки
- Инъекции — в т.ч.:
- DoS (отказ в обслуживании)
- Нарушение авторизации: недостаточная или избыточная выдача данных, в т.ч. IDOR
- Атаки пакетной обработки (batching) — специфичный для GraphQL способ брутфорса
- Злоупотребление небезопасными настройками по умолчанию
Практики и рекомендации
Валидация входных данных
Строгая валидация снижает риск инъекций и DoS. В GraphQL клиент передаёт идентификаторы, а бэкенд через резолверы ходит в HTTP, БД и др. Ввод попадает во внешние вызовы — это типичная поверхность для инъекций и DoS.
См. шпаргалки OWASP: Input Validation и Injection Prevention.
Общие практики
Принимайте только допустимые значения (белый список).
- Используйте строгие типы GraphQL: скаляры, enum; для сложных правил — валидаторы и при необходимости пользовательские скаляры.
- Задавайте схемы входа для мутаций.
- Явно разрешайте символы, не полагайтесь на чёрный список.
- Чем уже белый список, тем лучше; часто разумный старт — только буквы/цифры без Unicode, что отсекает многие атаки.
- Для Unicode используйте единую внутреннюю кодировку.
- Отклоняйте неверный ввод без избыточных подсказок о внутренней логике API.
Защита от инъекций
Если ввод передаётся в другой «интерпретатор» (SQL/NoSQL/ORM, ОС, LDAP, XML):
- Предпочитайте библиотеки с безопасным API (параметризация и т.п.).
- Следуйте документации.
- ORM/ODM полезны, но при неправильном использовании возможны ORM-инъекции.
- Иначе экранируйте/кодируйте по правилам целевого языка; выбирайте поддерживаемые библиотеки.
Дополнительно:
- SQL Injection Prevention
- NoSQL Injection Prevention
- LDAP Injection Prevention
- OS Command Injection Prevention
- XML Security и XXE Injection Prevention
Валидация процессов
Даже санитизированный ввод не должен управлять потоком данных без необходимости: например, не выполняйте HTTP-запросы на хост, который задал пользователь (если нет жёсткой бизнес-потребности).
Защита от DoS
DoS бьёт по доступности и стабильности API. Ниже — меры на уровне приложения и стека; отдельная шпаргалка — DoS.
Специфично для GraphQL:
- ограничение глубины запросов;
- ограничение «количества» (amount) объектов в запросе;
- пагинация;
- разумные таймауты на уровне приложения и/или инфраструктуры;
- анализ стоимости запроса и верхний предел;
- rate limiting по IP и/или пользователю;
- батчинг и кэш на сервере (DataLoader).
Ограничение глубины и количества
У запроса есть глубина вложенности и число запрашиваемых элементов (например first: 99999999). По умолчанию ограничений может не быть — риск DoS. Задайте лимиты (часто небольшая своя реализация). См. Apollo и How to GraphQL. Пагинация также помогает производительности.
graphql-java: MaxQueryDepthInstrumentation. JavaScript: graphql-depth-limit, graphql-input-number.
Пример запроса большой глубины:
query evil { # Depth: 0
album(id: 42) { # Depth: 1
songs { # Depth: 2
album { # Depth: 3
... # Depth: ...
album {id: N} # Depth: N
}
}
}
}
Пример запроса с огромным first:
query {
author(id: "abc") {
posts(first: 99999999) {
title
}
}
}
Таймауты
Таймауты ограничивают ресурсы на один запрос, но срабатывают не всегда вовремя. Значение зависит от API и способа получения данных.
На уровне приложения — таймауты для запросов и резолверов (в GraphQL из коробки нет — нужен свой код). См. статью в Medium и примеры ниже.
Пример таймаута (JavaScript) — фрагмент из ответа на Stack Overflow:
request.incrementResolverCount = function () {
var runTime = Date.now() - startTime;
if (runTime > 10000) { // таймаут 10 с
if (request.logTimeoutError) {
logger('ERROR', `Request ${request.uuid} query execution timeout`);
}
request.logTimeoutError = false;
throw 'Query execution has timeout. Field resolution aborted';
}
this.resolverCount++;
};
Пример таймаута (Java) через Instrumentation
public class TimeoutInstrumentation extends SimpleInstrumentation {
@Override
public DataFetcher<?> instrumentDataFetcher(
DataFetcher<?> dataFetcher, InstrumentationFieldFetchParameters parameters
) {
return environment ->
Observable.fromCallable(() -> dataFetcher.get(environment))
.subscribeOn(Schedulers.computation())
.timeout(10, TimeUnit.SECONDS) // таймаут 10 с
.blockingFirst();
}
}
Инфраструктурный таймаут
Проще задать таймаут на HTTP-сервере (Apache, nginx), reverse proxy или балансировщике; такие таймауты грубее и их чаще обходят, чем логику в приложении.
Анализ стоимости запроса
Полям/типам назначают «стоимость»; слишком дорогие запросы отклоняются. Реализация сложная и не всегда нужна — сначала проверьте API «тяжёлым» запросом на стенде. Цитата Apollo:
Прежде чем тратить много времени на анализ стоимости, убедитесь, что он вам нужен. Попробуйте «положить» staging API тяжёлым запросом — возможно, у вас нет глубокой вложенности или тысячи записей за раз обрабатываются нормально.
Подробнее — в разделе «Query Cost Analysis» той же статьи Apollo.
graphql-java: встроенный MaxQueryComplexityInstrumentation. JavaScript: graphql-cost-analysis или graphql-validation-complexity.
Ограничение частоты запросов
Лимиты по IP или пользователю (в т.ч. для анонимов) снижают спам. Удобно делать на WAF, API-шлюзе или веб-сервере (Nginx, Apache / mod_evasive). Реализация в коде сложнее. Про throttling в GraphQL — How to GraphQL.
Батчинг и кэш на сервере
Чтобы не дублировать запросы за одними и теми же данными в коротком окне, используйте батчинг и кэширование; например DataLoader.
Ресурсы системы
Без лимитов CPU/памяти API уязвим к DoS. На Linux — cgroups, ulimits, LXC; в контейнерах проще — см. раздел про лимиты ресурсов в Docker Security Cheat Sheet.
Контроль доступа
- Всегда проверяйте, что субъект имеет право читать или менять запрашиваемые данные (RBAC и др.). Это снижает риск IDOR, включая BOLA и BFLA.
- Проверки авторизации на рёбрах и на узлах графа (см. пример: на рёбрах было, на узлах — нет).
- Интерфейсы и union помогают возвращать разный набор полей в зависимости от прав.
- В резолверах query/mutation можно встраивать проверки, в т.ч. через middleware RBAC.
- Отключите интроспекцию в продакшене и на публичных контурах.
- Отключите GraphiQL и аналоги в продакшене / на публичных URL.
Доступ к объектам по ID
Часто в запросе передаётся прямой ID сущности (как первичный ключ в БД). Наличие ID не означает права доступа; иначе это BOLA / IDOR.
В схеме могут быть поля node / nodes для прямого доступа по ID. Проверка: cat schema.json | jq ".data.__schema.types[] | select(.name==\"Query\") | .fields[] | .name" | grep node. Удаление полей из схемы отключает путь, но авторизацию всё равно нужно проверять явно.
Доступ к полям (чтение)
Решите, могут ли разные клиенты читать разные наборы полей; при необходимости проверяйте в коде право на каждое поле.
Доступ к мутациям (изменение)
Если разрешены мутации, ограничьте, кто и какие поля может менять (только чтение для части клиентов, разные роли и т.д.).
Атаки через batching
GraphQL позволяет батчить запросы — несколько операций или несколько объектов за один HTTP-вызов, что даёт batching-атаку: быстрее и менее заметно, чем классический брутфорс. Типичный формат:
[
{ query: QUERY_0, variables: VARS_0 },
{ query: QUERY_1, variables: VARS_1 },
{ query: QUERY_N, variables: VARS_N }
]
Пример одного запроса с несколькими экземплярами droid:
query {
droid(id: "2000") {
name
}
second:droid(id: "2001") {
name
}
third:droid(id: "2002") {
name
}
}
Риски: DoS на уровне приложения, перечисление сущностей, брутфорс паролей/OTP/сессий; WAF/SIEM могут не увидеть множество операций в одном запросе; лимиты nginx по числу запросов не сработают.
Смягчение batching-атак
Лимиты на уровне кода «на один запрос»:
- ограничить число запрашиваемых объектов;
- запретить батчинг для чувствительных сущностей;
- ограничить число операций в одном батче.
Можно считать число разных объектов в одном вызове и блокировать после порога; для чувствительных данных отключить батчинг, чтобы атакующий вынужден был слать отдельные запросы как к REST. Комбинируйте меры.
Безопасная конфигурация
Часто по умолчанию включено нежелательное поведение:
- не возвращайте избыточные ошибки (без стека и debug в проде);
- ограничьте или отключите интроспекцию и GraphiQL по политике;
- учитывайте подсказки при опечатках полей, если интроспекция выключена.
Интроспекция и GraphiQL
Часто интроспекция и GraphiQL доступны без аутентификации — раскрывается схема, мутации, устаревшие и «внутренние» поля. Для внешнего API это может быть нормально; для внутреннего — нет. Безопасность через сокрытие не заменяет контролей, но отключение интроспекции снижает утечки. См. Wallarm и документацию вашей реализации; при необходимости фильтруйте доступ по ролям.
Без интроспекции поля всё равно можно угадывать; GraphQL может подсказывать похожие имена (Did you mean "user?") — при возможности отключите; не все движки это умеют. См. Shapeshifter и доклад.
Отключение интроспекции (Java)
GraphQLSchema schema = GraphQLSchema.newSchema()
.query(StarWarsSchema.queryType)
.fieldVisibility( NoIntrospectionGraphqlFieldVisibility.NO_INTROSPECTION_FIELD_VISIBILITY )
.build();
Отключение интроспекции и GraphiQL (JavaScript)
app.use('/graphql', graphqlHTTP({
schema: MySessionAwareGraphQLSchema,
validationRules: [NoIntrospection],
graphiql: process.env.NODE_ENV === 'development',
}));
Не раскрывайте лишнее в ошибках
В продакшене не отдавайте stack trace и не держите отладочный режим. В Apollo Server: debug: false или NODE_ENV в production/test. Внутренний лог стека без отдачи клиенту — masking and logging errors, общая документация по ошибкам.
Другие материалы
Инструменты
- InQL Scanner — сканер безопасности GraphQL; генерация запросов/мутаций по схеме.
- GraphiQL — обзор схемы и объектов.
- GraphQL Voyager — визуализация схемы.
Практики и документация
- Protecting GraphQL APIs from security threats
- Security points before implementing GraphQL
- Ограничение ресурсов (таймауты, throttling, сложность, глубина)
- Security perspectives of GraphQL
- Security perspective for developers
Дополнительно об атаках
© Перевод на русский язык. Оригинальные материалы: OWASP Cheat Sheet Series.
Этот проект использует материалы OWASP, распространяемые по лицензии CC BY-SA 4.0.