InQL and Burp for GraphQL Hunting: A Practical Workflow
Tooling does not find GraphQL bugs for you. It removes typing. InQL plus Burp is still my default loop when a target speaks GraphQL and the schema is large enough to waste a day by hand.
Load the schema without lying to yourself
If introspection is open, point InQL at the endpoint and generate the schema view. Export queries and mutations into a scratch folder. If introspection is closed, feed InQL (or a local schema file) from traffic: copy operation names and selection sets from the SPA, then expand manually.
Burp's GraphQL tab (or a matcher on {"query") keeps the endpoint in Repeater. Save one authenticated baseline request with cookies or bearer tokens intact. Every generated operation should start from that baseline, not from an empty Repeater tab.
Check for multiple endpoints—/graphql, /api/graphql, versioned paths, internal admin gateways on another host. InQL against the wrong URL wastes an afternoon. Confirm with a known operation from the browser first.
Generate, then delete 80%
InQL will happily emit every mutation including debug and seed. Delete noise. Keep anything that:
- Takes an id you do not own
- Returns PII fields
- Touches admin-sounding verbs
- Accepts nested filters or raw JSON scalars
- Looks deprecated yet still resolves
I batch remaining operations through Turbo Intruder or a simple wordlist of foreign ids. Two sessions: attacker token in Burp, victim object ids in the payload positions. Mark which operations need CSRF-style anti-automation headers so you do not burn the account on retries.
Authz checks beat schema tourism
The hunting question is always: can session A read or change session B's object through this field? Schema coverage without cross-account proof is a diary, not a report.
Watch for batching ([{query},{query}]) and query-with-mutation smuggling in a single POST if the server allows arrays. Some gateways authorize once per HTTP request and forget per-operation checks.
Also replay mobile-only operations in the web session and vice versa. Role confusion loves GraphQL because one endpoint serves every client. If the Android app sends a richer selection set, paste that set into the web token and see what returns.
Save a "known good" unauthorized denial next to the success case. Triage likes seeing the same operation fail for the attacker until the id swap—and succeed after. That contrast removes "maybe you were admin" arguments.
Hygiene
Mark rate-limited endpoints. GraphQL introspection storms and mass mutation generation can look like abuse even when you are documenting. Prefer persisted-query ids when the client uses them—swap the id after you decode the real document from the app bundle.
Store working proofs as Burp saves with the schema commit date in the filename. Schemas drift; last week's mutation name may be gone by the time triage replies.
When introspection flakes (CDN caching, WAF challenges), save the last good schema SDL locally and continue generating from the file. Do not hammer the live endpoint for another full introspection dump every hour.
Close the loop: schema map → candidate operations → two-account proof → short report with the exact document. InQL is a printer. You still have to aim.