Directive Deception : Exploiter les directives GraphQL personnalisées pour contourner la logique

Dans le paysage moderne des API, GraphQL est souvent présenté comme le “tueur de REST”, offrant aux développeurs une flexibilité et une efficacité inégalées. Mais comme le dit le proverbe, avec un grand pouvoir viennent de nombreuses façons de faire exploser accidentellement votre backend. L’un des surfaces d’attaque les plus sophistiquées mais souvent négligées dans l’écosystème GraphQL est la Directive.
Alors que les directives standard comme @skip et @include sont intégrées dans la spécification, le vrai danger — et la véritable utilité — réside dans les directives personnalisées. Qu’elles soient utilisées pour @auth, @cache, ou @log, ces annotations puissantes se trouvent souvent dans une position précaire : agissant comme un middleware de sécurité qui, s’il est mal configuré, peut être totalement contourné.
Dans cette analyse approfondie, nous explorons “Directive Deception” — l’art d’exploiter les directives GraphQL pour contourner la logique, esquiver les contrôles de sécurité et provoquer une surcharge des ressources.
L’anatomie des directives GraphQL : une épée à double tranchant
Avant de tout casser, il faut comprendre comment elles sont construites. Une directive est un identifiant précédé d’un @ qui peut être attaché à presque n’importe quelle partie d’une requête ou d’un schéma GraphQL.
1. Directives de schéma vs. Directives d’opération
Directives de schéma : Définies côté serveur pour décorer types ou champs (par ex., field: String @auth(role: "ADMIN")). Elles sont souvent utilisées pour déclencher une logique spécifique lors de l’exécution.
Directives d’opération : Envoyées par le client dans la requête elle-même (par ex., query { user @include(if: $isMe) { name } }).
La vulnérabilité apparaît lorsque des directives personnalisées sont implémentées comme des wrappers middleware ou des patterns de visiteur qui inspectent le document de requête. Si la logique qui traite ces directives ne prend pas en compte toute la complexité de l’AST GraphQL (Arbre de Syntaxe Abstraite), un attaquant peut repérer des “angles morts”.
Vulnérabilité 1 : Contournement de @auth via saut de logique
De nombreuses équipes implémentent une sécurité au niveau du champ en utilisant une directive @auth personnalisée. C’est élégant : vous taguez un champ dans votre schéma, et un “Transformateur de Directive” s’assure que seuls les utilisateurs autorisés peuvent le voir.
L’attaque : La directive “Fantôme”
Le problème survient lorsque le code côté serveur ne vérifie que les directives sur les nœuds de type Field, mais oublie de vérifier les Fragments Inline ou les Définitions de Fragment.
Considérez cette requête :
query BypassingAuth {
sensitiveData @auth(role: "ADMIN") # Le middleware voit cela et bloque
}
Maintenant, la version trompeuse :
query StealthyBypass {
... on Query {
sensitiveData # Si le middleware ne vérifie que les champs de premier niveau, cela peut passer
}
}
Si la logique de traitement des directives n’est pas récursive ou ne résout pas les fragments avant de vérifier les permissions, la logique @auth n’est jamais déclenchée. Le moteur d’exécution voit simplement un champ à résoudre et continue vers la base de données, en sautant la barrière de sécurité.
Pourquoi cela arrive
Les développeurs utilisent souvent des “Visiteurs de Schéma” pour envelopper les résolveurs. Si le visiteur ne regarde que la fieldDefinition et ne prend pas en compte la façon dont le client pourrait redéclarer le champ à l’intérieur d’un fragment, le “wrapper” n’est jamais appliqué au chemin d’exécution spécifique.
Vulnérabilité 2 : Injection de directive e0 manipulation d’arguments
L’”Injection de directive” est l’équivalent GraphQL de l’injection SQL, survenant lorsqu’une application construit dynamiquement une chaîne de requête GraphQL en utilisant des entrées utilisateur non nettoyées.
Le scénario : le piège Backend-for-Frontend (BFF)
Imaginez un BFF qui prend la préférence de tri d’un utilisateur et l’injecte dans une requête GraphQL backend :
const query = `query { products(sort: "${userInput}") { id name } }`;
Un attaquant n’envoie pas seulement une chaîne de tri ; il envoie :
"price") @include(if: true) @customDirective(arg: "malicious") #
La requête résultante devient :
query { products(sort: "price") @include(if: true) @customDirective(arg: "malicious") #") { id name } }
En “échappant” l’argument et en injectant ses propres directives, un attaquant peut :
- Contourner la logique : Injecter
@skip(if: true)pour masquer des champs critiques dans les logs tout en exécutant des mutations. - Déclencher des directives internes : Si le backend possède des directives internes (comme
@internalDebugou@bypassCache), l’attaquant peut maintenant les invoquer.
Vulnérabilité 3 : Fragments imbriqués e0 l’évasion de l’ensemble de sélection
Les directives sont souvent utilisées pour gérer le masquage de données ou la protection PII (Informations Personnelles Identifiables). Cependant, la prise en charge par GraphQL de fragments imbriqués peut créer une “mismatch de masquage”.
Si une directive de sécurité est appliquée à un niveau élevé mais que l’attaquant utilise un fragment profondément imbriqué pour référencer le même champ via un chemin relationnel différent, la directive de haut niveau peut ne pas être héritée.
L’exploitation du “Référencement Circulaire”
Dans de nombreux schémas, vous pouvez atteindre un Utilisateur depuis un Post, et un Post depuis un Utilisateur.
Si @auth est uniquement appliqué au champ User.email dans le type User, mais que le développeur a oublié de l’appliquer au type Author (qui retourne un objet User), un attaquant peut prendre le long chemin :
query DeepEvasion {
post(id: "1") {
author { # Si le résolveur 'author' ne déclenche pas la logique de directive 'User'
email
}
}
}
Vulnérabilité 4 : Manipulation du cache e0 l’épuisement des ressources 🌀
La directive @cache est un héros de la performance, mais elle peut devenir une arme de déni de service (DoS). Les attaquants peuvent utiliser des directives pour forcer le serveur à effectuer des opérations coûteuses et non mises en cache.
Cache manqué forcé
Si votre système utilise une directive @cache(ttl: 3600), le serveur génère probablement une clé de cache basée sur le hash de la requête ou des arguments spécifiques. Un attaquant peut utiliser la surcharge de directives ou la jittering d’arguments pour garantir que chaque requête est un cache miss.
query Exhaustion($random: String) {
heavyReport(id: "123") @cache(ttl: 0) # Surcharge du TTL par défaut si autorisé
alias1: heavyReport(id: "123", dummy: $random)
alias2: heavyReport(id: "123", dummy: $random)
}
En passant une chaîne aléatoire à un argument fictif ou en injectant une directive qui invalide le cache, un attaquant force le serveur à résoudre heavyReport (une opération coûteuse) plusieurs fois.
Le multiplicateur de complexité
Si le serveur calcule la complexité de la requête, les directives se voient souvent attribuer un “coût” de 0. Cependant, une directive personnalisée comme @transformImage(size: "ultra-hd") peut être coûteuse en calcul.
L’attaque : Un attaquant envoie une requête avec des centaines de champs aliasés, chacun décoré d’une directive “faible coût” mais “forte impact”.
Si la complexité est calculée comme :
$$Complexity = \sum_{i=1}^{n} (FieldCost_i + DirectiveCost_i)$$
Et que $DirectiveCost$ est incorrectement fixé à 0, la charge réelle du serveur devient $O(n \cdot ImpactRéel)$, menant à un épuisement immédiat des ressources.
Remédiation stratégique : comment protéger votre schéma 🛡️
Sécuriser les directives nécessite de s’éloigner du middleware “superficiel” et d’adopter une sécurité “profonde” axée sur le schéma.
1. Transformation unifiée des directives
Ne jamais se fier à un middleware qui ne scanne que l’ensemble de sélection de premier niveau. Utilisez un Transformateur de Directive qui modifie la Carte des Résolveurs elle-même. En enveloppant le résolveur au niveau du schéma, la logique de sécurité suit le champ, peu importe comment le client interroge (via fragments, alias ou nesting profond).
2. Validation stricte des directives
Limiter les directives que le client est autorisé à envoyer.
- Liste blanche : Autoriser uniquement
@includeet@skipdu côté client. - Schéma uniquement : S’assurer que les directives de sécurité comme
@authsont uniquement des Directives de Schéma et ne peuvent pas être fournies ou remplacées par le client dans la chaîne de requête.
3. Cartographie de complexité pour les directives
Attribuer un coût non nul à chaque directive personnalisée. Si une directive effectue une recherche en base ou une transformation lourde, son coût doit le refléter.
Conseil de sécurité : Utiliser une bibliothèque d’analyse de coût (comme graphql-cost-analysis) qui permet de définir un multiplicateur pour les directives.
4. Requêtes persistantes (la meilleure protection)
La façon la plus efficace de prévenir l’”Injection de directive” et la “Manipulation de requête” est d’utiliser des Requêtes Persistantes. En ne permettant au serveur d’exécuter que des hachages de requêtes pré-enregistrés, vous privez l’attaquant de la capacité d’injecter des directives malveillantes ou de créer des requêtes de type “deep-nesting” pour faire du DoS.
5. Désactiver l’introspection en production
Bien que ce ne soit pas une solution pour la logique sous-jacente, désactiver l’introspection rend beaucoup plus difficile pour un attaquant de “cartographier” vos directives personnalisées et de repérer celles vulnérables.
Conclusion
Les directives GraphQL sont une démonstration de conception extensible, mais elles introduisent une couche d’abstraction où la sécurité se cache souvent — et où les attaquants aiment jouer. “Directive Deception” ne concerne pas seulement une seule faille ; c’est l’échec à réaliser que dans GraphQL, le chemin vers les données est aussi important que les données elles-mêmes.
En s’assurant que votre logique de sécurité est intégrée dans les résolveurs plutôt que superposée à la requête, et en traitant chaque directive comme une ressource potentielle, vous pouvez construire un graphe à la fois flexible et redoutable.
Related InstaTunnel pages
Continue from this article into the most relevant product guides and workflows.
Related Topics
Keep building with InstaTunnel
Read the docs for implementation details or compare plans before you ship.