GraphQL-Alias-Batching: wie ein Request 200 Mutations packt
Cloudflare zählt HTTP-Calls, GraphQL-Resolver sind innerhalb eines Requests meist nicht rate-limited. Das ist keine Schwäche von Cloudflare, sondern ein Muster das Security-Teams oft übersehen. Hier der Pattern, die Exploits, und die einzige saubere Mitigation.
Das Muster in einem Satz
Wenn deine API-Gateway-Schicht Rate-Limits auf HTTP-Request-Ebene enforced und dein GraphQL-Server die Anzahl der operations pro Request nicht separat gegenprüft, kann ein Angreifer mit einem einzigen Request mehrere hundert Mutations ausführen.
Das ist kein exotischer Angriff. GraphQL-Alias-Syntax erlaubt es per Design:
mutation {
a1: submitVote(option: "yes") { ok }
a2: submitVote(option: "yes") { ok }
a3: submitVote(option: "yes") { ok }
# ... bis a200
}
Ein Request. Ein HTTP-Call. 200 Resolver-Ausführungen.
Warum das so oft unbemerkt ist
Drei Gründe:
- Cloudflare-Rules sehen nur HTTP-Request-Anzahl. Der Gateway-Layer prüft URL-Pattern, Header, Method, Source-IP. Der Body bleibt opaque.
- GraphQL-Server gehen davon aus, dass der Gateway vor ihnen ist und das regelt. Die Resolver-Implementation vertraut darauf, dass Rate-Limiting schon auf Netzwerk-Ebene passiert ist.
- Developer-Tests verwenden meist ein Operation pro Request. Die Alias-Pattern-Edge-Case taucht in Unit-Tests selten auf.
Konkrete Exploit-Klassen
- Rate-Limit-Bypass auf sensitive Mutations: Password-Reset, OTP-Generation, Credit-Application-Submit. Wenn ein Request 200 dieser Mutations enthält, bekommt ein Angreifer in einem Schuss soviel Output wie normalerweise in Stunden.
- Brute-Force-Enumeration: Mutations die existierende vs nicht-existierende User per differential Error-Response signalisieren. Aliases erlauben 200 parallele Checks in einem Request.
- Credit/Balance-Race-Conditions: Wenn eine Mutation intern Balance erhöht und Deduplication fehlt, lassen sich Race-Windows innerhalb eines Requests öffnen.
Die einzige saubere Mitigation
Rate-Limiting innerhalb des GraphQL-Servers, nicht am Gateway:
- Operation-Depth-Limit setzen (z. B. max 5)
- Operation-Count-pro-Request limitieren (meist 1-3 für Mutations, mehr nur für Queries)
- Pro Field/Resolver ein Semaphore das pro User-Session kount, nicht pro HTTP-Request
- Eine gute Library dafür ist
graphql-shieldmitrate-limit-Middleware oder selbst gebaut mit Redis-Counter
Was du heute tun kannst
- Greife in deinem GraphQL-Schema nach allen
mutation-Fields und prüfe: würde eine 100x-Alias-Ausführung in einem Request etwas nicht-idempotentes 100x auslösen? - Wenn ja → Operation-Count-Limit einbauen. Typisch: max 5 Mutations pro Request.
- Für alle verbleibenden Mutations: Rate-Limit pro User-Session (nicht pro IP, nicht pro Request). Redis-Zähler mit Sliding-Window.
Referenz
Wir haben dieses Muster 2026 bei einem Tier-1-Marketplace in einer Session-Audit-Engagement gefunden und responsible disclosed. Die Mitigation-Empfehlung mit graphql-shield-Rules wurde innerhalb 48 Stunden implementiert. Finding-Pattern ist ein wiederkehrender Klassiker, kein Einzelfall.