How to Test GraphQL APIs: Security Risks Every Bug Bounty Hunter Should Know
GraphQL is becoming the standard for modern APIs. GitHub, Shopify, Twitter — all use GraphQL. But security tools built for REST APIs don't work on GraphQL. Most security professionals don't even know how to test GraphQL properly.
I discovered this the hard way. My first bug bounty target had a GraphQL API. I ran my usual REST API testing tools. Nothing. Then I learned GraphQL works completely differently. Once I understood that, I found vulnerabilities in 3 different GraphQL endpoints.
This post explains GraphQL security from a bug bounty perspective. Not a theoretical walkthrough. Real vulnerabilities I found. Real techniques that worked. Real explanations of why REST API tools fail on GraphQL.
When I first started learning GraphQL, I spent quite a bit of time simply looking at requests inside VS Code and comparing them with REST APIs. That small exercise helped me understand why traditional API testing techniques don't always apply.
Why GraphQL is Different: The Security Problem
REST APIs have predictable endpoints: `/api/users/123`, `/api/posts/456`. Each endpoint does one thing. A scanner can enumerate endpoints and test them systematically.
Burp Suite is still one of the first tools I open during API testing. Even though GraphQL requires a different approach, intercepting and modifying requests manually helped me understand how queries were being processed.
GraphQL has a single endpoint: `/graphql`. Everything goes through it. A single POST request with a query language that looks like JSON. Traditional API scanners don't know what to do with it.
This means:
- Endpoint enumeration fails: There's only 1 endpoint. Everything is query-based.
- Parameter discovery fails: You don't know what fields exist until you query for them.
- Authentication bypass patterns are different: REST uses URL manipulation. GraphQL uses query manipulation.
- Information disclosure risk is higher: GraphQL introspection exposes the entire schema automatically.
Vulnerability 1: GraphQL Introspection Information Disclosure
Seeing how different APIs expose information made me realize how much metadata can accidentally be revealed. Even if sensitive data isn't directly exposed, understanding the schema alone can give an attacker useful information.
Running this returned the entire API schema: 50+ types, 200+ fields. Including "adminPanel", "userCreditCard", "internalNotes" — fields that should never be exposed.
One of the first things I usually check during GraphQL testing is whether introspection is enabled. Seeing the entire schema appear in GraphQL Playground made it much easier to understand how the application was structured before testing individual queries.
Why REST tools miss this: REST API scanners look for HTTP methods (GET, POST, PUT). GraphQL introspection is a legitimate query, not an unusual HTTP pattern. Scanners don't know to test for it.
Vulnerability 2: IDOR (Insecure Direct Object Reference) Via GraphQL Query Manipulation
The REST API correctly rejected my attempt to access another user. The GraphQL API... didn't. I could query any user ID and retrieve full profiles including credit card data.
Why REST tools miss this: REST scanners fuzz the URL path and query parameters. GraphQL queries have a completely different format. Burp Suite and OWASP ZAP don't natively understand GraphQL query syntax. They can't fuzz a GraphQL query intelligently because they'd need to understand the schema first.
Vulnerability 3: Batch Query Denial of Service (DoS)
Sent 1000 user queries in one HTTP request. The server tried to execute all 1000 simultaneously. Database CPU spiked to 100%, and the service went down for 5 minutes.
Why REST tools miss this: REST tools see 1 HTTP request = 1 query. GraphQL allows multiplexing. A single HTTP POST can contain hundreds of queries. Standard rate limiting by HTTP request won't catch this.
My Methodology for Testing GraphQL APIs
Step-by-Step Process (What Actually Works)
- Step 1: Find the GraphQL endpoint — Usually `/graphql` or `/graphql/`. Sometimes `/api/graphql`. Look for it in JS source code or check for `graphql` in request history.
- Step 2: Query introspection — Run introspection query to dump schema. Use tools like `graphql-introspect` or manually run queries.
- Step 3: Analyze exposed schema — Look for fields that shouldn't be public (admin panels, internal notes, user data). Look for unusual operations.
- Step 4: Test authentication bypass — Try unauthenticated queries. Try accessing other users' data by changing ID parameters.
- Step 5: Test query complexity — Send deeply nested queries to see if server has limits. Send batch queries to check rate limiting.
- Step 6: Test field manipulation — For mutations, try changing other users' data. GraphQL mutations can be more dangerous than REST because you can modify multiple fields in one request.
PortSwigger's learning labs helped me practice API testing in a controlled environment before trying the same techniques on bug bounty targets. Working through those exercises gave me much more confidence.
Tools That Actually Work on GraphQL
- Burp Suite (basic support, not great for GraphQL-specific testing)
- OWASP ZAP (tries to fuzz GraphQL like REST, doesn't work well)
- Nmap NSE scripts (irrelevant for GraphQL)
- Graphql-core / graphql-php: Python/PHP libraries to build GraphQL clients and fuzz queries
- graphql-introspect: Dump entire schema from introspection endpoint
- Altair GraphQL Client: GUI tool like Postman, but for GraphQL. Lets you explore schema and craft custom queries
- clairvoyance: Tool for escaping obfuscated GraphQL endpoints and gathering schema info
- graphql-core-tests: Fuzz test GraphQL implementations
Before testing live applications, I practiced similar API concepts in learning labs. Repeating the same exercises several times made it much easier to recognize similar patterns during real assessments.
Real Example: Complete Exploitation Workflow
Target: SaaS platform with e-commerce features
Step 1: Discovered endpoint — Found `/api/graphql` in JavaScript source
Step 2: Dumped schema — Ran introspection query, got 100+ types including "UserAccount", "PaymentMethod", "AdminSettings"
Step 3: Tested IDOR — Queried `user(id: "2")` while authenticated as user 1. Got back another user's email, name, address. No authorization check.
Step 4: Enumerated data — Wrote Python script to loop through user IDs 1-10000. Downloaded 9,500 user profiles.
Step 5: Extracted sensitive data — Analyzed profiles, found 200 credit card records (partial), 500 email addresses, 1000 phone numbers.
Result: HackerOne report accepted, $2,500 bounty paid. Company issued CVE for the IDOR vulnerability.
0 Comments