GraphQL Injection
GraphQL Injection is a critical security vulnerability that affects GraphQL APIs. Attackers exploit poorly designed queries, exposed introspection schemas, and unvalidated user input to execute unauthorized operations, extract sensitive data, or perform denial-of-service attacks through deeply nested or batched queries.
What Is GraphQL Injection?
GraphQL Injection is a vulnerability that arises when GraphQL APIs are misconfigured or fail to properly validate and sanitize user input. Unlike traditional SQL injection which targets database queries, GraphQL injection exploits the flexible nature of GraphQL's query language itself. Attackers can craft malicious GraphQL queries to bypass authorization checks, discover hidden schema fields through introspection, or overload servers with excessively complex queries.
The vulnerability often manifests in several forms: introspection abuse that reveals the entire API schema including internal types and hidden fields, nested query attacks that create exponentially expensive operations causing denial of service, batching attacks that allow brute-forcing operations, and traditional injection when GraphQL variables are improperly passed to underlying databases without parameterization. Because GraphQL allows clients to request exactly the data they need, it also gives attackers a powerful tool to probe and exploit the API's structure.
GraphQL Injection has become increasingly prevalent as more organizations adopt GraphQL for their APIs. The OWASP API Security Top 10 includes GraphQL-specific risks under API8 (Security Misconfiguration), recognizing that the flexibility and power of GraphQL requires careful security implementation. Many production GraphQL APIs expose introspection in production, lack query depth limiting, and fail to implement proper rate limiting or cost analysis, making them vulnerable to exploitation.
How It Works
The attacker identifies a GraphQL API endpoint, typically at /graphql or /api/graphql. They send an introspection query to discover the complete schema, including all types, fields, mutations, and their relationships. Many production APIs leave introspection enabled, revealing the entire API structure.
Using the introspection results, the attacker maps out all available queries and mutations, identifies fields that might contain sensitive data, discovers hidden or internal-only fields, and analyzes relationships between types to plan nested query attacks.
The attacker creates queries designed to exploit the API. This could be a deeply nested query like user { posts { author { posts { author { posts ... }}}}} repeated hundreds of levels deep, causing exponential database queries and server resource exhaustion.
If the API passes GraphQL variables directly into database queries without proper parameterization, the attacker injects SQL or NoSQL payloads through variables. For example, passing {"id": "1' OR '1'='1"} to a query that builds SQL strings directly from the variable.
The GraphQL server executes the malicious query. Depending on the attack type, this results in unauthorized data access, extraction of sensitive information through batched queries, denial of service from resource exhaustion, or successful SQL/NoSQL injection if variables are mishandled.
Vulnerable Code Example
@Component
public class UserResolver implements GraphQLQueryResolver {
@Autowired
private JdbcTemplate jdbcTemplate;
// VULNERABLE: No introspection disabled, no depth limits
// Direct SQL construction from GraphQL variables
public User getUserById(String id) {
// VULNERABLE: Direct string concatenation - SQL injection risk
String query = "SELECT * FROM users WHERE id = '" + id + "'";
return jdbcTemplate.queryForObject(query, new UserRowMapper());
}
// VULNERABLE: No complexity analysis, allows deeply nested queries
public List<Post> getPostsByUser(String userId) {
String query = "SELECT * FROM posts WHERE user_id = '" + userId + "'";
return jdbcTemplate.query(query, new PostRowMapper());
}
}
// GraphQL Schema allows unlimited nesting
// type Query {
// getUserById(id: String!): User
// }
// type User {
// id: ID!
// username: String
// posts: [Post]
// }
// type Post {
// id: ID!
// author: User # Circular reference - no depth limit!
// }
// Attacker can send:
// 1. Injection: getUserById(id: "1' OR '1'='1' --")
// 2. DoS: Deeply nested query with 100+ levels
// 3. Introspection: __schema query reveals entire APISecure Code Example
@Configuration
public class GraphQLConfig {
@Bean
public GraphQLServlet graphQLServlet() {
return GraphQLServlet.builder()
// SECURE: Disable introspection in production
.with(GraphQLConfiguration.with(schema)
.disableIntrospection(true)
.build())
.build();
}
@Bean
public MaxQueryDepthInstrumentation maxDepthInstrumentation() {
// SECURE: Limit query depth to prevent nested attacks
return new MaxQueryDepthInstrumentation(7);
}
@Bean
public MaxQueryComplexityInstrumentation complexityInstrumentation() {
// SECURE: Limit query complexity/cost
return new MaxQueryComplexityInstrumentation(200);
}
}
@Component
public class UserResolver implements GraphQLQueryResolver {
@Autowired
private JdbcTemplate jdbcTemplate;
public User getUserById(String id) {
// SECURE: Use parameterized queries
String query = "SELECT * FROM users WHERE id = ?";
return jdbcTemplate.queryForObject(query,
new Object[]{id}, new UserRowMapper());
}
public List<Post> getPostsByUser(String userId) {
// SECURE: Parameterized query prevents injection
String query = "SELECT * FROM posts WHERE user_id = ?";
return jdbcTemplate.query(query,
new Object[]{userId}, new PostRowMapper());
}
}
// With these protections:
// - Introspection disabled: attackers cannot discover schema
// - Depth limiting: nested queries fail after 7 levels
// - Complexity analysis: expensive queries are rejected
// - Parameterization: SQL injection is preventedTypes of GraphQL Injection
Introspection Abuse
GraphQL's introspection feature allows clients to query the schema itself using __schema and __type queries. While useful for development, leaving it enabled in production exposes the complete API structure to attackers. They can discover hidden fields, internal-only types, deprecated endpoints, and the relationships between all data types. This reconnaissance gives attackers a complete map of the API, revealing fields like isAdmin, internalNotes, or socialSecurityNumber that were never intended to be discoverable.
Nested Query / Batching Attacks
GraphQL allows circular type references and query batching, which attackers exploit for denial-of-service attacks. A deeply nested query like user { posts { author { posts { author ... }}}} repeated 50+ levels can cause exponential database queries, overwhelming the server. Query batching allows sending multiple operations in one request, enabling brute-force attacks where an attacker sends 10,000 login attempts in a single HTTP request. Without depth limits, complexity analysis, or rate limiting, these attacks can bring down production APIs.
Variable Injection
When GraphQL resolvers pass user-supplied variables directly into database queries without parameterization, traditional SQL or NoSQL injection becomes possible. An attacker sends a GraphQL query with variables like {"userId": "1' OR '1'='1' --"} or {"email": {"$ne": null}} for MongoDB. If the resolver concatenates these variables into query strings instead of using prepared statements, the injection payload executes in the database. This combines GraphQL's flexibility with classic injection techniques, allowing data exfiltration, authentication bypass, or data manipulation.
Impact
A successful GraphQL Injection attack can have severe consequences for an organization. The impact varies based on the attack type, API permissions, and the sensitivity of exposed data.
Enabled introspection reveals the entire API structure including hidden fields, internal types, administrative mutations, and data relationships. Attackers gain a complete blueprint of the API, discovering fields like password reset tokens, internal user roles, or administrative endpoints that were meant to be hidden.
Deeply nested queries or batched operations cause exponential database queries and memory consumption. A single malicious query with 50 levels of nesting can trigger millions of database operations, crashing the server or degrading performance for all users. This is especially damaging because it bypasses traditional rate limiting based on request count.
Through introspection, attackers discover fields they shouldn't access. By crafting queries that exploit missing authorization checks, they extract sensitive data across multiple resources in a single request. Batched queries allow extracting thousands of records, and GraphQL's ability to fetch related data means one query can pull user details, payment information, and private messages simultaneously.
When resolvers fail to use parameterized queries, GraphQL variables become injection vectors. Attackers bypass authentication, modify data through injected UPDATE or DELETE statements, or escalate privileges. Combined with GraphQL's batching, this allows automated exploitation at scale, potentially compromising entire databases.
Prevention Checklist
Turn off GraphQL introspection queries in production environments. Introspection should only be available in development. This prevents attackers from discovering your entire API schema, hidden fields, and internal types. Most GraphQL servers provide a configuration option to disable introspection with a single flag.
Set maximum query depth limits (typically 5-10 levels) to prevent deeply nested queries. Implement query complexity analysis that assigns costs to fields and rejects queries exceeding a threshold. Libraries like graphql-depth-limit and graphql-query-complexity make this straightforward. This protects against DoS attacks from exponentially expensive queries.
Never concatenate GraphQL variables directly into SQL or NoSQL queries. Always use parameterized queries or prepared statements. Treat GraphQL variables the same way you would treat any user input in a traditional API. Use ORM methods or database libraries that handle parameterization automatically.
Don't rely solely on hiding fields from the schema. Implement authorization checks at the resolver level for every field that contains sensitive data. Use middleware or directives like @auth or @hasRole to enforce permissions. Attackers may discover hidden fields through other means, so every field must validate access independently.
Implement rate limiting based on query complexity points, not just request count. A single batched query with 1,000 operations should consume more quota than a simple query. Use tools that calculate the cost of each query based on field complexity and return data volume. Set per-user and per-IP limits to prevent abuse.
For highly sensitive APIs, implement persistent queries where only pre-approved query hashes are accepted. This prevents arbitrary queries entirely. At minimum, validate all queries against your schema and reject malformed or suspicious queries. Log unusual query patterns for security monitoring and use automated tools to test for GraphQL vulnerabilities in CI/CD.
Real-World Examples
GitLab GraphQL Exposure
GitLab's GraphQL API inadvertently exposed sensitive repository information through its public API. Security researchers discovered that introspection was enabled, revealing internal fields and relationships. Improperly scoped queries allowed unauthorized access to private repository metadata. GitLab quickly disabled introspection in production and implemented stricter authorization checks on GraphQL resolvers.
Shopify GraphQL Vulnerabilities
Security researchers discovered multiple GraphQL security issues in Shopify's API, including the ability to craft deeply nested queries that caused significant server load. One vulnerability allowed batching thousands of queries to enumerate valid shop domains and extract business information at scale. Shopify implemented query complexity analysis and depth limiting across their GraphQL infrastructure.
Facebook GraphQL Schema Leaks
Facebook's internal GraphQL schemas were inadvertently exposed through debugging endpoints left active in production. While not a direct attack, the exposure revealed internal API structures, field naming conventions, and data relationships that could aid attackers in targeting specific vulnerabilities. This highlighted the importance of disabling development tools and introspection in production environments.
HackerOne GraphQL DoS Campaigns
Multiple bug bounty reports on HackerOne documented GraphQL denial-of-service vulnerabilities across numerous organizations. Researchers demonstrated that APIs lacking query depth and complexity limits could be taken offline with single HTTP requests containing deeply nested circular queries. The most severe case required only 3-4 nested levels to cause database timeouts due to exponential join operations.
Ready to Test Your Knowledge?
Put what you have learned into practice. Try identifying and fixing GraphQL Injection vulnerabilities in our interactive coding challenges, or explore more security guides to deepen your understanding.