GraphQL Vulnerabilities
GraphQL Vulnerabilities at a glance
Overview
GraphQL is a powerful query language for APIs that allows clients to request exactly the data they need. However, this flexibility introduces unique security challenges. Unlike REST APIs with fixed endpoints, GraphQL's single endpoint and flexible queries create new attack surfaces.
Common GraphQL vulnerabilities include deeply nested queries causing denial of service, missing authorization at the field level allowing unauthorized data access, introspection queries revealing the entire API schema, batching attacks amplifying request impact, and injection vulnerabilities in resolvers. The recursive nature of GraphQL queries means a single malicious query can trigger thousands of database calls or expose vast amounts of data.
Where it occurs
GraphQL vulnerabilities occur in APIs lacking query depth or complexity limits, field-level authorization, or input validation, and in resolvers vulnerable to injection or excessive data fetching.
Impact
GraphQL vulnerabilities lead to denial of service through query complexity attacks, excessive data exposure bypassing intended access controls, complete API schema disclosure through introspection, database overload from N+1 queries, injection attacks in resolvers, and information disclosure through error messages. A single malicious query can bring down an entire service or exfiltrate sensitive data.
Prevention
Prevent GraphQL abuse by enforcing depth and complexity limits, validating resolver inputs, using field-level auth, disabling introspection, allowlisting or persisting queries, parameterizing DB access, limiting batches, and logging query activity.
Examples
Switch tabs to view language/framework variants.
GraphQL API allows deeply nested queries causing DoS
No query depth limits enable resource exhaustion.
const { ApolloServer } = require('apollo-server');
const typeDefs = `
type User {
id: ID!
posts: [Post!]!
}
type Post {
author: User!
}
`;
const server = new ApolloServer({
typeDefs,
resolvers
// BUG: No depth limit or complexity analysis
});- Line 16: No protection against deep queries
Deep nested queries can cause exponential database queries and resource exhaustion.
const { ApolloServer } = require('apollo-server');
const { createComplexityLimitRule } = require('graphql-validation-complexity');
const server = new ApolloServer({
typeDefs,
resolvers,
validationRules: [
createComplexityLimitRule(1000, {
onCost: (cost) => console.log('Query cost:', cost)
})
],
plugins: [
{
requestDidStart() {
return {
didResolveOperation({ request }) {
const depth = getQueryDepth(request.query);
if (depth > 5) {
throw new Error('Query too deep');
}
}
};
}
}
]
});- Line 7: Complexity limit rule
- Line 17: Depth checking
Implement both complexity analysis and depth limits to prevent DoS.
Engineer Checklist
-
Implement query depth limits (max 5-7 levels)
-
Calculate and enforce query complexity scores
-
Add field-level authorization in resolvers
-
Disable introspection in production
-
Implement DataLoader to prevent N+1 queries
-
Limit batch operation sizes
-
Validate all resolver inputs
-
Use parameterized queries in resolvers
-
Implement query cost analysis
-
Add rate limiting on the GraphQL endpoint
-
Avoid exposing stack traces in errors
-
Log all GraphQL queries
-
Use persisted/allowed queries in production
-
Implement timeout limits on resolver execution
-
Test with complex nested queries
End-to-End Example
A GraphQL API has no query depth limits, allowing attackers to craft deeply nested queries that cause exponential database calls and service outage.
// Vulnerable: No query limits
const { ApolloServer, gql } = require('apollo-server');
const typeDefs = gql`
type User {
id: ID!
name: String
posts: [Post]
}
type Post {
id: ID!
title: String
author: User
}
type Query {
user(id: ID!): User
}
`;
const resolvers = {
Query: {
user: (_, { id }) => getUserById(id) // No authorization
},
User: {
posts: (user) => getPostsByUser(user.id) // N+1 problem
},
Post: {
author: (post) => getUserById(post.authorId) // Allows deep nesting
}
};
const server = new ApolloServer({ typeDefs, resolvers });// Secure: Query limits and authorization
const { ApolloServer, gql } = require('apollo-server');
const depthLimit = require('graphql-depth-limit');
const { createComplexityLimitRule } = require('graphql-validation-complexity');
const DataLoader = require('dataloader');
const typeDefs = gql`
type User {
id: ID!
name: String
posts: [Post]
}
type Post {
id: ID!
title: String
author: User
}
type Query {
user(id: ID!): User
}
`;
// DataLoader to prevent N+1
const userLoader = new DataLoader(async (ids) => {
return await getUsersByIds(ids);
});
const resolvers = {
Query: {
user: async (_, { id }, context) => {
// Field-level authorization
if (!context.user) {
throw new Error('Unauthorized');
}
return userLoader.load(id);
}
},
User: {
posts: async (user, _, context) => {
// Check authorization for user's posts
if (context.user.id !== user.id && !context.user.isAdmin) {
throw new Error('Unauthorized');
}
return getPostsByUser(user.id);
}
},
Post: {
author: (post) => userLoader.load(post.authorId)
}
};
const server = new ApolloServer({
typeDefs,
resolvers,
validationRules: [
depthLimit(5), // Max 5 levels deep
createComplexityLimitRule(1000, { // Max complexity score
scalarCost: 1,
objectCost: 10,
listFactor: 10
})
],
introspection: process.env.NODE_ENV !== 'production', // Disable in prod
context: ({ req }) => ({
user: authenticateUser(req)
}),
formatError: (error) => {
// Don't expose internal errors
if (error.message.startsWith('Database')) {
return new Error('Internal server error');
}
return error;
}
});Discovery
Test with introspection queries to map the schema. Craft deeply nested queries and observe performance degradation or errors.
-
1. Test GraphQL introspection
httpAction
Query GraphQL schema using introspection
Request
POST https://api.example.com/graphqlBody:"{__schema{types{name,fields{name}}}}"Response
Status: 200Body:{ "note": "Complete API schema revealed including all types and fields" }Artifacts
api_schema field_list type_definitions -
2. Test query depth limits
httpAction
Send deeply nested query to test depth restrictions
Request
POST https://api.example.com/graphqlBody:"{user{posts{author{posts{author{posts{author{posts{author{name}}}}}}}}}}"Response
Status: 200Body:{ "note": "Deep query executes, causing thousands of database queries" }Artifacts
n_plus_one_queries performance_degradation -
3. Test field-level authorization
httpAction
Request unauthorized fields to test access controls
Request
POST https://api.example.com/graphqlBody:"{users{email,password,ssn}}"Response
Status: 200Body:{ "note": "Sensitive fields accessible without proper authorization" }Artifacts
authorization_bypass sensitive_data
Exploit steps
Attacker uses introspection to discover the schema, then crafts deeply nested or complex queries to cause DoS or extract excessive data.
-
1. Map complete API via introspection
Schema discovery
httpAction
Extract complete GraphQL schema and identify attack surface
Request
POST https://api.example.com/graphqlBody:"{__schema{queryType{fields{name,args{name,type{name}}}}}}"Response
Status: 200Body:{ "note": "Full API structure revealed including hidden queries" }Artifacts
complete_schema query_list hidden_endpoints -
2. Execute query depth DoS attack
Resource exhaustion via nesting
httpAction
Send maximally nested query to exhaust server resources
Request
POST https://api.example.com/graphqlBody:"{user{posts{comments{author{posts{comments{author{posts{...}}}}}}}}"Response
Status: 200Body:{ "note": "Service crashes or becomes unresponsive" }Artifacts
service_outage cpu_exhaustion database_overload -
3. Extract unauthorized data via field injection
Mass data exfiltration
httpAction
Query all users with sensitive fields lacking authorization
Request
POST https://api.example.com/graphqlBody:"{users{id,email,passwordHash,ssn,creditCard}}"Response
Status: 200Body:{ "note": "All user PII and credentials extracted" }Artifacts
user_database password_hashes pii
Specific Impact
Service outage from query complexity DoS, unauthorized data access through missing field-level authorization, and complete API schema disclosure.
Fix
Implement query depth limits to prevent deeply nested queries. Use complexity analysis to limit expensive queries. Add field-level authorization in resolvers. Use DataLoader to prevent N+1 queries. Disable introspection in production. Sanitize error messages to avoid information disclosure.
Detect This Vulnerability in Your Code
Sourcery automatically identifies graphql vulnerabilities vulnerabilities and many other security issues in your codebase.
Scan Your Code for Free