Sécurité GraphQL
GraphQL offre une flexibilité puissante mais introduit des vecteurs d'attaque uniques : attaques de query complexity, abus d'introspection, contournement d'autorisation et divulgation d'informations.
Vulnérabilités courantes dans GraphQL
1. Attaques de Query Depth / Complexity
Les requêtes profondément imbriquées peuvent provoquer un DoS en consommant des ressources excessives :
query MaliciousQuery {
user(id: "1") {
posts {
comments {
author {
posts {
comments {
author {
posts {
# ... imbriqué à l'infini
}
}
}
}
}
}
}
}
}
2. Abus d'introspection
L'introspection activée en production expose le schéma complet, facilitant la reconnaissance :
query IntrospectionQuery {
__schema {
types {
name
fields {
name
type {
name
}
}
}
}
}
3. Autorisation défaillante
L'autorisation doit avoir lieu dans les resolvers, et pas seulement dans les requêtes de premier niveau. L'IDOR est fréquent lorsque l'autorisation n'est pas vérifiée sur les champs imbriqués.
4. Attaques par injection
GraphQL n'est pas à l'abri des SQLi ou NoSQLi si les entrées ne sont pas assainies dans les resolvers.
Protections essentielles
Limitation de Query Depth
// Exemple avec Apollo Server
import depthLimit from 'graphql-depth-limit';
const server = new ApolloServer({
typeDefs,
resolvers,
validationRules: [depthLimit(5)] // Profondeur max 5
});
Analyse de Query Complexity
import { createComplexityLimitRule } from 'graphql-validation-complexity';
const server = new ApolloServer({
validationRules: [
createComplexityLimitRule(1000, {
scalarCost: 1,
objectCost: 5,
listFactor: 10
})
]
});
Rate Limiting
- Basé sur les requêtes : Limiter les requêtes par minute et par utilisateur
- Basé sur la complexity : Budget de complexity points
- Analyse de coût : Attribuer un coût à chaque champ
Autorisation dans GraphQL
Autorisation au niveau des champs
const resolvers = {
Query: {
user: async (parent, { id }, context) => {
// Autorisation au niveau de la query
if (!context.user) {
throw new AuthenticationError('Not authenticated');
}
return getUserById(id);
}
},
User: {
email: (user, args, context) => {
// Autorisation au niveau du champ
if (context.user.id !== user.id && !context.user.isAdmin) {
return null; // Masquer l'email aux autres utilisateurs
}
return user.email;
},
ssn: (user, args, context) => {
// Champ extrêmement sensible
if (!context.user.isAdmin) {
throw new ForbiddenError('Admin only');
}
return user.ssn;
}
}
};
Autorisation basée sur les directives
type User @auth(requires: AUTHENTICATED) {
id: ID!
username: String!
email: String! @auth(requires: OWNER_OR_ADMIN)
ssn: String! @auth(requires: ADMIN)
}
Protection de l'introspection
const server = new ApolloServer({
typeDefs,
resolvers,
introspection: process.env.NODE_ENV !== 'production',
// Ou contrôler par rôle
plugins: [{
requestDidStart() {
return {
didResolveOperation({ request, context }) {
if (request.operationName === 'IntrospectionQuery'
&& !context.user?.isAdmin) {
throw new ForbiddenError('Introspection disabled');
}
}
}
}
}]
});
Validation des entrées
- Validation de schéma : GraphQL valide les types automatiquement
- Custom scalars : Email, URL, DateTime avec validation
- Assainissement des entrées : Assainir les entrées avant de les utiliser dans les resolvers
- Requêtes paramétrées : Utiliser des prepared statements dans la base de données
Batching et DataLoader
Prévenir le problème N+1, qui peut être exploité pour un DoS :
import DataLoader from 'dataloader';
const userLoader = new DataLoader(async (userIds) => {
const users = await getUsersByIds(userIds);
return userIds.map(id => users.find(u => u.id === id));
});
const resolvers = {
Post: {
author: (post, args, { userLoader }) => {
return userLoader.load(post.authorId); // En lot !
}
}
};
Surveillance et journalisation
- Journalisation des requêtes : Journaliser toutes les requêtes avec leurs metadata (utilisateur, IP, heure)
- Suivi des erreurs : Sentry, Datadog pour les exceptions
- Surveillance des performances : Apollo Studio, GraphQL Hive
- Détection d'anomalies : Alerter sur les requêtes suspectes (trop complexes, etc.)
Outils de sécurité
- GraphQL Armor : Suite de middleware de sécurité
- graphql-shield : Couche de permissions avec règles
- InQL : Extension Burp Suite pour le pentest GraphQL
- BatchQL : Outil de security testing pour GraphQL
- graphql-cop : Outil d'audit de sécurité
OWASP GraphQL Top 10
- Broken Object Level Authorization
- Broken Authentication
- Excessive Data Exposure
- Resource Exhaustion
- Broken Function Level Authorization
- Mass Assignment
- Security Misconfiguration
- Injection
- Improper Assets Management
- Insufficient Logging & Monitoring
Pratiques de développement sécurisé
- Implémenter l'autorisation dans TOUS les resolvers
- Désactiver l'introspection en production
- Limiter la query depth et la complexity
- Rate limiting agressif
- Utiliser DataLoader pour prévenir le N+1
- Assainir les entrées avant traitement
- Mettre en place une journalisation exhaustive
- Audits de sécurité et pentests réguliers
Recommandations finales
GraphQL exige un changement de mentalité en matière de sécurité par rapport à REST. La flexibilité du client requiert des défenses robustes côté serveur. Implémentez le depth limiting, l'analyse de complexity et l'autorisation au niveau des champs dès le départ. Utilisez des outils de surveillance pour détecter les schémas d'abus. Testez régulièrement avec des outils spécialisés comme InQL et BatchQL.
