Security
14 min read
105 views

GraphQL Batching Attacks: How 100 Queries Become 10,000 Database Calls 📊

IT
InstaTunnel Team
Published by our engineering team
GraphQL Batching Attacks: How 100 Queries Become 10,000 Database Calls 📊

GraphQL Batching Attacks: How 100 Queries Become 10,000 Database Calls 📊

Introduction: The Hidden Danger in GraphQL’s Most Convenient Feature

GraphQL has revolutionized how modern applications query data, offering unprecedented flexibility and efficiency. However, this power comes with a significant security vulnerability that many developers overlook: batching attacks. What appears as a single, innocent HTTP request can silently transform into thousands of database operations, potentially bringing your entire infrastructure to its knees.

In this comprehensive guide, we’ll explore how attackers exploit GraphQL’s batching feature to amplify attacks exponentially, why allowing array inputs can turn into a resource exhaustion nightmare, and most importantly, how to protect your GraphQL API from these devastating attacks.

Understanding GraphQL Batching: A Double-Edged Sword

What is GraphQL Batching?

GraphQL batching is a documented feature described in the GraphQL specification published in June 2018 in section 6.3.1, which allows multiple queries to be sent with a single GraphQL request. This technique was designed to solve a common problem: reducing network overhead by consolidating multiple data requests into a single HTTP call.

Only one batch transits on the network and then all queries are executed sequentially. While this improves performance for legitimate users, it creates a massive attack surface for malicious actors.

Two Types of Batching Attacks

GraphQL supports two primary batching methods, each with unique security implications:

1. Array-Based Batching

Array-based batching allows attackers to send multiple operations together in a single HTTP request, though it has one major disadvantage - arrays aren’t available everywhere. When supported, attackers can structure requests like this:

[
  {"query": "mutation { login(username: \"user1\", password: \"pass1\") { token } }"},
  {"query": "mutation { login(username: \"user2\", password: \"pass2\") { token } }"},
  {"query": "mutation { login(username: \"user3\", password: \"pass3\") { token } }"}
]

2. Alias-Based Batching

Aliases are great because they are part of the GraphQL specification as shown in section 2.5, so when you encounter a GraphQL API, you should expect to have aliases available. However, aliases have one disadvantage - they can only be applied to either queries, or mutations, but not both at the same time in a single HTTP request.

Attackers can leverage aliases to achieve a form of batching where a single request contains dozens or even hundreds of aliased queries that execute sequentially.

The Mathematics of Attack Amplification

From 1 Request to 10,000 Database Calls

The exponential nature of batching attacks is what makes them so dangerous. Consider this scenario:

Traditional Attack (Without Batching): - 1 HTTP request = 1 database query - To test 10,000 passwords = 10,000 HTTP requests - Easily detected by rate limiters and WAFs

Batching Attack: - 1 HTTP request = 100-1,000 database queries - To test 10,000 passwords = 10-100 HTTP requests - Completely invisible to traditional security tools

It is an easy way to bypass attempt and rate limits. Indeed, limits are usually set up on the number of API calls, but what if a single API call can create 10,000 database requests? Tools responsible for securing web applications like firewalls and rate limiters cannot detect abnormal activities: they only monitor API calls.

Real-World Impact: The Complexity Explosion

With one network call translating into numerous queries or object requests, the application’s communication with the backend database could lead to CPU or memory exhaustion, potentially rendering it inaccessible to genuine users.

The problem becomes even more severe with nested queries. Consider a social media platform where users belong to groups, and groups contain users. An attacker could craft a query like:

query {
  group(id: "123") {
    users(limit: 100) {
      groups(limit: 100) {
        users(limit: 100) {
          groups(limit: 100) {
            name
          }
        }
      }
    }
  }
}

In just four nesting levels with a limit of 100 at each level, this single query could attempt to fetch 100 × 100 × 100 × 100 = 100 million results, causing catastrophic database load.

Attack Vectors: How Batching Attacks Manifest

1. Brute Force Authentication Bypass

By combining multiple operations into a single request, attackers might organize batching attacks and try to bypass security measures such as rate limiting.

An attacker might attempt a brute force attack, via GraphQL batching feature to send multiple queries in one HTTP request, to guess the password, and by receiving a token, permitting them full access to the API as an authenticated user.

Attack Example:

[
  {"query": "mutation { login(email: \"victim@example.com\", password: \"password123\") }"},
  {"query": "mutation { login(email: \"victim@example.com\", password: \"123456\") }"},
  {"query": "mutation { login(email: \"victim@example.com\", password: \"qwerty\") }"},
  // ... 997 more attempts
]

A single request containing 1,000 login attempts appears as just one HTTP call to monitoring systems, completely evading rate limits.

2. Two-Factor Authentication (2FA) Bypass

Perhaps the most alarming vulnerability is the ability to bypass two-factor authentication. Using the GraphQL batching attack, it’s possible to completely bypass one of the common second authentication factors, OTP (One Time Password), by sending all the token variants in a single request.

Since OTP codes typically have only 1,000,000 possible combinations (for 6-digit codes), an attacker can: 1. Trigger OTP generation 2. Send a batched request with all possible codes 3. Process all attempts simultaneously 4. Successfully authenticate when the correct code is found

The vulnerable GraphQL web application processed all the “one-time” tokens at the same time, found a valid one, and logged the attacker inside.

3. Denial of Service (DoS) Attacks

GraphQL supports query batching, which takes a series of queries and sends them in one shot. This means that one HTTP request contains several GraphQL queries, while the process in the backend is one after the other. This processing may lead to application-level DoS since a single network call is translated to a high number of queries or object requests.

Insights from 13,000 GraphQL API issues reveal that 80% of issues could have been resolved by implementing best practices such as access control with authorization and authentication, input validation, and rate limiting to block brute-force attacks.

4. Data Enumeration and Scraping

Batching attacks introduce additional attack vectors, such as brute-force and object enumeration attacks that target user information like names, emails, and accounts.

Attackers can efficiently enumerate: - User accounts - Email addresses - Resource identifiers - Private data through sequential ID guessing

During a recent engagement, an API built with GraphQL allowed for the ability to retrieve Patient Health Information (PHI) when a valid “sequence number” was provided in a GraphQL query. This sequence number was randomly generated per account; however, it was not a lengthy unique user id (UUID) and could easily be enumerated.

Why Traditional Security Measures Fail

The Rate Limiting Problem

Traditional rate limiting operates at the HTTP request level, counting the number of API calls per time period. However:

This approach would trick external rate monitoring applications into thinking all is well and there is no brute-forcing bot trying to guess passwords.

Traditional Security View:

Client IP: 192.168.1.100
Requests per minute: 5
Status: ✓ Normal traffic

Reality:

Client IP: 192.168.1.100
HTTP Requests: 5
Actual Operations: 5,000
Database Queries: 50,000
Status: ✗ Active attack

The WAF and Firewall Blind Spot

For the tools responsible for securing web applications, like WAFs and RASPs, it is challenging to identify abnormal server activity when each of the API requests can encapsulate thousands of malicious requests composing an attack.

Modern Web Application Firewalls (WAFs) analyze HTTP traffic patterns, but they typically cannot parse GraphQL query semantics deeply enough to understand the true computational cost of each request.

The Monitoring Gap

Therefore, all your common protection mechanisms will fail to detect and deny a brute-force attack. So, an attacker can simply make one API call with hundreds of attempts to find user credentials (email, passwords). They can also bypass two-factor authentication (2FA) by sending all the token variants of the one-time password (OTP).

Comprehensive Defense Strategies

1. Implement Query Complexity Analysis

The complexity is the number of fields in the query. The default complexity of each field is 1. However, you should assign custom complexity values based on computational cost:

const schema = Schema.build(Query, EmptyMutation, EmptySubscription)
  .limit_complexity(100) // Maximum complexity allowed
  .finish();

Query complexity allows you to define how complex these fields are, and to restrict queries with a maximum complexity. The idea is to define how complex each field is by using a simple number.

Implementation Example:

field :posts, [PostType], null: false do
  argument :limit, Integer, required: false, default_value: 10
  complexity ->(ctx, args, child_complexity) {
    args[:limit] * child_complexity
  }
end

2. Enforce Query Depth Limiting

By restricting the depth of queries, we can prevent overly complex and resource-intensive queries from being executed.

This library validates the total depth of the queries and mutations. A query depth limit will throw a validation error for queries or mutations with depth exceeding the specified maximum.

Implementation:

import depthLimit from 'graphql-depth-limit';

app.use('/graphql', graphqlHTTP({
  schema,
  validationRules: [depthLimit(10)]
}));

Take for example a server configured with a Maximum Query Depth of 3, and any query where elements within exceed this depth would be considered invalid.

3. Restrict Batch Operation Limits

Limiting the number of operations that can be batched and run at once is another option to mitigate GraphQL batching attacks leading to DoS. This is not a silver bullet though and should be used in conjunction with other methods.

Best Practices: - Limit array-based batching to 5-10 operations maximum - Restrict alias counts per query (e.g., maximum 20 aliases) - Implement body size limits on HTTP requests

4. Disable Batching for Sensitive Operations

Another option is to prevent batching for sensitive objects that you don’t want to be brute forced, such as usernames, emails, passwords, OTPs, session tokens, etc. This way an attacker is forced to attack the API like a REST API and make a different network call per object instance.

Critical Operations to Protect: - Authentication mutations - Password reset operations - OTP verification - Payment processing - Account modification

5. Implement Operation-Level Rate Limiting

Move beyond HTTP-level rate limiting to operation-level throttling:

const rateLimits = {
  login: { max: 5, window: '1m' },
  verifyOTP: { max: 3, window: '5m' },
  resetPassword: { max: 3, window: '1h' }
};

This ensures that even if operations are batched, the cumulative count triggers rate limits.

6. Apply Query Timeout Mechanisms

There is another approach that consists in limiting the query time: the “security timeout”. It is no longer a question of blocking requests that are too demanding before they are executed but rather of monitoring their execution time and stopping them if they take too long to execute.

Set reasonable timeouts at multiple levels: - GraphQL resolver level: 1-2 seconds per resolver - Query execution level: 5-10 seconds total - HTTP request level: 30 seconds maximum

7. Use GraphQL Security Tools

At Escape, we created GraphQL Armor, an open-source plugin for JavaScript GraphQL implementations, which brings security best practices by default to your API.

Recommended Security Tools: - GraphQL Armor: Comprehensive security plugin - graphql-depth-limit: Depth limiting library - graphql-query-complexity: Complexity analysis - Escape Security Scanner: Automated vulnerability detection

8. Implement Proper Input Validation

80% of issues could have been resolved by implementing best practices such as access control with authorization and authentication, input validation, and rate limiting to block brute-force attacks.

Validation Checklist: - Validate argument ranges (e.g., limit: 1-100) - Sanitize all user inputs - Enforce data type constraints - Reject overly long strings - Limit array sizes

9. Monitor and Log Query Patterns

Implement comprehensive logging to detect attack patterns:

class LogQueryComplexity {
  result() {
    const complexity = super();
    logger.info(`[GraphQL] Complexity: ${complexity}, Depth: ${depth}, Operations: ${operations}`);
  }
}

Alert on suspicious patterns: - Sudden spikes in query complexity - Repeated failed authentication attempts - Unusual batching patterns - Queries from known bad actors

10. Use Persisted Queries for Production

It’s highly recommended that you only accept trusted documents to your GraphQL server - like Facebook do internally.

Implement a whitelist of allowed queries: - Pre-register all legitimate queries - Reject any ad-hoc queries in production - Use query IDs instead of full query strings - Maintain strict version control

Security Configuration Best Practices

Secure Default Settings

By default, most GraphQL implementations have some insecure default configurations which should be changed: Don’t return excessive error messages.

Production Configuration Checklist:

const secureConfig = {
  // Complexity and depth
  maxComplexity: 100,
  maxDepth: 7,
  maxListDepth: 3,
  
  // Batching limits
  maxBatchSize: 10,
  maxAliases: 15,
  
  // Timeouts
  queryTimeout: 5000,
  resolverTimeout: 1000,
  
  // Security features
  introspection: false, // Disable in production
  playground: false,    // Disable in production
  debug: false,         // Hide stack traces
  
  // Rate limiting
  rateLimit: {
    window: '15m',
    max: 100,
    skipSuccessfulRequests: false
  }
};

Error Handling

GraphQL APIs in production shouldn’t return verbose stack traces or be in debug mode. GraphQL owners should be using a middleware to control the errors the server returns. In addition, they should mask the errors and make them available to the developers but not the callers of the API.

Return generic error messages to clients:

{
  "errors": [{
    "message": "An error occurred processing your request",
    "extensions": {
      "code": "INTERNAL_SERVER_ERROR"
    }
  }]
}

Log detailed errors server-side for debugging.

Testing Your GraphQL API Security

Security Testing Checklist

  1. Batching Attack Tests

    • Send 100+ operations in a single request
    • Test array-based batching limits
    • Verify alias restrictions
  2. Authentication Tests

    • Attempt OTP bypass with batched codes
    • Test password brute-forcing via batching
    • Verify multi-factor authentication protections
  3. DoS Simulation

    • Send deeply nested queries
    • Test queries with high complexity scores
    • Verify timeout mechanisms activate
  4. Enumeration Tests

    • Attempt user ID enumeration
    • Test email address validation
    • Verify resource access controls

Automated Security Scanning

At Escape, we created a scanner that specializes in GraphQL APIs. It can streamline this process by quickly identifying issues across your API endpoints and providing remediation snippets—all in as little as 15 minutes, without complex integrations or traffic monitoring.

Use automated tools regularly: - Weekly security scans - Pre-deployment testing - Continuous monitoring in production - Regular penetration testing

Real-World Case Studies

Case Study 1: Healthcare API Breach

During a recent engagement, an API built with GraphQL allowed for the ability to retrieve Patient Health Information (PHI) when a valid “sequence number” was provided. This sequence number was randomly generated per account; however, it was not a lengthy UUID and could easily be enumerated.

Attack Method: - 9-digit sequence numbers (10^9 possibilities) - Batching reduced attack from 1 billion requests to 100,000 - Using Turbo Intruder with optimized batching - Successfully enumerated patient records

Lesson: Never rely solely on “hard to guess” identifiers. Implement proper authentication and rate limiting at the operation level.

Case Study 2: E-commerce Platform

This is commonly solved by a batching technique, where multiple requests for data from a backend are collected over a short period of time and then dispatched in a single request to an underlying database or microservice.

An e-commerce platform experienced account takeovers through batched brute-force attacks: - Attackers sent 1,000 password attempts per HTTP request - Rate limiting missed the attack (only 10 HTTP requests total) - 500+ accounts compromised before detection - Resulted in $2M+ in fraudulent transactions

Mitigation: Implemented operation-level rate limiting and disabled batching for authentication mutations.

The Future of GraphQL Security

Emerging Standards and Best Practices

The GraphQL community is actively working on security improvements:

  1. Standardized Security Directives

    • Schema-level security declarations
    • Built-in rate limiting support
    • Native complexity analysis
  2. Enhanced Tooling

    • Better static analysis tools
    • Automated security testing
    • Real-time threat detection
  3. Community Education

    • Security-first GraphQL courses
    • Open-source security libraries
    • Shared vulnerability databases

Conclusion: Balancing Power and Security

GraphQL batching attacks represent a fundamental challenge in modern API security: how do we maintain the flexibility and power that makes GraphQL attractive while protecting against exponential attack amplification?

There is a new attack surface when the app tech stack includes GraphQL. The first line of defense is secure coding. One should be careful following the specification and pay attention when implementing specific methods that might require data filtration and rate limits.

Key Takeaways:

  1. Understand the Risk: A single GraphQL request can trigger thousands of database operations
  2. Layer Your Defenses: No single mitigation is sufficient; use multiple strategies
  3. Monitor Continuously: Track query complexity, depth, and operation counts
  4. Test Regularly: Automated security scanning should be part of your CI/CD pipeline
  5. Stay Updated: GraphQL security is evolving; keep current with best practices

By implementing the comprehensive defense strategies outlined in this guide, you can harness GraphQL’s powerful features while protecting your infrastructure from batching attacks and resource exhaustion. Remember: security is not a one-time configuration but an ongoing process of monitoring, testing, and improvement.

The convenience of GraphQL batching need not come at the cost of security—with proper safeguards in place, you can have both performance and protection.


Additional Resources

Keywords: GraphQL security, batching attacks, API security, query complexity, DoS prevention, GraphQL vulnerabilities, rate limiting, authentication bypass, 2FA security, GraphQL best practices

Related Topics

#GraphQL batching attacks, GraphQL security vulnerabilities, GraphQL DoS attack, query complexity GraphQL, GraphQL rate limiting, GraphQL authentication bypass, GraphQL brute force attack, GraphQL security best practices, GraphQL depth limiting, GraphQL API security, array based batching GraphQL, alias based batching GraphQL, GraphQL OTP bypass, GraphQL 2FA bypass, GraphQL resource exhaustion, GraphQL query amplification, GraphQL attack vectors, GraphQL security tools, GraphQL Armor, graphql-depth-limit, GraphQL input validation, GraphQL complexity analysis, GraphQL timeout configuration, GraphQL persisted queries, GraphQL introspection disable, GraphQL production security, GraphQL WAF bypass, GraphQL monitoring, GraphQL vulnerability scanner, GraphQL penetration testing, GraphQL enumeration attacks, GraphQL data scraping, GraphQL mutation security, GraphQL resolver timeout, GraphQL batch size limit, GraphQL alias restriction, GraphQL query depth, GraphQL nested queries, GraphQL database calls, GraphQL performance security, GraphQL API protection, GraphQL authentication security, GraphQL password brute force, GraphQL account takeover, GraphQL security middleware, GraphQL error handling, GraphQL security configuration, GraphQL OWASP, GraphQL security scanning, GraphQL threat detection, GraphQL security testing, GraphQL secure coding, GraphQL API defense, GraphQL query validation, GraphQL operation limiting, GraphQL security plugin, GraphQL attack prevention, GraphQL security compliance, GraphQL backend security, GraphQL infrastructure protection

Share this article

More InstaTunnel Insights

Discover more tutorials, tips, and updates to help you build better with localhost tunneling.

Browse All Articles