Introduction
GraphQL is a query language for an API and not for a database i.e., it is database agnostic. Clients can use GraphQL to request for many types of data from multiple source in the API, as well as choose what parts of the data should be returned to the client.
Schema
It is the blueprint of the GraphQL service that is used to define types and data used throughout the application and their requirements. Query and Mutations are two types in the schema definition. It also governs which data a client can request or mutate. To define types, GraphQL uses some primary types called Scalar Types, which are as follows →
- Int → A signed 32‐bit integer.
- Float → A signed double-precision floating-point value.
- String → A UTF‐8 character sequence.
- Boolean → true or false.
- ID → The ID scalar type represents a unique identifier, often used to refetch an object or as the key for a cache. The ID type is serialized in the same way as a String, however, defining it as an ID signifies that it is not intended to be human-readable.
- Custom Scalar types can be defined as follows →
scalar Date
. - Enums → A special type of scalar value which has a specific number of allowed values.
1
2
3
4
enum PrivacyType {
Public
Private
}
Query
All GraphQL services have a query type which is similar to an object type but is special because it is used to define the entry point for a query. A query can be sent as follows →
1
2
3
4
5
6
{
user {
name
avatarLink
}
}
The query returns the following form of data →
1
2
3
4
5
6
7
8
{
'data': {
'user': {
'name': 'John Snow',
'avatarLink': 'https://....'
}
}
}
The query can be defined as follows →
1
2
3
4
type Query {
user(id: ID!): User
... # other query definition.
}
Mutation
A mutation may or may not be defined for a GraphQL service. It can be used to modify the data store for the GraphQL service. It’s like a union of the PUT, POST and DELETE request types. It can be defined in the following manner →
1
2
3
4
type Mutation {
createUser(name: String!, avatarLink: String, dateOfBirth: String, location: String): User
... # Other mutations
}
A client can send a request with the following body →
1
2
3
4
5
6
7
mutation {
createUser(name: 'Ned Stark') {
name
location
avatarLink
}
}
The response from the server could be something like the following →
1
2
3
4
5
6
7
8
9
{
'data': {
'createUser': {
'name': 'Ned Stark',
'location': null,
'avatarLink': null,
}
}
}
GraphQL solves following 2 major problems of REST API implementation →
- Unnecessary data fetching leads to utilizing high bandwidth (Over fetching)
- Unnecessary implementation of different endpoints for different functions leads to complexity and need to do multiple calls from front end to get required data. (Under fetching)
Resolver
A resolver is a function that’s responsible for populating the data for a single field in the schema, which could be by fetching data from the backend or via a third party API. The basic function is to convert operations into data.
Security Concerns
The most basic security concern is that GraphQL does not provide authorizations checks for any actions to read or modify data through it.
GraphQL endpoints such as Playground and iGraphQL can be fuzzed for to find important information. A more complete list is as follows →
- /graphql
- /graphiql
- /graphql/console
- /graphql.php
- /graphiql.php
- /explorer
- /altair
- /playground
- /graphql-playground
- /graphql-explorer
GraphQL introspection query can be used to retrieve information such as queries and mutations from the schema. An introspection query for use through BurpSuite is as follows →
1
{"query":" query IntrospectionQuery {\n __schema {\n queryType { name }\n mutationType { name }\n types {\n ...FullType\n }\n directives {\n name\n description\n locations\n args {\n ...InputValue\n }\n }\n }\n }\n fragment FullType on __Type {\n kind\n name\n description\n fields(includeDeprecated: true) {\n name\n description\n args {\n ...InputValue\n }\n type {\n ...TypeRef\n }\n isDeprecated\n deprecationReason\n }\n inputFields {\n ...InputValue\n }\n interfaces {\n ...TypeRef\n }\n enumValues(includeDeprecated: true) {\n name\n description\n isDeprecated\n deprecationReason\n }\n possibleTypes {\n ...TypeRef\n }\n }\n fragment InputValue on __InputValue {\n name\n description\n type { ...TypeRef }\n defaultValue\n }\n fragment TypeRef on __Type {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n }\n }\n }\n }\n }\n }\n }\n }"}
The query part of the request without spaces whitespaces is as follows →
1
__schema{queryType{name},mutationType{name},types{kind,name,description,fields(includeDeprecated:true){name,description,args{name,description,type{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name}}}}}}}},defaultValue},type{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name}}}}}}}},isDeprecated,deprecationReason},inputFields{name,description,type{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name}}}}}}}},defaultValue},interfaces{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name}}}}}}}},enumValues(includeDeprecated:true){name,description,isDeprecated,deprecationReason,},possibleTypes{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name}}}}}}}}},directives{name,description,locations,args{name,description,type{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name}}}}}}}},defaultValue}}}
The same query but URL encoded is as follows →
1
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++++++}++++}++}}
The same query but with proper formatting (human readable) →
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
query IntrospectionQuery {
__schema {
queryType { name }
mutationType { name }
types {
...FullType
}
directives {
name
description
locations
args {
...InputValue
}
}
}
}
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
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
}
}
}
}
}
}
}
}
The result can be pasted into GraphQL voyager (in resources) to gain a graph based table visualization of queries available. The entire JSON object would need to be parsed to retrieve mutations. Otherwise, GraphiQL online can also be used to figure out mutations.
IDOR and AuthZ issues are common in GraphQL.