BIPI
BIPI

GraphQL Batching Abuse and How to Stop It

Cybersecurity

GraphQL aliasing lets one HTTP request execute hundreds of operations, which silently bypasses rate limits and turns login mutations into brute-force engines. Here's how to detect and mitigate batching abuse.

By Arjun Raghavan, Security & Systems Lead, BIPI · January 17, 2025 · 7 min read

#graphql#api-security#rate-limiting

Most rate limiters count HTTP requests. GraphQL lets a single HTTP request execute hundreds of operations through aliases or array batching, so the limiter sees one request while the resolver does 500 things. This is the bug class behind countless brute-force, scraping, and resource-exhaustion findings.

How to test for it

Take any rate-limited mutation, like login or password reset, and rewrite the query to alias it 100 times in a single request.

  • Aliased login: { a1: login(email:"u@x.com", password:"p1"){ token } a2: login(email:"u@x.com", password:"p2"){ token } ... a100: ... } sent as one POST.
  • Array batching: send a JSON array of 100 separate operations, [{query:"..."}, {query:"..."}, ...], where the GraphQL endpoint handles each independently.
  • Mixed alias plus batching: 10 requests in an array, each containing 100 aliases, for 1000 logical operations per HTTP request.
  • Test against signup endpoints with disposable email patterns to scrape captcha-protected flows.

InQL automates alias-based attacks. The graphql-cop tool flags batching support in seconds. For password spray specifically, build a Burp Intruder payload that injects a different password per alias position and watch for differential responses.

Why naive limiters fail

A typical Apollo deployment puts a Cloudflare or NGINX rate limiter in front, configured at 60 requests per minute per IP. The attacker sends one request with 100 aliases. Cloudflare counts one. The resolver runs 100 logins. The attacker is now at 6000 logins per minute against a limiter set to 60.

Detection

Parse the GraphQL query at the gateway, count selection-set operations and aliases, and emit metrics per-user. Alert on any single request executing more than 10 operations or aliases. Apollo Studio surfaces operation-name distributions; alert on a flat distribution where one operation suddenly accounts for 90 percent of executions, since that is the brute-force signature.

Remediation

  1. Count aliases and array-batched operations against your rate limit, not HTTP requests. graphql-armor-max-aliases and graphql-armor-max-tokens provide drop-in middleware for Apollo and Yoga.
  2. Set a maximum alias count per request. Five is plenty for legitimate clients. Reject requests above with a 400.
  3. Disable HTTP-level batching unless you specifically need it. In Apollo Server, set allowBatchedHttpRequests: false.
  4. Adopt persisted queries with operation hashes. Pre-registered operations cannot contain runtime alias attacks.
  5. Apply rate limits at the resolver level, keyed on the resolver name plus user, so 100 aliased logins count as 100 logins regardless of HTTP packaging.
  6. Add CAPTCHA or proof-of-work on the login mutation specifically, gated by request frequency.

Real bug we found

On a fintech with strong password policies and 5-attempts-per-minute lockout, a single GraphQL POST with 200 aliased login mutations bypassed lockout entirely because the lockout counter incremented per HTTP request. Fix took two days: add graphql-armor-max-aliases at limit 3, and move lockout counting into the resolver itself, keyed on the email argument, so each aliased call increments the counter.

Validation

Send 10 aliased logins and confirm rejection at the alias count gate. Send a valid query, then re-send with 50 aliases on different fields and confirm rejection. Confirm operation hash whitelisting in production by sending an unregistered query and verifying a PersistedQueryNotFound error. Add a CI test that POSTs 100 aliased operations against a stub endpoint and asserts a 400.

Read more field notes, explore our services, or get in touch at info@bipi.in. Privacy Policy · Terms.