⚠️ Authorised Testing Only: Test GraphQL vulnerabilities exclusively within bug bounty programme scope or on systems you own. Accessing other users’ data through IDOR — even in a testing context — may constitute an unauthorised access offence. Read the programme’s policy on data access before testing authorization flaws. When demonstrating IDOR, use two accounts you control rather than accessing real user data.
While most bug bounty hunters are still queueing up the same XSS and IDOR checks on every target, the hunters consistently landing Critical payouts in 2026 are reading GraphQL schemas. Not because GraphQL is inherently more vulnerable — because it’s under-tested. Most programmes have had their REST endpoints hammered for years. The GraphQL endpoint, if the team added it six months ago, may have had zero external security review.
I’ve found critical IDOR vulnerabilities on GraphQL endpoints where introspection was disabled. I’ve found injection points that the WAF wasn’t configured to filter because it was still pattern-matching REST requests. I’ve found objects exposed through GraphQL that weren’t accessible through any REST equivalent. The methodology is different enough from REST testing that it requires its own framework — and that’s what this article builds.
By the end you’ll have the complete GraphQL attack sequence: endpoint discovery, introspection abuse, injection, broken object-level authorisation, and batching attacks.
🎯 What You’ll Master in Day 22
Discover GraphQL endpoints in web applications and extract full schemas via introspection
Map mutations and queries from the schema for IDOR and authorization testing
Test and report GraphQL IDOR via direct object ID manipulation
Test for injection vulnerabilities in GraphQL query arguments
Use batch query abuse to bypass rate limiting on sensitive operations
The first thing you learn about GraphQL hunting is that finding the endpoint is itself part of the work. There’s no Swagger UI at a predictable URL. Finding the endpoint is the first step. The most reliable method is monitoring your browser’s Network tab during normal application use: GraphQL requests appear as POST requests with a JSON body containing a query field. Burp Suite’s HTTP history captures these automatically when you proxy the application.
For applications where you haven’t spotted active GraphQL traffic, probe common endpoint paths. Send a POST request with body {"query": "{ __typename }"} to each path. A GraphQL endpoint returns {"data": {"__typename": "Query"}} on success, or a structured JSON error referencing GraphQL on any valid endpoint that rejected your query. Both responses confirm the endpoint exists. Non-GraphQL paths return HTML error pages or irrelevant JSON that doesn’t match this pattern.
Find GraphQL Endpoints and Trigger Introspection on HackerOne Programs
⏱️ 15 minutes · Browser + DevTools only
Step 1: Pick a public GraphQL API for practice
Go to: apis.guru/graphql-apis/ — a directory of public GraphQL APIs
Choose one (GitHub, SpaceX, Countries, Star Wars)
Step 2: Open browser DevTools Network tab
Navigate to the API’s documentation or playground
Filter by “graphql” in the network requests
Step 3: Send the introspection query
Using the API’s GraphQL playground or a direct fetch:
fetch(‘[endpoint]’, {
method: ‘POST’,
headers: {‘Content-Type’: ‘application/json’},
body: JSON.stringify({query: ‘{__schema{types{name}}}’})
}).then(r=>r.json()).then(console.log)
Step 4: Examine the type list
How many types does the schema contain?
Do any type names suggest sensitive operations?
(look for: Admin, Internal, Private, Token, Secret)
Step 5: Get full field details for an interesting type
{__type(name:”User”){fields{name type{name ofType{name}}}}}
What fields does the User type expose?
Are any field names sensitive (password, token, adminRole)?
Step 6: On a bug bounty program (with scope permission):
Use DevTools on a real target application
Monitor network requests during login, profile update, etc.
Identify any GraphQL requests and note the endpoint path
✅ What you just learned: Introspection on a real API immediately shows you the complete attack surface without any fuzzing or guessing. The type names and field names in a well-designed schema are often self-documenting — seeing a MutationType with fields like resetPasswordAdmin, createInternalUser, or bypassRateLimit tells you exactly what to test next. This reconnaissance phase is what separates GraphQL hunters who find Critical findings from those who send the same generic XSS tests to every endpoint.
📸 Screenshot an introspection schema response. Share in #bug-bounty on Discord.
GraphQL Introspection — The Free Schema Dump
Introspection is a GraphQL feature that allows clients to query the schema itself — asking “what queries and mutations are available, what arguments do they take, what types do they return?” When enabled in production (which it should not be), introspection hands the attacker a complete blueprint of the backend API including operations that are never used by the frontend application.
The full introspection query is verbose, but the critical sections are the type list (all object types and their fields) and the mutation type (all write operations). Fields named adminToken, internalUserId, bypassAuth, or mutations named createAdminUser flag themselves as high-priority targets. Even “safe” schemas reveal ID fields on user and resource objects — the starting point for IDOR testing.
GRAPHQL INTROSPECTION QUERIES
# Full schema dump (paste into Burp Repeater or Altair)
{
__schema {
types {
name
fields { name type { name ofType { name } } }
}
mutationType { name fields { name args { name type { name } } } }
queryType { name fields { name args { name type { name } } } }
}
}
# Quick type list only
{ __schema { types { name } } }
# Inspect a specific type
{ __type(name: “User”) { fields { name type { name } } } }
# Introspection disabled? Try field suggestion bypass
# Many APIs disable __schema but leave __type accessible
📸 GraphQL introspection response with high-value fields annotated. The adminToken field on the User type is the most significant finding — if any query returns this field for arbitrary users, it is a Critical information disclosure. The internalId field is an IDOR vector — test whether it lets you access other users’ data. The role field warrants authorization testing: can a regular user access data visible only to admin roles? Introspection turns API reconnaissance from guessing into reading a blueprint.
IDOR via Direct Object IDs
GraphQL queries frequently accept direct object IDs as arguments. When authorization is implemented at the application layer rather than per-resolver, the API may return any object’s data for any valid ID regardless of which user is making the request. Testing GraphQL IDOR follows the same pattern as REST IDOR: create two test accounts, retrieve your own object’s ID, then use Account B to query Account A’s data using that ID.
query { user(id: “122”) { id email } } # one below your own
query { user(id: “124”) { id email } } # one above your own
Broken Authorization on Mutations
GraphQL mutations — the write operations — are where broken authorization causes the most damage. Developers sometimes implement read authorization carefully (checking whether a user can see data) but miss authorization on mutations (checking whether a user can change data). Mutations worth testing: password reset for another user’s account, privilege escalation (changing your own role field), accessing admin-only mutations visible in the schema but not in the UI, and deleting or modifying other users’ resources.
The schema from introspection reveals every mutation even if the frontend application never calls them. An assignAdminRole mutation that exists in the schema but is never called from the UI is almost certainly not properly access-controlled — developers often forget to add authorization checks to mutations that aren’t exposed to users. This is the highest-ROI attack surface from introspection: mutations that exist but are invisible.
🌐 EXERCISE 2 — PORTSWIGGER (25 MIN)
GraphQL IDOR and Authorization Labs on PortSwigger Web Academy
Step 1: Go to PortSwigger Web Academy GraphQL section
URL: portswigger.net/web-security/graphql
Read the introductory material (15 minutes)
Focus on: introspection, IDOR, accessing private GraphQL posts
Step 2: Complete Lab — “Accessing private GraphQL posts”
This lab has a GraphQL API with an IDOR vulnerability.
Find the blog post query.
Use introspection to map the schema.
Access a hidden blog post that shouldn’t be visible to your user.
Step 3: Note the exact introspection query you used
What type contained the hidden field?
What argument did you use to access the private post?
Step 4: Complete Lab — “Finding a hidden GraphQL endpoint”
This lab hides the GraphQL endpoint.
Use the discovery techniques from this article.
Once found, run introspection and identify the vulnerability.
Step 5: Document your findings format
For each lab: write a one-paragraph bug bounty report summary:
– Endpoint and vulnerability type
– Steps to reproduce
– Impact (what data is accessible)
– Severity rating and justification
✅ What you just learned: PortSwigger’s GraphQL labs demonstrate the real-world pattern — the vulnerability is in the data returned for objects you shouldn’t access, not in finding a bug in the query syntax itself. The authorization check is missing at the resolver level, meaning the GraphQL API happily returns whatever data the query requests without checking whether the requesting user is allowed to see it. This pattern repeats across thousands of real bug bounty targets because GraphQL’s flexibility makes per-field authorization easy to miss.
📸 Screenshot your completed lab badge. Share in #bug-bounty on Discord.
GraphQL Injection
GraphQL arguments that are passed to backend database queries or system operations are injection vectors. The GraphQL layer itself is not injectable (it parses structured query syntax), but the arguments within queries are passed to resolvers that often construct SQL, NoSQL, or other queries. SQL injection in GraphQL: test string arguments with ', ' OR '1'='1, and ' OR SLEEP(5)--. NoSQL injection: test object arguments with {"$gt": ""} and {"$where": "1==1"}. SSRF: test URL arguments with your Burp Collaborator callback URL.
securityelites.com
GraphQL Injection Test — SQL in String Argument
Normal query:
{“query”: “{ user(username: \”admin\”) { id email } }”}
Injection test — time-based:
{“query”: “{ user(username: \”admin’ AND SLEEP(5)–\”) { id email } }”}
📸 GraphQL injection testing. The GraphQL query structure itself is not injectable — the parser handles the query syntax correctly. The vulnerability is in the resolver function that takes the argument value and constructs a backend query without proper parameterisation. Time-based SQL injection (SLEEP) is the most reliable blind detection method. NoSQL injection with $regex or $gt operators exploits MongoDB-style object arguments. Both are High to Critical severity when confirmed.
Result: rate limit of 10 req/min = 50 credential attempts/min (5x multiplier per batch)
With 100-alias batch: 10 req/min = 1,000 attempts/min
📸 GraphQL batch query abuse for rate limit bypass. Standard rate limiting counts HTTP requests — one request with 100 aliased mutations is processed as 100 operations but counted as 1 request. The multiplication factor equals the batch size. This is particularly effective against OTP verification endpoints where a 6-digit OTP has only 1,000,000 possible values — a 100-alias batch at 10 requests/minute = 1,000,000 attempts in ~16 hours, bypassing any per-request rate limit.
Batch Query Abuse for Rate Limit Bypass
GraphQL supports aliased queries — sending multiple operations with different aliases in a single HTTP request. Rate limiting typically operates at the HTTP request level: X requests per minute per IP. Batch query abuse exploits this by sending 100 operations as aliases in one HTTP request. The server counts one request but processes 100 operations. This is particularly impactful against OTP verification endpoints, login mutation rate limiting, and any operation where the protection relies on HTTP request counting.
# Rate limit of 10 req/min = 1000 credential tests/min
⚡ EXERCISE 3 — BURP SUITE (20 MIN)
Test GraphQL Introspection and Schema Analysis with Burp InQL
⏱️ 20 minutes · Burp Suite Community + InQL extension
Step 1: Install InQL extension in Burp Suite
Burp Suite → Extender → BApp Store → search “InQL” → Install
Step 2: Find a GraphQL practice target
Use one of:
– DVGA (Damn Vulnerable GraphQL Application):
docker run -p 5013:5013 dolevf/dvga
– dvga.example.com (public instance if available)
– Any target from PortSwigger GraphQL labs
Step 3: Proxy the GraphQL endpoint through Burp
Configure browser to use Burp proxy (127.0.0.1:8080)
Make any request to the target
Step 4: Use InQL Scanner tab
Go to Burp → InQL Scanner
Enter the target GraphQL URL
Click Analyze — InQL runs introspection and parses the schema
Step 5: Review generated query templates
InQL generates ready-to-test queries for every operation
Look for: mutations, admin operations, user object queries
Step 6: Send interesting queries to Repeater
Right-click any operation → Send to Repeater
Test with your own IDs vs other user IDs
Test mutation authorization
Document any findings with request/response evidence
✅ What you just learned: InQL transforms raw introspection data into actionable query templates — instead of manually constructing queries from the JSON schema, you get pre-built templates for every operation ready to test in Repeater. This workflow (discover endpoint → InQL schema analysis → Repeater authorization testing) covers the core of professional GraphQL security assessment and directly applies to bug bounty targets with GraphQL APIs.
📸 Screenshot the InQL schema analysis output. Share in #bug-bounty on Discord.
GraphQL Reporting Tip: When reporting introspection as a standalone finding, always demonstrate what the exposure enables — don’t just report “introspection is enabled.” Show the schema excerpt containing the most sensitive fields or mutations, and state the attack scenarios it facilitates. A well-documented introspection finding that says “schema reveals adminBypassToken field and createAdminUser mutation — tested and both are accessible without admin authorization” gets triaged as High/Critical. “Introspection is enabled” alone gets triaged as Informational.
🧠 QUICK CHECK — GraphQL Bug Bounty
You run introspection and discover a mutation named deleteUserAccount(userId: ID!) in the schema. The application’s UI only shows a “Delete My Account” button that deletes the authenticated user. How do you test this for a vulnerability?
IDOR testGet own ID with Account A → query with Account B using Account A’s ID
Batch rate limit bypassalias multiple mutations in one request: a1: login(…) a2: login(…)
SQL injection testPass ‘ AND SLEEP(5)– in string arguments
Best toolBurp Suite + InQL extension for schema analysis and query generation
🏆 Day 22 Complete — GraphQL Bug Bounty
Day 23 covers WebSocket security — cross-site WebSocket hijacking and message injection vulnerabilities.
GraphQL Real Finding — IDOR to Account Takeover Chain
Let me walk you through exactly how a real GraphQL critical finding looks, because the theory only makes sense when you see the attack path end to end.
The target had disabled GraphQL introspection — a common security measure teams implement when they think it stops enumeration. It doesn’t. I ran the __type query manually and got the schema back. The backend was validating the introspection query keyword but not the underlying type system queries. First finding.
Once I had the schema, I mapped the user-related queries. There was a getUser(id: Int) query that returned full user profile data — email, phone, address, linked payment methods. The ID was a sequential integer. No authorisation check on the resolver beyond “authenticated user.” I queried ID 1. Got the admin’s full profile. Critical IDOR, full account takeover chain, remediated same day.
# If this returns another user’s data — critical IDOR
# Step 4: Batch query to confirm scale
[
{“query”: “{getUser(id: 1) {email}}”},
{“query”: “{getUser(id: 2) {email}}”},
{“query”: “{getUser(id: 3) {email}}”}
]
# Confirm multi-user data exposure for full impact statement
The report on that finding was straightforward: here’s the query, here’s the response with another user’s payment methods, here’s the CVSS score (9.3), here’s the remediation — add authorisation check to the resolver comparing the requested user ID against the authenticated session’s user ID. Programme paid within 48 hours. That’s what GraphQL IDOR looks like on a real programme.
What is GraphQL and why is it a bug bounty target?
GraphQL is a flexible API query language where clients specify exactly what data they want. It is a prime bug bounty target because introspection exposes the full API blueprint, authorization is frequently missing at the resolver level creating IDOR, and batch queries can bypass rate limiting.
How do you find GraphQL endpoints in bug bounty?
Probe /graphql, /api/graphql, /v1/graphql, /query with a POST body containing {query: “{__typename}”}. Search JavaScript bundles for graphql URL strings. Monitor browser DevTools Network tab for POST requests with JSON query bodies during normal app usage.
What is GraphQL introspection and why is it a finding?
Introspection returns the complete API schema — all types, queries, mutations, and arguments. When enabled in production it exposes internal operations and admin functions not visible in the UI. Medium severity alone; escalates to High/Critical when the schema reveals exploitable admin operations or IDOR vectors.
How does GraphQL IDOR work?
Queries accepting direct object IDs (user(id: 123)) without per-resolver authorization checks return any object’s data for any valid ID. Test with two accounts: retrieve your ID with Account A, query it with Account B. If Account B receives Account A’s data, IDOR is confirmed.
What is batch query abuse in GraphQL?
Sending multiple aliased operations in a single HTTP request to bypass per-request rate limiting. One HTTP request can contain 100 aliased login mutations, multiplying credential testing throughput by 100× while counting as one request against rate limiters.
What is the best tool for GraphQL security testing?
Burp Suite with the InQL extension — InQL parses introspection responses and generates query templates for every operation. Altair GraphQL Client for manual exploration. GraphQL Voyager for visual schema mapping. All compatible with professional bug bounty workflows.
← Previous
Day 21: HTTP Request Smuggling
Next →
Day 23: WebSocket Security
📚 Further Reading
Day 21: HTTP Request Smuggling— Request manipulation fundamentals that apply to crafting custom GraphQL requests in Burp Suite.
Bug Bounty 60-Day Course Hub— Full course overview — Day 22 GraphQL completes the API security block alongside Day 20 IDOR and Day 21 Smuggling.
How to Use Shodan in 2026— Finding exposed GraphQL endpoints using Shodan’s HTTP title and header searches before engaging with a target.
PortSwigger — GraphQL API Vulnerabilities— The most comprehensive free GraphQL security learning resource with hands-on labs covering introspection, IDOR, and injection.
Awesome GraphQL Security— Curated list of GraphQL security tools, research papers, and real-world vulnerability writeups.
ME
Mr Elite
Owner, SecurityElites.com
My favourite GraphQL finding was a Critical IDOR on a fintech platform. Introspection was disabled — the dev team thought that protected them. I tested __type queries instead and found the User type had a field called accountBalance. Sent a query with sequential IDs and confirmed I could read any user’s account balance, transaction history, and linked card last four digits. No admin access needed. Just a loop and sequential integers. The entire attack took twelve minutes from finding the endpoint to having a Critical report ready. The fix was a one-line resolver check. The lesson: disabling introspection reduces reconnaissance, it doesn’t prevent IDOR.