Sécurité des API GraphQL : introspection, injections et bonnes pratiques
Sécuriser les API GraphQL : désactiver l'introspection en production, limiter la profondeur et la complexité des requêtes, persisted queries, authentification, et prévention des injections.
GraphQL : pourquoi les vulnérabilités sont différentes
GraphQL est fondamentalement différent des API REST du point de vue de la sécurité. Avec REST, chaque endpoint expose une ressource limitée. Avec GraphQL, un seul endpoint expose potentiellement toute votre surface de données via un langage de requête flexible. Cette flexibilité est une arme à double tranchant : elle simplifie le développement mais crée des vecteurs d'attaque absents dans les API REST classiques.
Attaques spécifiques à GraphQL
- Introspection pour la reconnaissance : GraphQL permet à n'importe quel client de découvrir la totalité du schéma (types, champs, mutations, relations). Exposée en production, elle donne à l'attaquant une cartographie complète de vos données.
- Batching attacks : une seule requête GraphQL peut contenir des dizaines d'opérations (batch). Un attaquant peut faire 1000 tentatives de mot de passe en une seule requête HTTP, contournant les rate limiters basés sur le nombre de requêtes.
- Nested queries (attaque N+1 malveillante) : une requête profondément imbriquée peut générer des milliers de requêtes SQL côté serveur, provoquant un DoS applicatif.
- Field suggestions : GraphQL retourne des suggestions pour les champs mal orthographiés (`Did you mean: password?`) — révèle des champs sensibles même sans introspection.
- IDOR via GraphQL : comme dans les API REST, accéder aux objets d'autres utilisateurs en changeant les arguments ID dans les requêtes.
- Injections via les arguments : injection SQL, NoSQL ou commande via les arguments des queries/mutations si non sanitisés.
Désactiver l'introspection en production
L'introspection GraphQL est un outil puissant pour les développeurs — elle permet aux IDE et aux clients de découvrir le schéma. Mais en production, elle offre à un attaquant une carte détaillée de toutes vos données.
Désactivation par framework
- Apollo Server : configurer `introspection: false` dans les options du serveur en production (`NODE_ENV === 'production'`). Apollo Studio Sandbox reste accessible avec un endpoint dédié si nécessaire.
- GraphQL-yoga / Pothos / Nexus : option `disableIntrospection` dans la configuration du serveur
- Hasura : désactiver l'introspection via la variable d'environnement `HASURA_GRAPHQL_ENABLE_INTROSPECTION=false`
- Alternative : restreindre l'introspection par IP (uniquement depuis le réseau interne ou les IPs de développement) plutôt que de la désactiver complètement
Field Suggestions
- Même sans introspection, GraphQL peut révéler des champs sensibles via les suggestions. Désactiver les suggestions ou les masquer en production.
- Apollo Server : utiliser le plugin `@graphql-armor/remove-field-suggestion` ou configurer les options d'erreur pour masquer les suggestions
- Tester avec des requêtes malformées intentionnellement : `{ passw }` → si la réponse contient "Did you mean: password?", les suggestions sont actives
Limiter la profondeur et la complexité
Sans limites, un attaquant peut envoyer des requêtes GraphQL exponentiellement coûteuses. Un schéma avec des relations récursives peut générer des requêtes qui consomment toute la mémoire du serveur.
Depth limiting
- Limiter la profondeur maximale des requêtes : une requête `user → posts → comments → author → posts → comments → ...` à 10 niveaux de profondeur peut générer des millions de requêtes SQL.
- graphql-depth-limit : middleware pour Apollo et Express limitant la profondeur maximale des requêtes. Configurer à 5-10 niveaux selon votre schéma.
- @graphql-armor : suite complète de protections pour Apollo/Yoga incluant depth limit, complexity limit, et protection contre les alias attacks
Query complexity
- Attribuer un coût à chaque field et opération, rejeter les requêtes dépassant un budget de complexité total
- graphql-query-complexity : calcule la complexité d'une requête avant son exécution. Configurer des poids par type de field (champs simples vs relations).
- La complexité est plus précise que la profondeur : une requête peu profonde mais très large (100 fields) peut être plus coûteuse qu'une requête profonde mais étroite
Timeout d'exécution
- Configurer un timeout d'exécution global (ex. 5-10 secondes) pour les requêtes GraphQL — arrêter l'exécution des queries trop lentes
- Combiner avec une limite de payload (taille maximale du body HTTP) pour éviter les requêtes massives
Persisted Queries : whitelist de requêtes
Les Persisted Queries (aussi appelées Trusted Documents) permettent de n'accepter que les requêtes GraphQL pré-enregistrées par votre application. C'est l'équivalent d'une whitelist d'opérations.
- Principe : au build de l'application cliente, hasher toutes les opérations GraphQL (SHA-256). En production, le client envoie uniquement le hash et les variables — le serveur retrouve la requête correspondante dans son store.
- Apollo Persisted Queries : `@apollo/client` + `createPersistedQueryLink` côté client, APQ middleware côté serveur. Côté cache CDN, permet aussi de cacher les requêtes GET GraphQL.
- Avantages sécurité : un attaquant ne peut envoyer que les requêtes déjà autorisées — impossible d'envoyer des requêtes de reconnaissance, des nested queries malveillantes ou des introspections.
- Limitation : ne fonctionne que si vous contrôlez tous les clients. Pour les APIs publiques (third-party), les persisted queries ne peuvent pas être imposées.
- Trusted Documents (GraphQL Yoga) : alternative aux APQ avec une liste blanche explicite des opérations autorisées — plus strict que les APQ car le fallback avec la requête complète est désactivé.
Authentification et autorisation
- Authentification au niveau HTTP : valider le JWT ou le token de session avant d'atteindre le résolveur GraphQL — ne pas confier l'authentification aux résolveurs individuels
- Autorisation field-level : chaque field sensible doit vérifier les permissions de l'utilisateur courant. Utiliser des directives `@auth` personnalisées ou des librairies comme `graphql-shield` pour la gestion des permissions centralisée.
- Context d'authentification : injecter l'utilisateur authentifié dans le context GraphQL au niveau du middleware — accessible dans tous les résolveurs sans requête DB supplémentaire
- Rate limiting par opération : un rate limiter HTTP classique est contournable avec le batching GraphQL. Utiliser un rate limiter GraphQL-aware (par opération et par complexité) comme `graphql-rate-limit`
- Logs d'audit : logger chaque opération GraphQL avec l'utilisateur, les variables (en masquant les données sensibles), et le résultat pour les investigations forensiques
Prévention des injections
- Paramètres typés : GraphQL force la déclaration des types des arguments — utiliser des scalars personnalisés (email, URL, UUID) pour valider le format dès la couche GraphQL
- ORM et requêtes paramétrées : ne jamais construire des requêtes SQL par concaténation dans les résolveurs. Utiliser un ORM (Prisma, TypeORM) ou des requêtes préparées.
- Validation des inputs : valider et assainir les arguments dans les résolveurs avant de les passer aux couches de données (longueur maximale, caractères autorisés, regex)
- NoSQL Injection : si vous utilisez MongoDB, protéger contre les injections via les opérateurs MongoDB (`$where`, `$regex`) dans les arguments GraphQL
Conclusion
Sécuriser une API GraphQL demande des mesures spécifiques absentes dans le toolkit REST classique. La priorité : désactiver l'introspection en production, configurer des limites de profondeur et complexité, et implémenter les persisted queries pour les clients connus. Compléter avec une autorisation field-level rigoureuse et un rate limiting GraphQL-aware. Ces mesures combinées couvrent la quasi-totalité des vecteurs d'attaque spécifiques à GraphQL.
Besoin d'un accompagnement en cybersécurité ?
CloudSecure vous aide à sécuriser votre infrastructure cloud et à protéger vos données.
Contactez-nous