GraphQL Vulnerabilities
Last updated
Last updated
GraphQL is a query language designed to build client applications by providing an intuitive and flexible syntax and system for describing their data requirements and interactions. GraphQL uses a declarative approach to fetching data, clients can specify exactly what data they need from the API. As a result, GraphQL provides a single endpoint, which allows clients to get the necessary data, instead of multiple endpoints in the case of a REST API.
GraphQL server uses a schema to describe the shape of available data. This schema defines a hierarchy of types with fields that are populated from back-end data stores. The schema also specifies exactly which queries and mutations are available for clients to execute.
Most of the schema types have one or more fields
. Each field returns data of the type
specified. Every type definition in a GraphQL schema belongs to one of the following categories:
Object, this includes the three special root operation types
:
Scalar types always resolve to concrete data. GraphQL provides the following default scalar types:
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
(serialized as a String
) a unique identifier that's often used to refetch an object or as the key for a cache. Although it's serialized as a String, an ID is not intended to be human‐readable.
These primitive types cover the majority of use cases. However, it is possible to create custom scalar types.
Most of the types in a GraphQL schema are object types. An object type contains a collection of fields
, each of which has its own type
. Two object types can include each other as fields.
The Query
type is a special object type that defines all of the top-level entry points for queries that clients execute against a server. Each field of the Query type defines the name and return type of a different entry point.
You can use the Query type to fetch data about users as follows:
All fields within the Query operation are requested parallelly.
The Mutation
type is similar in structure and purpose to the Query type. Whereas the Query type defines entry points for read operations, the Mutation type defines entry points for write
operations. This type is optional. Each field of the Mutation type defines the signature and return type of a different entry point.
You can use the Mutation type to create a new user as follows:
Fields within the Mutation operation are requested sequentially.
The Subscription type is used for notifying users of any changes, which have occured in a system. This type is optional.
The Subscription type works the following way:
A client subscribes on some action and creates a connection with the server (commonly via WebSocket).
When this action is occured the server sends a notification via the created connection.
You can subscribe to create a new user as follows:
Input
types are special object types that allow you to provide hierarchical data as arguments to fields (as opposed to providing only flat scalar arguments). Each field of an input type can be only a scalar, an enum, or another input type.
The Enum
is similar to a scalar type, but its legal values are defined in the schema. Enums are most useful in situations where the user must pick from a prescribed list of options.
The Union
type declares which object types are included in the union. A field can have a union (or a list of that union) as its return type. In this case, it can return any object type that's included in the union.
All of a union's included types must be object types (not scalars, input types, etc.).
An interface specifies a set of fields that multiple object types can include. If an object type implements an interface, it must include all of that interface's fields.
A field can have an interface (or a list of that interface) as its return type. In this case, it can return any object type that implements that interface.
GraphQL defines the introspection schema, which is used to ask a GraphQL for information about what queries it supports. You can fetch introspection schema with the following query:
You can also use various GraphQL IDEs or GraphQL Voyager for introspection.
However, developers can forbid introspection of their applications. In this case, you can try to obtain the schema with the clairvoyance
.
Typically, an introspection schema looks as the following one:
The queryType
, mutationType
, subscriptionType
define the names of fields that contain list of corresponding queries supported by an application. In other words, if the queryType
name is QueryRoot
you can find all supported the Query types inside the element of the data.__schema.types
list with QueryRoot
name.
The types
contains all supported variables and queries.
The directives
contains a list of supported directives.
For example, the following schema defines the User
object and the allUsers
query that returns the list of Users
:
GraphQL resolvers can be implemented as a REST API gateway and use the provided parameters to craft and send requests to the API. It the parameters are not validated properly resolvers can be vulnerable to SSRF.
Suppose, the GrapghQL scheme contains a query that provide information about an user by ID:
The resolver can look like this (pseudocode):
In this case, you can send request with the following queries:
This results in the following GET requests:
This is possible since the ID Scalar should be serialized as a string and "1337/friends/1" is a valid string.
GraphQL engines are used to implement GraphQL API. The engines can have vulnerabilities or be misconfigured.
In order to determine which engine is used you can use the graphw00f
.
GraphQL does not define any access control by design. Developers implement an access contol inside resolve-methods and a business logic code. So try to bypass access control checks with the techniques used in the case of the REST API.
References:
Tool: AutoGraphQL + How to use guide
Try to change the Content-Type
header to get CSRF:
Try to send GET
requests instead of POST
ones to get CSRF.
References:
The GraphQL specification allows multiple requests to be sent in a single request by batching them together. If the developers did not implement some mechanism to prevent the sending of batch requests, you could potentially bypass the rate limit by sending queries in a single request.
By default GraphQL does not restrict length of queries. The GraphQL queries can be nested one inside the other and create cascading requests to the database. As a result, nested queries can be performed in order to cause a denial of service attack:
GraphQL has a nice and expressive way of returning errors. However, error messages can be too informative. Try to cause errors, for instance by fuzzing parameters, error messages can reveal details about the error, actual paths on the system, chunks of code or queries, etc.
Even though GraphQL is strongly typed, SQL, NoSQL and Command injections are still possible since GraphQL is just a layer between a client and backend.
Often the GraphQL API is exposed a lot of information, such private data, debug information, hidden data or stacktraces.
References: