Exploiting GraphQL for Fun and Bounties

What is GraphQL

As per graphql.org:

GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data. GraphQL provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need and nothing more, makes it easier to evolve APIs over time, and enables powerful developer tools.

Let's simplify this definition; GraphQL is very similar to ordering food at Subway. At subway you have the power to ask the server exactly what you want to eat. You can specify the type of bread, the ingredients, and any specific sauce you want. Then the server gives you exactly what you asked for.

In the same way, GraphQL is a query language that allows you to request data from a server. Instead of getting a fixed set of data, like in traditional APIs, you can send a query to the server specifying exactly what data you need. You define the structure of the data you want and the server responds with that specific data, nothing more and nothing less.

GraphQL Operations

A GraphQL operation is a request made to a GraphQL server to perform a specific action, such as retrieving data (query) or modifying data (mutation). It follows a structured syntax and is defined using the GraphQL query language.

GraphQL operations consist of the following components:

1. Operation Type:

2. Operation Name:

A user-defined name given to the operation. It helps in identifying and referencing the operation when needed.

3. Selection Set:

A set of fields that define the specific data requirements for the operation and nothing more, avoiding over-fetching and under-fetching of data.

GraphQL Operation Example

5. Fragments:

Fragments are used in GraphQL to reduce duplication and reuse common field selections in queries. They allow you to define a set of fields once and include them in multiple queries. By using fragments, you can make your queries more concise and maintainable.

GraphQL Fragments Example

In the above example, the userFields fragment is defined once and can be reused in GetUser queries, reducing duplication, and improving maintainability.

What is Introspection?

Introspection allows clients to query the GraphQL server about its schema. With Introspection we can discover and explore the entire API surface, making it easier to understand and interact with the available data and operations.

We can get the schema of GraphQL by querying the __schema field, always available on the root type of a Query. Let's understand this by a simple example.

Introspection query to get all types:

query AllTypesQuery {
  __schema {
    types {
      name
    }
  }
}

Get Schema if Introspection is disabled

Having the schema makes it simpler for us to exploit it since we have the schema and know which queries and mutations are accessible. What if introspection is disabled?

By default, GraphQL server comes with an in-built capability to hint at the correct field names to use in either queries or mutations if the wrong ones are specified in the request.

Field Suggestions

🛑 NOTE: This is not a vulnerability; its a feature which we can leverage to see graphql schema. So don't report this to bug bounty platforms.

By leveraging this capability, an attacker could conduct a bruteforce attack to discover the GraphQL schema.

I will list some tools that can be used to automate this:

Abusing GraphQL Batching

GraphQL batching is an optimization technique that allows multiple GraphQL queries to be merged and sent together in a single request. Upon reaching the server, all the queries in the batch are executed one after another.

Since GraphQL allows batching, an attacker can craft multiple login mutation requests with different OTP codes in a single batch. The attacker could send an array of login mutations, each with a different OTP code, to the server in one HTTP request.

[
  {
    "query": "mutation { login( otp: '111111' ) { accessToken expiresAt }}"
  },
  {
    "query": "mutation { login( otp: '222222' ) { accessToken expiresAt }}"
  },
  {
    "query": "mutation { login( otp: '333333' ) { accessToken expiresAt }}"
  }
  ...
]

If the server had proper rate limits in place then we would fail this bruteforce attack after 5-10 attempts but using graphql we can try every combination in just one single request and the server would process each query one after another bypassing rate limit.

Common Weaknesses in GraphQL and REST APIs

Vulnerabilities such as IDOR, SQLi and CSRF are present in Graphql, just as with REST APIs. Graphql is not inherently secure; it is merely a query language. To execute a query, developers must write resolver functions which then execute the query on the backend. These resolvers can create human errors which often lead to access control issues and privilege escalation.

Example Scenario: A Note-Taking Application

Consider a simple GraphQL API for a note-taking application where users can create, read, update, and delete their notes. Each note has an id, title, content, and userId to associate it with the creator.

1. Vulnerable Query - Read Note:

query {
  noteById(id: "123") {
    id
    title
    content
  }
}

In this query, the noteById field fetches the note by its id. However, if proper authorization and access control checks are not in place, an attacker can easily modify the id parameter and request notes that belong to other users.

2. Vulnerable Mutation - Modify Note:

mutation {
  editNote(id: "123", content: "This book is great!") {
    id
    title
    content
   }
}

In this mutation, the editNote field edits the content of note by its id. However, if proper authorization and access control checks are not in place, an attacker can easily modify the id parameter and change notes that belong to other users.

Some Useful Tools