GraphQL Cheat Sheet

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.

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