$npx -y skills add elementalsouls/Claude-BugHunter --skill hunt-graphqlHunting skill for graphql vulnerabilities. Built from 12 public bug bounty reports across IDOR via node() / GID, mutation IDOR including AI/LLM features, cross-tenant IDOR, SSRF via argument, batching-DoS, query-cost-bypass, SQLi via argument, broken-object-level-authz, auth-bypa
| 1 | ## Crown Jewel Targets |
| 2 | |
| 3 | GraphQL vulnerabilities are high-value because the attack surface is both broad and deep — a single endpoint can expose entire data models, privilege escalation paths, and cross-API state confusion. Highest payouts occur in: |
| 4 | |
| 5 | - **Platform APIs** (GitHub, Shopify, Stripe-tier targets) where GraphQL mutations interact with REST APIs managing the same resources |
| 6 | - **Race conditions between GraphQL mutations and REST endpoints** where state synchronization is non-atomic — these hit medium-to-high severity reliably |
| 7 | - **Authorization persistence bugs** where team/org/repo membership state is controlled by one API but readable/writable by another |
| 8 | - **B2B SaaS platforms** where one tenant affecting another via schema traversal = critical |
| 9 | - **Internal admin GraphQL endpoints** accidentally exposed to lower-privilege users |
| 10 | |
| 11 | The GitHub reports demonstrate the crown jewel pattern: **privilege that should be revoked persists because two APIs disagree on ground truth**. |
| 12 | |
| 13 | --- |
| 14 | |
| 15 | ## Attack Surface Signals |
| 16 | |
| 17 | **URL Patterns:** |
| 18 | ``` |
| 19 | /graphql |
| 20 | /api/graphql |
| 21 | /v1/graphql |
| 22 | /query |
| 23 | /gql |
| 24 | /graph |
| 25 | /api/v2/graphql |
| 26 | /internal/graphql |
| 27 | ``` |
| 28 | |
| 29 | **Response Headers:** |
| 30 | ``` |
| 31 | Content-Type: application/json (with query body) |
| 32 | X-Request-Id + no REST-style path params = likely GraphQL |
| 33 | ``` |
| 34 | |
| 35 | **JavaScript Source Patterns:** |
| 36 | ```js |
| 37 | // grep for these in JS bundles |
| 38 | "query {" |
| 39 | "mutation {" |
| 40 | "__typename" |
| 41 | "apollo" |
| 42 | "ApolloClient" |
| 43 | "graphql-tag" |
| 44 | "gql`" |
| 45 | "operationName" |
| 46 | "GRAPHQL_URI" |
| 47 | ``` |
| 48 | |
| 49 | **Tech Stack Signals:** |
| 50 | - Apollo Server/Client in JS bundles |
| 51 | - Relay in React apps |
| 52 | - `graphene` or `strawberry` (Python), `graphql-ruby`, `gqlgen` (Go), `Lighthouse` (Laravel) |
| 53 | - POST requests with `{"query": "..."}` body shape in Burp history |
| 54 | - `__schema` or `__type` in any response = confirmed GraphQL |
| 55 | |
| 56 | **Recon Sources:** |
| 57 | - `github.com` search: `"graphql" site:target.com` |
| 58 | - Wayback Machine for `/graphql` paths |
| 59 | - JS bundle scanning with `LinkFinder` or `getallurls` |
| 60 | |
| 61 | --- |
| 62 | |
| 63 | ## Step-by-Step Hunting Methodology |
| 64 | |
| 65 | 1. **Discover the endpoint** — spider JS bundles, check `/graphql`, `/api/graphql`, review Burp passive scan hits for `application/json` POST with query fields |
| 66 | |
| 67 | 2. **Test introspection** — send the full introspection query. Even if blocked, try field-level enumeration: |
| 68 | ```graphql |
| 69 | { __typename } |
| 70 | ``` |
| 71 | If that returns, introspection may be partially blocked but the schema is discoverable |
| 72 | |
| 73 | 3. **Map the full schema** — use `InQL` (Burp extension) or `graphql-voyager` to visualize relationships. Specifically look for: |
| 74 | - Mutations that modify ownership, permissions, or membership |
| 75 | - Mutations that mirror REST API functionality |
| 76 | |
| 77 | 4. **Identify REST/GraphQL overlap** — document every resource that can be modified via BOTH REST and GraphQL. These dual-write surfaces are your RC targets. |
| 78 | |
| 79 | 5. **Test authorization boundaries per mutation** — replay mutations as lower-privilege users. Does the server enforce the same authz as the equivalent REST call? |
| 80 | |
| 81 | 6. **Hunt cross-API state desync** — find sequences where: |
| 82 | - REST action should revoke access |
| 83 | - GraphQL mutation re-grants or preserves it |
| 84 | - Test the ordering: REST first → GraphQL → check state; then GraphQL first → REST → check state |
| 85 | |
| 86 | 7. **Test for persistent privilege after role/membership changes** — remove a user via REST, then call the corresponding GraphQL mutation for that resource. Query current state via both APIs and compare. |
| 87 | |
| 88 | 8. **Probe for IDOR in node IDs** — GraphQL global IDs often encode object type + ID. Swap IDs across object boundaries and across account contexts. |
| 89 | |
| 90 | 9. **Check batch query abuse** — send arrays of operations to bypass rate limiting or amplify enumeration. |
| 91 | |
| 92 | 10. **Document the exact reproduction chain** — for RC bugs, time-based steps must be reproducible deterministically. |
| 93 | |
| 94 | --- |
| 95 | |
| 96 | ## Payload & Detection Patterns |
| 97 | |
| 98 | **Full Introspection Query:** |
| 99 | ```graphql |
| 100 | { |
| 101 | __schema { |
| 102 | types { |
| 103 | name |
| 104 | fields { |
| 105 | name |
| 106 | type { |
| 107 | name |
| 108 | kind |
| 109 | } |
| 110 | } |
| 111 | } |
| 112 | } |
| 113 | } |
| 114 | ``` |
| 115 | |
| 116 | **Minimal Introspection Probe (bypass attempt):** |
| 117 | ```graphql |
| 118 | { __typename } |
| 119 | ``` |
| 120 | |
| 121 | **curl introspection test:** |
| 122 | ```bash |
| 123 | curl -s -X POST https://target.com/graphql \ |
| 124 | -H "Content-Type: application/json" \ |
| 125 | -H "Authorization: Bearer YOUR_TOKEN" \ |
| 126 | -d '{"query":"{ __schema { queryType { name } } }"}' | jq . |
| 127 | ``` |
| 128 | |
| 129 | **Field suggestion probe (bypass blind introspection blocks):** |
| 130 | ```graphql |
| 131 | { unknownField } |
| 132 | ``` |
| 133 | If response returns `"Did you mean: [realFieldName]?"` — schema is enumerable despite in |