GraphQL is an API query language for communication between clients and servers. GraphQL services define a contract (represented by a schema) through which a client can communicate with a server. The client can send queries to the GraphQL server and specify exactly what data they want in the response to avoid the large response objects. As GraphQL is platform-agnostic, it can be implemented with a wide range of programming languages and can be used to communicate with virtually any data store.
GraphQL schemas define the structure of the service’s data, listing the available objects (known as types), fields, and relationships. Schemas operations: queries (fetch data), data mutations (add, change, remove), subscriptions (permanent connection, push data to client).
Introspection is a built-in GraphQL function that enables you to query a server for information about the schema.
All GraphQL operations use the same endpoint (unlike REST APIs), and are generally sent as a POST request.
- Testing GraphQL (OWASP Testing Guide)
- Testing GraphQL APIs (PortSwigger)
- What is GraphQL? (PortSwigger)
- GraphQL Playground
- GraphQL Visualizer
- Clairvoyance (GitHub)
- Bypassing GraphQL introspection defences (PortSwigger)
Examples
Schema
Mandatory fields are marked with “!”.
type Product {
id: ID!
name: String!
description: String!
price: Int
}
Query
- “query” operation, optional
- “query name” (myGetProductQuery) can be anything, optional
- “data structure” is the data that will be returned (name, description)
- “arguments” (id: 123), filter similar to a WHERE clause in SQL
- “variables” ($id) allows to pass dynamic arguments
query myGetProductQuery {
getProduct(id: 123) {
name
description
}
}
Get all employees.
query myGetEmployeeQuery {
getEmployees {
id
name {
firstname
lastname
}
}
}
Get employee with id = 1. If user-supplied arguments are used to access objects directly then a GraphQL API can be vulnerable to access control vulnerabilities such as insecure direct object references (IDOR).
query myGetEmployeeQuery {
getEmployees(id:1) {
name {
firstname
lastname
}
}
}
Variable is declared with “$id: ID!”. “!” means mandatory.
query getEmployeeWithVariable($id: ID!) {
getEmployees(id:$id) {
name {
firstname
lastname
}
}
}
Variables:
{
"id": 1
}
Mutation
Modify the data (equivalent to POST, PUT, DELETE).
mutation {
createProduct(name: "Flamin' Cocktail Glasses", listed: "yes") {
id
name
listed
}
}
Alias
Use aliases to return types or fields with the same name (like in SQL). Use syntax “thisIsMyAlias: “.
Use aliases to return multiple instances of the same type of object in one request to bypass rate limiting.
query getProductDetails {
product1: getProduct(id: "1") {
id
name
}
product2: getProduct(id: "2") {
id
name
}
}
Testing GraphQL
Burp Scanner can automatically find GraphQL endpoints, test for introspection and test for suggestions as part of its scans.
Find the GraphQL endpoint
Send a universal query
GraphQL services will often respond to any non-GraphQL request with a “query not present” or similar error.
Every GraphQL endpoint has a reserved field called __typename that returns the queried object’s type as a string. Send this query to common GraphQL URLs:
query{__typename}
/graphql
/api
/api/graphql
/graphql/api
/graphql/graphql
/graphql/v1
/api/v1
/api/graphql/v1
/graphql/api/v1
/graphql/graphql/v1
POST /api HTTP/1.1
Host: <hostname>
Content-Type: application/json
Content-Length: 31
"query":"query{__typename}"
If the URL is a GraphQL endpoint, the response will include this string in the response:
{"data": {"__typename": "query"}}
If you can’t find the GraphQL endpoint by sending POST requests to common endpoints, try resending the universal query using alternative HTTP methods.
Test for unauthorized access to the GraphQL schema through introspection
Install Burp Suite‘s extension InQL.
Send a probe request
To use introspection to discover schema information, query the __schema field. This field is available on the root type of all queries. Send this probe request. If it succeeds, it will return the names of all available queries.
{
"query": "{__schema{queryType{name}}}"
}
Send the full introspection query
Returns full details on all queries, mutations, subscriptions, types, and fragments.
If introspection is enabled but the above query doesn’t run, try removing the onOperation, onFragment, and onField directives from the query structure. Many endpoints do not accept these directives as part of an introspection query, and you can often have more success with introspection by removing them. Also try Clairvoyance to obtain the schema when introspection is disabled.
query IntrospectionQuery {
__schema {
queryType {
name
}
mutationType {
name
}
subscriptionType {
name
}
types {
...FullType
}
directives {
name
description
args {
...InputValue
}
onOperation #Often needs to be deleted to run query
onFragment #Often needs to be deleted to run query
onField #Often needs to be deleted to run query
}
}
}
fragment FullType on __Type {
kind
name
description
fields(includeDeprecated: true) {
name
description
args {
...InputValue
}
type {
...TypeRef
}
isDeprecated
deprecationReason
}
inputFields {
...InputValue
}
interfaces {
...TypeRef
}
enumValues(includeDeprecated: 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
}
}
}
}
Full query on one line.
query IntrospectionQuery{\n__schema{\nqueryType{\nname\n}\nmutationType{\nname\n}\nsubscriptionType{\nname\n}\ntypes{\n...FullType\n}\ndirectives{\nname\ndescription\nargs{\n...InputValue\n}\nonOperation\nonFragment\nonField\n}\n}\n}\n\nfragment FullType on __Type{\nkind\nname\ndescription\nfields(includeDeprecated:true){\nname\ndescription\nargs{\n...InputValue\n}\ntype{\n...TypeRef\n}\nisDeprecated\ndeprecationReason\n}\ninputFields{\n...InputValue\n}\ninterfaces{\n...TypeRef\n}\nenumValues(includeDeprecated:true){\nname\ndescription\nisDeprecated\ndeprecationReason\n}\npossibleTypes{\n...TypeRef\n}\n}\n\nfragment InputValue on __InputValue{\nname\ndescription\ntype{\n...TypeRef\n}\ndefaultValue\n}\n\nfragment TypeRef on __Type{\nkind\nname\nofType{\nkind\nname\nofType{\nkind\nname\nofType{\nkind\nname\n}\n}\n}\n}
Full query on one line without the onOperation, onFragment, and onField.
query IntrospectionQuery{\n__schema{\nqueryType{\nname\n}\nmutationType{\nname\n}\nsubscriptionType{\nname\n}\ntypes{\n...FullType\n}\ndirectives{\nname\ndescription\nargs{\n...InputValue\n}\n}\n}\n}\n\nfragment FullType on __Type{\nkind\nname\ndescription\nfields(includeDeprecated:true){\nname\ndescription\nargs{\n...InputValue\n}\ntype{\n...TypeRef\n}\nisDeprecated\ndeprecationReason\n}\ninputFields{\n...InputValue\n}\ninterfaces{\n...TypeRef\n}\nenumValues(includeDeprecated:true){\nname\ndescription\nisDeprecated\ndeprecationReason\n}\npossibleTypes{\n...TypeRef\n}\n}\n\nfragment InputValue on __InputValue{\nname\ndescription\ntype{\n...TypeRef\n}\ndefaultValue\n}\n\nfragment TypeRef on __Type{\nkind\nname\nofType{\nkind\nname\nofType{\nkind\nname\nofType{\nkind\nname\n}\n}\n}\n}
Full query on one line, using bypass techniques (\n=%0A, {=%7B, }=%7D).
GET /api?query=query%20IntrospectionQuery%7B%0A__schema%0A%7B%0AqueryType%7B%0Aname%0A%7D%0AmutationType%7B%0Aname%0A%7D%0AsubscriptionType%7B%0Aname%0A%7D%0Atypes%7B%0A...FullType%0A%7D%0Adirectives%7B%0Aname%0Adescription%0Aargs%7B%0A...InputValue%0A%7D%0A%7D%0A%7D%0A%7D%0Afragment%20FullType%20on%20__Type%7B%0Akind%0Aname%0Adescription%0Afields(includeDeprecated:true)%7B%0Aname%0Adescription%0Aargs%7B%0A...InputValue%0A%7D%0Atype%7B%0A...TypeRef%0A%7D%0AisDeprecated%0AdeprecationReason%0A%7D%0AinputFields%7B%0A...InputValue%0A%7D%0Ainterfaces%7B%0A...TypeRef%0A%7D%0AenumValues(includeDeprecated:true)%7B%0Aname%0Adescription%0AisDeprecated%0AdeprecationReason%0A%7D%0ApossibleTypes%7B%0A...TypeRef%0A%7D%0A%7D%0Afragment%20InputValue%20on%20__InputValue%7B%0Aname%0Adescription%0Atype%7B%0A...TypeRef%0A%7D%0AdefaultValue%0A%7D%0A%0Afragment%20TypeRef%20on%20__Type%7B%0Akind%0Aname%0AofType%7B%0Akind%0Aname%0AofType%7B%0Akind%0Aname%0AofType%7B%0Akind%0Aname%0A%7D%0A%7D%0A%7D%0A%7D HTTP/1.1
Use GraphQL Visualizer to visualize the results or use Burp Suite‘s extension InQL.
Test for CSRF
TO COMPLETE