Protégez votre API GraphQL contre les vulnérabilités

Bonjour, Habr! Je vous présente la traduction de l'article Protéger votre API GraphQL des vulnérabilités de sécurité .


GraphQL devient rapidement le choix des développeurs qui ont besoin de créer une API pour leur application client. Mais, comme toutes les nouvelles technologies, GraphQL est soumis à certains risques de sécurité inhérents. Que vous construisiez un projet tiers ou une application d'entreprise à grande échelle, vous devez vous assurer de vous protéger contre ces menaces.


image


Bien que les menaces rĂ©pertoriĂ©es dans cet article soient spĂ©cifiques Ă  GraphQL, votre implĂ©mentation introduira un nouvel ensemble de menaces qui devront ĂȘtre traitĂ©es. Il est Ă©galement important que vous compreniez les menaces auxquelles sont exposĂ©es les applications en cours d'exĂ©cution sur le rĂ©seau.


Menace: grandes requĂȘtes profondĂ©ment imbriquĂ©es et coĂ»teuses Ă  calculer


Solution : limitation de la profondeur d'imbrication


La puissance fournie par GraphQL est associĂ©e Ă  de nouvelles menaces de sĂ©curitĂ©. Les plus courantes sont les requĂȘtes profondĂ©ment imbriquĂ©es, qui conduisent Ă  des calculs coĂ»teux et Ă  un Ă©norme JSON, qui peuvent nuire au rĂ©seau et Ă  sa bande passante.


La bonne façon de protéger votre API contre ce type d'attaque est de limiter la profondeur des demandes afin que les demandes profondes malveillantes soient bloquées jusqu'à ce que le résultat soit calculé.


GraphQL Depth Limit fournit une interface simple pour limiter la profondeur des requĂȘtes.


import depthLimit from 'graphql-depth-limit' import express from 'express' import graphqlHTTP from 'express-graphql' import schema from './schema' const app = express() app.use('/graphql', graphqlHTTP((req, res) => ({ schema, validationRules: [ depthLimit(10) ] }))) 

Menace: demandes de mutation vulnérables à la force brute


Solution : limiter le nombre de demandes


La recherche de connexions et de mots de passe est la plus ancienne astuce de l'histoire du piratage. Au cours de la derniĂšre dĂ©cennie, il y a eu tellement de fuites de donnĂ©es sur Internet qu'une base de donnĂ©es de 772 904 991 e-mails uniques et 21 222 975 mots de passe uniques a rĂ©cemment Ă©tĂ© trouvĂ©e. Pour vĂ©rifier si des informations sur votre e-mail et votre mot de passe avaient Ă©tĂ© divulguĂ©es, Troy Hunt a mĂȘme crĂ©Ă© un site Web Have I been Pwned , pour lequel, entre autres, il a utilisĂ© cette base de donnĂ©es.


Heureusement, vous avez un moyen facile de rendre le contournement vraiment difficile et coûteux pour les attaquants, ce qui fera de vous une cible moins attrayante pour eux.


Le plugin GraphQL Rate Limit vous permet de spĂ©cifier des restrictions pour vos requĂȘtes de trois maniĂšres diffĂ©rentes: en utilisant des directives graphql-shield personnalisĂ©es ou en utilisant la fonction pour limiter le nombre de base de requĂȘtes.


Ce plugin vous permettra de dĂ©finir une fenĂȘtre de temps et une limite sur le nombre de demandes pour cela. La dĂ©finition d'une grande fenĂȘtre temporelle pour les demandes trĂšs vulnĂ©rables, telles que la connexion, et de petites fenĂȘtres temporelles pour les demandes moins vulnĂ©rables vous aidera Ă  maintenir une expĂ©rience agrĂ©able pour les utilisateurs ordinaires et deviendra un cauchemar pour les attaquants.


Créez une directive pour limiter le nombre de demandes:


Ici, vous aurez besoin d'un identifiant unique pour chaque demande. Vous pouvez utiliser l'adresse IP de l'utilisateur ou un autre identifiant, unique pour chaque utilisateur et correspondant Ă  chaque demande.


 const rateLimitDirective = createRateLimitDirective({ identifyContext: (context) => { return context.id }, }) 

Ajoutez une directive à votre schéma:


 import { createRateLimitDirective } from 'graphql-rate-limit' export const schema = { typeDefs, resolvers, schemaDirectives: { rateLimit: rateLimitDirective, }, } export default schema 

Enfin, ajoutez une directive Ă  votre requĂȘte vulnĂ©rable:


 #        60  Login(input: LoginInput!): User @rateLimit( window: "60s" max: 10 message: "You are doing that too often. Please wait 60 seconds before trying again." ) 

Menace: permettre à l'utilisateur d'influencer les données spécifiques à l'utilisateur


Solution : prenez ces données d'une session utilisateur lorsque cela est possible


Il est facile de supposer que si vous souhaitez autoriser l'utilisateur à mettre à jour une ressource, cela vaut la peine de laisser l'utilisateur décider de la ressource qu'il souhaite mettre à jour. Mais que se passe-t-il si un utilisateur obtient un identifiant de ressource auquel il ne devrait vraiment pas avoir accÚs?


Supposons que nous ayons une demande de mutation updateUser qui permet Ă  l'utilisateur de mettre Ă  jour son profil.


 mutation UpdateUser($input: {"id": "test123" , "email": "test@example.com"}) { UpdateUser(input: $input) { id firstName lastName } } 

S'il n'y a pas de protection cÎté serveur, l'attaquant, disposant d'une liste d'identifiants, pourrait potentiellement mettre à jour l'adresse e-mail de tout utilisateur. La solution évidente ici consiste à ajouter une vérification pour vous assurer que l'ID de l'utilisateur actuel correspond à l'ID dans les champs de saisie.


Ne faites pas ceci:


 function updateUser({ id, email }) { return User.findOneAndUpdate({ _id: id }, { email }) .catch(error => { throw error; }); } 

Une maniĂšre moins Ă©vidente mais correcte de rĂ©soudre ce problĂšme consiste Ă  empĂȘcher l'identifiant d'ĂȘtre utilisĂ© comme entrĂ©e et Ă  utiliser l'identifiant utilisateur de l'objet contextuel.


Faites ceci:


 function updateUser({ email }, context) { return User.findOneAndUpdate({ _id: context.user._id }, { email }) .catch(error => { throw error; }); } 

C'est peut-ĂȘtre un exemple assez banal, mais effectuer de telles actions pour chacun des objets avec lesquels l'utilisateur interagit directement peut vous protĂ©ger contre de nombreuses erreurs risquĂ©es.


ExĂ©cution simultanĂ©e de plusieurs requĂȘtes coĂ»teuses


Solution : limite de coĂ»t des requĂȘtes


En attribuant un prix Ă  chaque demande et en indiquant le prix maximum par demande, nous pouvons nous protĂ©ger contre les intrus qui pourraient essayer de rĂ©pondre Ă  trop de demandes coĂ»teuses en mĂȘme temps.


Le plugin GraphQL Cost Analysis est un moyen facile de spĂ©cifier le coĂ»t des requĂȘtes et la limite de coĂ»t maximum.


Déterminez le coût maximum:


 app.use( '/graphql', graphqlExpress(req => { return { schema, rootValue: null, validationRules: [ costAnalysis({ variables: req.body.variables, maximumCost: 1000, }), ], } }) ) 

Déterminez le coût de chaque demande:


 Query: { Article: { multipliers: ['limit'], useMultipliers: true, complexity: 3, }, } 

Menace: divulgation des détails d'implémentation de GraphQL


Solution : désactiver l'introspection dans le code "combat"


GraphQL est un outil de dĂ©veloppement extrĂȘmement utile. Il est si puissant qu'il documente mĂȘme votre schĂ©ma, vos demandes et vos abonnements pour vous. Ces informations peuvent ĂȘtre une mine d'or pour les attaquants qui souhaitent trouver des vulnĂ©rabilitĂ©s dans votre application.


Le plugin GraphQL Display Introspection empĂȘchera votre schĂ©ma de fuir vers le public. Importez simplement le plugin et appliquez-le Ă  vos rĂšgles de validation.


 import express from 'express'; import bodyParser from 'body-parser'; import { graphqlExpress } from 'graphql-server-express'; + import NoIntrospection from 'graphql-disable-introspection'; const myGraphQLSchema = // ...    ! const PORT = 3000; var app = express(); app.use('/graphql', bodyParser.json(), graphqlExpress({ schema: myGraphQLSchema, validationRules: [NoIntrospection] })); app.listen(PORT); 

Source: https://habr.com/ru/post/fr481840/


All Articles