Führen Sie diese fünf Tests gegen ein Staging-Deployment Ihrer GraphQL-API aus, bevor der Endpoint öffentlich erreichbar ist. Konfigurieren Sie Staging so, wie später die Produktion konfiguriert sein wird, sonst sind die Ergebnisse kaum aussagekräftig.
Die Tests folgen den vier Bereichen des OWASP GraphQL Cheat Sheet: Eingabevalidierung, teure Queries, Zugriffskontrolle und unsichere Standardkonfiguration.
1. Defaults prüfen
Übermäßig detaillierte Fehlermeldungen, Introspection und GraphiQL sind die typischen unsicheren Defaults. Testen Sie alle drei. Beginnen Sie mit der kleinstmöglichen Introspection-Query:
{__schema{types{name}}}
Prüfen Sie, ob der Server mit Schema-Details antwortet oder die Anfrage ablehnt. Suchen Sie anschließend im Browser nach GraphiQL. Schicken Sie zuletzt eine fehlerhaft formulierte Query und eine Query mit einem ungültigen Argument und lesen Sie die Fehlermeldung. Resolver-Namen, Stack Traces oder Datenbankmeldungen in der Antwort sind für Angreifer eine Landkarte.
Die Lösung ist reine Konfiguration. Deaktivieren Sie Introspection und GraphiQL überall dort, wo die API nicht zum Erkunden gedacht ist, geben Sie generische Fehlermeldungen zurück und halten Sie die Details in den serverseitigen Logs fest.
2. Feindliche Eingaben durch jedes Argument schicken
Der Nutzer liefert Identifier, und Data Fetcher verwenden diese anschließend in HTTP-Requests, Datenbankabfragen und anderen Aufrufen. Genau dort entsteht Injection. Testen Sie jedes Argument jeder Query und jeder Mutation mit den Payloads, die Sie auch bei jeder anderen API einsetzen würden: SQL- und NoSQL-Injection, OS Command Injection, SSRF und CRLF-Injection.
Damit sich das skripten lässt, erzeugen Sie die Argumentlisten aus dem Schema, solange Introspection in Staging noch aktiv ist, lassen Ihre Payloads darüber laufen und schalten Introspection danach ab.
Das sollten Sie durchsetzen:
- Spezifische GraphQL-Typen wie Scalars und Enums sowie definierte Schemas für Mutation-Eingaben.
- Eine Allowlist von Zeichen statt einer Denylist. Alphanumerische Eingaben ohne Unicode sind ein guter Ausgangspunkt, weil sie viele Angriffe ausschließen.
- Parametrisierte Statements für alles, was an einen Interpreter übergeben wird. Wenn Sie ein ORM oder ODM einsetzen, verwenden Sie es wie dokumentiert, denn ORM-Injection ist weiterhin möglich.
- Ablehnung ungültiger Eingaben, ohne offenzulegen, wie die Validierung funktioniert.
3. Die teure Query finden
Eine API ist verwundbar, wenn irgendein Ressourcenlimit fehlt oder schlecht gesetzt ist. Der OWASP-Eintrag zu Unrestricted Resource Consumption nennt Execution Timeouts, maximal allozierbaren Speicher, File Descriptors, Prozesse, die maximale Größe von Uploads, Datensätze pro Seite, Operationen pro Client-Request und Ausgabenlimits bei Drittanbietern.
Bauen Sie zum Testen die tiefste Query, die Ihr Schema zulässt, indem Sie Relationen in einer Schleife verfolgen. Passen Sie die Namen an Ihr Schema an:
{ users { orders { customer { orders { customer { email } } } } } }
Fügen Sie weitere Ebenen hinzu, bis der Server ablehnt oder langsamer wird. Fordern Sie danach eine sehr große Seitengröße an und senden Sie eine überdimensionierte Payload.
Das Cheat Sheet empfiehlt, Prüfungen einzubauen, die zu teure Queries begrenzen oder verhindern. Kombinieren Sie das mit einer Obergrenze für die Seitengröße, einem Execution Timeout und Maximalgrößen für Strings, Arrays und Uploads. Leiten Sie die Werte aus den Traffic-Logs einer Woche ab, damit legitime Clients darunter bleiben.
4. Batching am Rate Limiter vorbei
Batching ist Brute Force, bei dem die Anzahl der Requests verborgen bleibt. Im zweiten Szenario des OWASP-Eintrags steht eine Upload-Mutation hinter einem Rate Limiter und einer Größenprüfung. Der Angreifer sendet einen einzigen POST an /graphql, der ein Array mit fast tausend Operationen enthält. Der Limiter sieht einen Request, und dem Server geht der Speicher aus. Wenn Ihre API Uploads annimmt, behandelt unser Artikel zu sicheren Datei-Uploads: Entwurf und Test diese Seite.
Versuchen Sie dasselbe gegen eine Login- oder OTP-Mutation:
[
{"query": "mutation {login(user: \"alice\", pass: \"guess1\") {token}}"},
{"query": "mutation {login(user: \"alice\", pass: \"guess2\") {token}}"}
]
Prüfen Sie, ob Ihr Limit jede Operation im Array zählt und ob fehlgeschlagene Versuche trotzdem Ihre Lockout-Logik erreichen. Probieren Sie dann jede weitere Möglichkeit aus, die Ihr Server bietet, mehrere Operationen in einem Request zu bündeln.
Begrenzen Sie die Anzahl der Operationen pro Client-Request und drosseln Sie, wie oft ein einzelner Client eine bestimmte sensible Operation ausführen darf, etwa die Validierung eines OTP. Begrenzen Sie auch die Kosten: Im ersten Szenario des Eintrags löst ein Skript über einen Passwort-vergessen-Ablauf zehntausende kostenpflichtige SMS aus. Setzen Sie Ausgabenlimits bei Drittanbietern und Billing-Alerts dort, wo Limits nicht möglich sind.
5. Nach dem Feld eines anderen fragen
Authentifizierung sagt Ihnen, wer anfragt. Sie entscheidet nicht, welche Felder diese Person lesen darf. Der GraphQL-Leitfaden zur Autorisierung sagt, dass die Ausführung erst beginnen sollte, nachdem Authentifizierungs-Middleware die Identität bestätigt hat, und dass der Server dennoch pro angefragtem Feld entscheiden muss, ob der Nutzer es sehen darf.
Testen Sie mit zwei gewöhnlichen Accounts. Fordern Sie mit der Session von Account A die Objekte von Account B per ID an und erreichen Sie sie anschließend über ein verknüpftes Objekt, das beide Accounts sehen können. Passen Sie die Namen an Ihr Schema an:
# Direct lookup: should be refused for account A
{ customer(id: "CUSTOMER_ID_OF_B") { email } }
# Same data through a shared parent: must also be refused
{ product(id: "SHARED_PRODUCT_ID") { orders { customer { email } } } }
Wird die erste Query abgelehnt und die zweite liefert die E-Mail-Adresse von B, sitzt die Prüfung auf dem Top-Level-Lookup und wurde nie auf den verschachtelten Typ angewendet. Wiederholen Sie den Test ganz ohne Session. Werden dieselben Daten auch über REST oder eine Mobile-API ausgeliefert, testen Sie auch diese Einstiegspunkte. Unser Artikel jede ID Ihrer App auf IDOR und BOLA testen beschreibt dasselbe Vorgehen über GraphQL hinaus.
Autorisierung innerhalb eines Field Resolvers ist für Prototypen in Ordnung, muss aber für jeden Einstiegspunkt dupliziert werden. Laufen die Kopien auseinander, sehen Nutzer je nach genutzter API unterschiedliche Daten. Verlagern Sie die Logik in die Business-Logic-Schicht, damit es eine einzige verlässliche Quelle gibt. Im Beispiel des Leitfadens erhält postRepository.getBody den Nutzer und den Post und gibt den Inhalt nur an den Autor zurück. Übergeben Sie dabei ein vollständig geladenes User-Objekt, keinen opaken Token und keinen API-Key.
Was ein Scanner Ihnen nicht verrät
Automatisierte Tools liefern Breite, doch der OWASP Web Security Testing Guide weist darauf hin, dass sie für Anwendungen im Allgemeinen gebaut sind und nicht für Ihren individuellen Code und Ihre Geschäftsregeln. Die schwerwiegendsten Schwachstellen sind häufig mit der Geschäftslogik verflochten.
Die Tests 1 bis 3 lassen sich überwiegend skripten. Test 5 erfordert manuellen Aufwand, weil jemand verstehen muss, welcher Mandant welches Objekt besitzt. Deshalb lohnt es sich bei ihm am meisten, ein externes Team zu beauftragen. Unser Penetrationstest-Angebot deckt das ab.
Wo Sie anfangen
- Senden Sie die Introspection-Query und öffnen Sie GraphiQL im Staging-Deployment. Schalten Sie ab, was antwortet, und testen Sie anschließend mit einer fehlerhaften Query erneut, um die Fehlermeldungen zu prüfen.
- Listen Sie alle Argumente aller Queries und Mutations auf. Stellen Sie sicher, dass jedes einen spezifischen Typ oder Enum und einen Allowlist-Validator hat.
- Senden Sie die tief verschachtelte Query, eine Anfrage mit großer Seitengröße und eine überdimensionierte Payload. Setzen Sie die Limits, senden Sie die Queries erneut und bestätigen Sie, dass jede abgelehnt wird.
- Senden Sie einen Array-Batch gegen Ihre Login- und OTP-Mutations. Erzwingen Sie ein Limit pro Request sowie für Wiederholungen sensibler Operationen.
- Führen Sie den Zwei-Account-Test samt verschachtelten Relationen für jeden Typ aus, der personenbezogene oder geschäftliche Daten enthält. Verlagern Sie jede Prüfung, die Sie in einem Resolver finden, in die Business-Logic-Schicht.
