Skip to content

Services, Wiki-Artikel, Blog-Beiträge und Glossar-Einträge durchsuchen

↑↓NavigierenEnterÖffnenESCSchließen
Schwachstellenklassen Glossary

GraphQL Security - Angriffe auf GraphQL-APIs

GraphQL APIs pose specific security risks: introspection exposes the entire API structure, deeply nested queries can cause denial-of-service attacks (query depth attacks), batching allows rate limits to be bypassed, and a lack of field-level authorization leads to data leaks. Protection: Disable introspection in production, set query depth limits, implement field-level authorization, and enforce query complexity limits.

GraphQL is a query language for APIs that allows clients to request exactly the data they need. Compared to REST APIs, GraphQL offers more flexibility—but also a larger attack surface. The most significant GraphQL security risks arise from introspection (API self-disclosure), query complexity (DoS), batching, and the lack of field-level authorization.

GraphQL Basics and Attack Surface

The key security difference between REST and GraphQL lies in their structure: REST APIs have fixed endpoints with clearly defined data structures and clear authorization per endpoint. GraphQL, on the other hand, has only a single endpoint through which any combination of data can be queried.

This means that authorization in GraphQL must occur at the level of each individual field. A single insecure field can expose all data accessible through it.

The specific attack surface of GraphQL encompasses the following areas:

  • Introspection: complete schema information upon request
  • Query Depth: arbitrarily deep nesting of queries
  • Query Complexity: arbitrarily broad queries with catastrophic performance
  • Batching: multiple operations in a single request
  • Field-Level Authorization: lack of per-field permission checks
  • SSRF via URL fields: server-side requests from GraphQL resolvers
  • Injection in resolver arguments: SQL injection, NoSQL injection, etc.

Introspection - Schema Reconnaissance

GraphQL offers a built-in introspection feature that returns the complete database schema on request—including all types, fields, mutations, and subscriptions:

{ __schema { types { name fields { name type { name } } } } }

A typical response reveals not only the standard data structures but also sensitive fields such as passwordHash and internalNotes, as well as undocumented admin areas with mutations like deleteUser and resetAllPasswords.

Tools like GraphQL Voyager or InQL (Burp Suite extension) visualize the schema as an interactive graph and show the attacker the complete internal data structure—including forgotten or undocumented admin fields.

Additional reconnaissance queries:

# List all queries:
{ __schema { queryType { fields { name description } } } }

# All mutations:
{ __schema { mutationType { fields { name args { name } } } } }

DoS Attacks: Depth and Complexity

Query Depth Attack (recursive queries)

If a schema contains recursive types (e.g., User has friends: [User]), an attacker can create arbitrarily deep nesting:

{
  user(id: 1) {
    friends {
      friends {
        friends {
          friends {
            friends {
              friends { id }
            }
          }
        }
      }
    }
  }
}

10 levels of nesting can result in millions of database queries and cause the server to crash due to memory exhaustion or timeouts.

Query Complexity Attack (Broad Queries)

Even without recursion, valid queries can cause catastrophic performance issues by retrieving all users along with all posts, all comments, and their respective authors in a single request.

Query Batching (Rate Limit Bypass)

[
  {"query": "mutation { login(email: \"a@b.com\", password: \"pass1\") }"},
  {"query": "mutation { login(email: \"a@b.com\", password: \"pass2\") }"},
  ...1000 more...
]

1,000 login attempts result in a single HTTP request. Rate limiting at the HTTP level fails completely—brute-force protection must be implemented at the GraphQL operation level.

Authorization Vulnerabilities

Field-Level Authorization Bypass

The most common mistake: Authorization is only checked at the query level, not at the level of individual fields.

type User {
  id: ID
  name: String
  email: String
  passwordHash: String    # NO separate auth check!
  internalNotes: String   # All logged-in users can read this?!
  creditCardLast4: String
}

A logged-in user with low permissions can then query:

{ user(id: 999) { passwordHash creditCardLast4 internalNotes } }

The server returns all fields without throwing an error—and doesn’t even check whether user(id: 999) is their own account.

Horizontal IDOR via GraphQL

If a resolver only checks whether a user is logged in, but not whether the requested data belongs to the user, horizontal IDOR attacks are possible:

{ user(id: 2) { email privateMessages { content } } }

Mutation IDOR

mutation { deletePost(id: 5) }

This call deletes Post 5—but only if the resolver checks whether Post 5 belongs to the logged-in user. If this check is missing, any user can delete any posts.

Detection in Penetration Testing

Specialized tools are available for GraphQL security testing:

ToolPurpose
InQL (Burp Suite Extension)Automatic schema extraction and testing
GraphQL VoyagerSchema visualization as an interactive graph
graphw00fGraphQL engine fingerprinting
clairvoyanceSchema extraction even without introspection

Testing Methodology

1. Engine Fingerprinting: graphw00f identifies the GraphQL engine in use (Apollo, Hasura, GraphQL-Java, Strawberry, etc.)—then check for known CVEs for the identified engine.

python3 main.py -f -t https://target.com/graphql

2. Test introspection:

{ __schema { types { name } } }

If a response is present, introspection is active.

3. Schema extraction without introspection:

python3 main.py -u https://target.com/graphql -w wordlist.txt

clairvoyance sends many queries and infers the schema from error messages—this also works when introspection is disabled.

4. Depth/Complexity Test: Send a 10-level nested query and measure the response time.

5. Batching Test: Send 100 login attempts in a single request and check if all are processed.

6. Authorization Testing:

  • List all sensitive fields (passwordHash, token, credit, admin)
  • Query as a normal user: are they returned?
  • Test user(id: OTHER_USER_ID): IDOR vulnerability?
  • Call admin mutations without admin privileges

Mitigation Measures

1. Disable introspection in production

// Apollo Server:
const server = new ApolloServer({
  typeDefs,
  resolvers,
  introspection: process.env.NODE_ENV !== 'production',  // DEV only!
  plugins: [process.env.NODE_ENV === 'production' && ApolloServerPluginLandingPageDisabled()]
});
// graphql-java:
GraphQL.newGraphQL(schema)
  .instrumentation(new MaxQueryDepthInstrumentation(10))
  .build();

2. Query Depth Limiting

// Node.js (graphql-depth-limit):
import depthLimit from 'graphql-depth-limit';
const server = new ApolloServer({
  validationRules: [depthLimit(7)]  // Max 7 levels
});

3. Query Complexity Limiting

// Node.js (graphql-query-complexity):
const complexity = getComplexity({
  schema,
  query: document,
  variables,
  estimators: [simpleEstimator({ defaultComplexity: 1 })],
  maximumComplexity: 1000  // Max 1000 complexity points
});
if (complexity > 1000) throw new Error('Query too complex');

4. Limit or Disable Batching

// Use batching only when absolutely necessary!
// Set small payload limits:
app.use('/graphql', bodyParser.json({ limit: '10kb' }));

5. Field-Level Authorization for every sensitive field

// GraphQL Shield (Node.js):
const permissions = shield({
  Query: {
    user: isAuthenticated,          // Auth at query level
  },
  User: {
    email: isOwnerOrAdmin,          // Auth at field level!
    passwordHash: deny,             // Never accessible!
    internalNotes: isAdmin,         // Admins only!
  }
});

The basic principle: every field has its own auth rule. By default, everything is prohibited; access must be explicitly granted.

6. Rate Limiting at the Operation Level

Rate limiting must not only be applied at the HTTP level but must also be implemented for each user and each operation type. The login mutation, for example, should be limited to a maximum of 5 attempts per minute. Batching must also be limited to a maximum number of operations per request.