
RĂ©cemment, GraphQL gagne de plus en plus en popularitĂ© et suscite de plus en plus l'intĂ©rĂȘt des experts en sĂ©curitĂ© de l'information. La technologie est utilisĂ©e par des entreprises telles que: Facebook, Twitter, PayPal, Github et autres, ce qui signifie qu'il est temps de comprendre comment tester une telle API. Dans cet article, nous parlerons des principes de ce langage de requĂȘte et des instructions pour tester la pĂ©nĂ©tration des applications avec GraphQL.
Pourquoi avez-vous besoin de connaĂźtre GraphQL? Ce langage de requĂȘte se dĂ©veloppe activement et de plus en plus d'entreprises y trouvent une utilisation pratique. Dans le cadre des programmes Bug Bounty, la popularitĂ© de ce langage augmente Ă©galement, des exemples intĂ©ressants peuvent ĂȘtre vus
ici ,
ici et
ici .
La prĂ©parationUn site de test oĂč vous trouverez la plupart des exemples dans cet article.
Une liste avec des applications que vous pouvez également utiliser pour étudier.
Pour interagir avec différentes API, il est préférable d'utiliser l'IDE GraphQL:
Nous recommandons le dernier IDE: Insomnia a une interface simple et pratique, il existe de nombreux paramĂštres et l'auto-complĂ©tion des champs de requĂȘte.
Avant de passer directement aux méthodes générales d'analyse de sécurité des applications avec GraphQL, nous rappelons les concepts de base.
Qu'est-ce que GraphQL?
GraphQL est un langage de requĂȘte API conçu pour fournir une alternative plus efficace, puissante et flexible Ă REST. Il est basĂ© sur un Ă©chantillonnage de donnĂ©es dĂ©claratif, c'est-Ă -dire que le client peut spĂ©cifier exactement les donnĂ©es dont il a besoin Ă partir de l'API. Au lieu de plusieurs points de terminaison, l'API (REST) ââGraphQL reprĂ©sente un seul point de terminaison qui fournit au client les donnĂ©es demandĂ©es.
Différences clés entre REST et GraphQL
GĂ©nĂ©ralement, dans l'API REST, vous devez obtenir des informations de diffĂ©rents points de terminaison. Dans GraphQL, pour obtenir les mĂȘmes donnĂ©es, vous devez effectuer une requĂȘte indiquant les donnĂ©es que vous souhaitez recevoir.

L'API REST fournit les informations que le développeur mettra dans l'API, c'est-à -dire que si vous avez besoin d'obtenir plus ou moins d'informations que l'API ne le suggÚre, des actions supplémentaires seront nécessaires. Encore une fois, GraphQL fournit exactement les informations demandées.
Un ajout utile est que GraphQL a un schéma qui décrit comment et quelles données le client peut recevoir.
Types de requĂȘtes
Il existe 3 principaux types de requĂȘtes dans GraphQL:
- RequĂȘte
- Mutation
- Abonnement
RequĂȘteLes requĂȘtes de requĂȘte sont utilisĂ©es pour rĂ©cupĂ©rer / lire des donnĂ©es dans un schĂ©ma.
Un exemple d'une telle demande:
query { allPersons { name } }
Dans la demande, nous indiquons que nous voulons obtenir les noms de tous les utilisateurs. En plus du nom, nous pouvons spécifier d'autres champs:
Ăąge ,
identifiant ,
messages , etc. Pour savoir quels champs nous pouvons obtenir, vous devez appuyer sur Ctrl + Espace. Dans cet exemple, nous transmettons le paramĂštre avec lequel l'application renverra les deux premiers enregistrements:
query { allPersons(first: 2) { name } }
MutationSi le type de requĂȘte est nĂ©cessaire pour lire des donnĂ©es, le type de mutation est nĂ©cessaire pour Ă©crire, supprimer et modifier des donnĂ©es dans GraphQL.
Un exemple d'une telle demande:
mutation { createPerson(name:"Bob", age: 37) { id name age } }
Dans cette demande, nous créons un utilisateur avec le nom Bob et ùgé de 37 ans (ces paramÚtres sont passés comme arguments), dans la piÚce jointe (accolades) nous indiquons quelles données nous voulons recevoir du serveur aprÚs avoir créé l'utilisateur. Cela est nécessaire pour comprendre que la demande a abouti, ainsi que pour obtenir des données que le serveur génÚre indépendamment, telles que
id .
AbonnementUn autre type de requĂȘte dans GraphQL est l'abonnement. Il est nĂ©cessaire d'informer les utilisateurs de tout changement survenu dans le systĂšme. Cela fonctionne comme ceci: le client souscrit Ă un Ă©vĂ©nement, aprĂšs quoi une connexion est Ă©tablie avec le serveur (gĂ©nĂ©ralement via WebSocket), et lorsque cet Ă©vĂ©nement se produit, le serveur envoie une notification au client concernant la connexion Ă©tablie.
Un exemple:
subscription { newPerson { name age id } }
Lorsqu'une nouvelle personne est créée, le serveur envoie des informations au client. La prĂ©sence de requĂȘtes d'abonnement dans les schĂ©mas est moins courante que la requĂȘte et la mutation.
Il convient de noter que toutes les capacitĂ©s de requĂȘte, de mutation et d'abonnement sont créées et configurĂ©es par le dĂ©veloppeur d'une API spĂ©cifique.
En option
En pratique, les dĂ©veloppeurs utilisent souvent alias et OperationName dans les requĂȘtes pour plus de clartĂ©.
AliasGraphQL pour les requĂȘtes fournit la fonction d'alias, qui peut faciliter la comprĂ©hension de ce que le client demande.
Supposons que nous ayons une requĂȘte du formulaire:
{ Person(id: 123) { age } }
qui affichera le nom d'utilisateur avec l'
ID 123. Que ce nom d'utilisateur soit Vasya.
Pour que la prochaine fois vous ne vous demandiez pas ce que cette demande affichera, vous pouvez le faire comme ceci:
{ Vasya: Person(id: 123) { age } }
Nom de l'opérationEn plus de l'alias, GraphQL utilise OperationName:
query gettingAllPersons { allPersons { name age } }
OperationName est nécessaire pour expliquer exactement ce que fait la demande.
Pentest
AprĂšs avoir compris les bases, nous allons directement au pentest. Comment comprendre qu'une application utilise GraphQL? Voici un exemple de requĂȘte qui a une requĂȘte GraphQL:
POST /simple/v1/cjp70ml3o9tpa0184rtqs8tmu/ HTTP/1.1 Host: api.graph.cool User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:65.0) Gecko/20100101 Firefox/65.0 Accept: */* Accept-Language: ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3 Accept-Encoding: gzip, deflate Referer: https://api.graph.cool/simple/v1/cjp70ml3o9tpa0184rtqs8tmu/ content-type: application/json Origin: https://api.graph.cool Content-Length: 139 Connection: close {"operationName":null,"variables":{},"query":"{\n __schema {\n mutationType {\n fields {\n name\n }\n }\n }\n}\n"}
Quelques paramĂštres par lesquels vous pouvez comprendre que c'est GraphQL, et pas autre chose:
- dans le corps de la requĂȘte, il y a des mots: __schema, champs, operationName, mutation, etc.;
- dans le corps de la requĂȘte, il y a beaucoup de caractĂšres "\ n". Comme le montre la pratique, ils peuvent ĂȘtre supprimĂ©s pour faciliter la lecture de la demande;
- souvent le moyen d'envoyer une demande au serveur: âgraphql
Super, trouvé et identifié. Mais
oĂč insĂ©rer le guillemet comment savoir avec quoi nous devons travailler? L'introspection viendra Ă la rescousse.
Introspection
GraphQL fournit un schĂ©ma d'introspection, c'est-Ă -dire un schĂ©ma dĂ©crivant les donnĂ©es que nous pouvons obtenir. GrĂące Ă cela, nous pouvons dĂ©couvrir quelles demandes existent, quels arguments peuvent / doivent leur ĂȘtre transmis, et bien plus encore. Notez que dans certains cas, les dĂ©veloppeurs n'autorisent pas intentionnellement la possibilitĂ© d'introspection de leur application. Cependant, la grande majoritĂ© laisse encore cette possibilitĂ©.
ConsidĂ©rez les exemples de base des requĂȘtes.
Exemple 1. Obtenir toutes sortes de demandes query { __schema { types { name fields { name } } } }
Nous formons une requĂȘte de requĂȘte, indiquons que nous voulons recevoir des donnĂ©es sur __schema, et dans ce type, leurs noms et champs. Dans GraphQL, il existe des noms de variables de service: __schema, __typename, __type.
Dans la réponse, nous recevrons tous les types de demandes, leurs noms et champs qui existent dans le schéma.
Exemple 2. Obtention de champs pour un type de demande spĂ©cifique (requĂȘte, mutation, description) query { __schema { queryType { fields { name args { name } } } } }
La rĂ©ponse Ă cette demande sera toutes les demandes possibles que nous pouvons exĂ©cuter sur le schĂ©ma de rĂ©ception des donnĂ©es (type de requĂȘte), et les arguments possibles / nĂ©cessaires pour celles-ci. Pour certaines requĂȘtes, la spĂ©cification du ou des arguments est requise. Si vous exĂ©cutez une telle demande sans spĂ©cifier un argument requis, le serveur doit afficher un message d'erreur que vous devez le spĂ©cifier. Au lieu de queryType, nous pouvons remplacer mutationType et subscriptionType pour obtenir toutes les demandes possibles de mutations et d'abonnements, respectivement.
Exemple 3. Obtention d'informations sur un type de demande spécifique query { __type(name: "Person") { fields { name } } }
GrĂące Ă cette requĂȘte, nous obtenons tous les champs pour le type Personne. Comme argument, au lieu de Personne, nous pouvons transmettre tout autre nom de demande.
Maintenant que nous pouvons comprendre la structure générale de l'application testée, déterminons ce que nous recherchons.
Divulgation d'informationsLe plus souvent, une application utilisant GraphQL se compose de nombreux champs et types de requĂȘtes et, comme beaucoup de gens le savent, plus l'application est complexe et volumineuse, plus il est difficile de configurer et de surveiller sa sĂ©curitĂ©. C'est pourquoi, avec une introspection minutieuse, vous pouvez trouver quelque chose d'intĂ©ressant, par exemple: les noms complets des utilisateurs, leurs numĂ©ros de tĂ©lĂ©phone et d'autres donnĂ©es critiques. Par consĂ©quent, si vous souhaitez trouver quelque chose comme ça, nous vous recommandons de vĂ©rifier tous les champs et arguments possibles de l'application. Ainsi, dans le cadre du pentest dans l'une des applications, des donnĂ©es utilisateur ont Ă©tĂ© trouvĂ©es: nom, numĂ©ro de tĂ©lĂ©phone, date de naissance, certaines donnĂ©es de carte, etc.
Un exemple:
query { User(id: 1) { name birth phone email password } }
En parcourant les valeurs id, nous pouvons obtenir des informations sur les autres utilisateurs (et peut-ĂȘtre pas, si tout est correctement configurĂ©).
InjectionsInutile de dire que presque partout oĂč il y a du travail avec une grande quantitĂ© de donnĂ©es, il y a des bases de donnĂ©es? Et lĂ oĂč il y a une base de donnĂ©es - il peut y avoir des injections SQL, des injections NoSQL et d'autres types d'injections.
Un exemple:
mutation { createPerson(name:"Vasya'--+") { name } }
Voici une injection SQL Ă©lĂ©mentaire dans l'argument de requĂȘte.
Contournement d'autorisationDisons que nous pouvons créer des utilisateurs:
mutation { createPerson(username:"Vasya", password: "Qwerty1") { } }
En supposant qu'il existe un certain paramĂštre isAdmin dans le gestionnaire sur le serveur, nous pouvons envoyer une demande du formulaire:
mutation { createPerson(username:"Vasya", password: "Qwerty1", isAdmin: True) { } }
Et faites de l'utilisateur Vasya un administrateur.
Dos
En plus de la commodité déclarée, GraphQL a ses propres failles de sécurité.
Prenons un exemple:
query { Person { posts { author { posts { author { posts { author ... } } } } } } }
Comme vous pouvez le voir, nous avons créé une sous-requĂȘte en boucle. Avec un grand nombre de tels investissements, par exemple 50 000, nous pouvons envoyer une demande qui sera traitĂ©e par le serveur pendant trĂšs longtemps ou la «supprimer» complĂštement. Au lieu de traiter les demandes valides, le serveur sera occupĂ© Ă dĂ©baller l'imbrication gĂ©ante de la demande factice.
En plus de l'imbrication importante, les requĂȘtes elles-mĂȘmes peuvent ĂȘtre «lourdes» - c'est quand une requĂȘte a beaucoup de champs et de piĂšces jointes internes. Une telle demande peut Ă©galement entraĂźner des difficultĂ©s de traitement sur le serveur.
Conclusion
Nous avons donc examiné les principes de base des tests de pénétration des applications avec GraphQL. Nous espérons que vous avez appris quelque chose de nouveau et d'utile pour vous. Si ce sujet vous intéresse et que vous souhaitez l'étudier plus en profondeur, nous vous recommandons les ressources suivantes:
Et n'oubliez pas: la pratique rend parfait. Bonne chance