Practical GraphQL attack vectors

On a recent engagement, we found an instance of GraphQL on a server and I noticed that there are not many articles describing the different ways to attack GraphQL instances even though these are used by a lot of big names in the industry including Facebook, GitHub, Pinterest, Twitter, HackerOne and more.

So what is GraphQL?

Developed by Facebook in 2012 and publicly released in 2015, GraphQL is an open-source data query language for APIs that provides a more efficient alternative to the RESTful architecture by working with the data in a more structured and object-oriented way. One of the main benefits of GraphQL is that it isn’t tied to any one database or storage engine. Instead, it is backed by existing code and data meaning that unlike REST APIs where the client directly interacts with the code that is sent to the database, the client interacts with the GraphQL instance which in turn interacts with the database. A visual example of this is the following image (credit to devopedia.org):

GraphQL vs Rest API

This has the advantage that all the information the client requires can be accessed with a single query, instead of multiple queries directly to the database in the case of previous architectures. As incredibly useful as GraphQL is, there are however some downsides with it security-wise.

In this article, I will explain some of these downsides and show from a penetration testing perspective some methods to test for vulnerabilities and misconfigurations.

Tools

First of all, let’s talk a little bit about some of the tools that can help in the penetration process of GraphQL instances.
A nice extension for BurpSuite is GraphQL Raider available in the BApp Store for Burp Pro license owners. This extension has a built-in editor and scanner payloads that test for common vulnerabilities.
For Chrome users there is the GraphQL Network extension that is useful for fast querying directly from the browser.
But the most useful tool that I found for testing GraphQL instances is Insomnia. This is a GraphQL IDE that has automatic introspection (more on this later), autocomplete, and other advanced options that make it just really fun to work with. It’s a fast, simple, efficient and pleasant to look at tool that does the job when you are doing manual testing.

Common GraphQL vulnerabilities

Introspection

First and foremost when encountering a GraphQL endpoint you want to do an introspection. All default GraphQL instances come with the introspection system enabled which is used to ask a GraphQL schema for information about what queries it supports. This allows basically anyone to see the layout of the entire database. A quick and easy query for this is the following:

{
  __schema {
    types {
      name
    }
  }
}

Luckily, Insomnia does automatic introspection for us and after that, it enables autocomplete for everything, so there is no need to manually read all the queries and types and reconstruct everything from scratch. Even more, if the introspection is documented by the developers, that documentation is also available in the “show Documentation” tab of Insomnia, where we have detailed descriptions of every query. This is incredibly useful to find hidden queries that would normally not be implemented to every user and also allows us to get a better understanding of how the GraphQL instance works.

Documentation of the GraphQL schema

Documentation showing all possible queries and format

Missing access controls

By default, GraphQL instances do not implement access controls. It is left to the developers to implement proper access controls in the code of the application either between the HTTP server and the GraphQL server or in the resolver code or business logic code. So you can imagine that misconfigurations can happen, or developers simply forget to implement these access controls altogether.
In the case of access controls in the resolver or business logic code, errors are more likely to occur because more than one database can be tied to the GraphQL instance. If proper utilization is done for one database but not for the other, then all the contents that can be queried from that particular database can be accessed. Because of this, it is important to fuzz all possible queries for such misconfigurations.

SQL and NoSQL Injections

Even though GraphQL is strongly typed, SQL and NoSQL Injections are still possible since GraphQL is just a layer between the client and the database. The problem can reside in the layer developed to fetch variables from GraphQL queries in order to interrogate the database. Variables that are not properly sanitized lead to SQL or NoSQL injections.

An example of an SQL injection could be:

{
  login(
    input:{user:"admin" password:"password' or 1=1 -- -"})
      {
      success jwt
      }
}

While an example for a NoSQL injection could look like the following:


{
  users(
    search: "{password: { $regex: \".*\"}, lastName:Admin }")
      {
      firstName lastName id password
      }
}

We use a regex inside the search argument of the query in order to obtain information of the admin user.

Information disclosure

Even if introspection is disabled by the developer, GraphQL has the advantage (from the attackers perspective) to be incredibly verbose in its error messages.
For example, we can blindly start with the following invalid query and slowly build it into a valid query only by reading the error messages:

 query {games} 
1-4z
Querying an invalid “games” variable shows a verbose error message.
 query {getGames} 
1-5z
Querying the valid “getGames” vaiable shows us another verbose error that leads us to our next steps
 query {getGames{game}} 
1-6z
Querying an invalid subsection shows the valid “name” subsection that we need to use

If we continue to build the query we will eventually reach a valid one. This shows that a query can be constructed from scratch from verbose error messages even when we don’t have the benefits of introspection.

Bypassing rate limiting

Because of the nature of GraphQL, we can send multiple queries in a single request by batching them together. If the developers did not implement any kind of mechanism to prevent us from sending batch requests than we could potentially bypass rate limiting by sending the following queries in a single request:


mutation {login(input:{email:"a@example.com" password:"password"}){success jwt}}
mutation {login(input:{email:"b@example.com" password:"password"}){success jwt}}
....
mutation {login(input:{email:"x@example.com" password:"password"}){success jwt}}

As there is only one request sent from the client to the GraphQL instance, classical rate-limiting such as from a WAF does not apply.

Another attack scenario using a batching attack could be to bypass two-factor authentication (2FA).
While application authentication is done by GraphQL, it’s not uncommon to implement 2FA. Using the same kind of attack, it is possible to completely bypass one of the common second authentication factors, OTP (One Time Password), by sending all the tokens variants in a single request.

Denial of Service

Because limiting query depth is disabled by default, nested queries can be performed in order to cause a denial of service attack. Because of the nature of the GraphQL query language, multiple queries can be nested one inside the other, creating cascading requests to the database. This attack is similar in nature to the Billion Laughs attack found in the XML parser. An example of a nested query is the following:


query {
  posts{
    title
    comments{
      comment
      user{
        comments{
          user{
            comments{
              comment
              user{
                comments{
                  comment
                  user{
                    comments{
                      comment
                      user{
                      ...
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

Conclusions

As applications and infrastructures become more complex and interconnected so do opportunities for misconfigurations and errors become more prevalent. Because GraphQL leaves security in the hands of the developer, applications implementing this query language can be left insecure by default. The first line of defense in GraphQL as in any other internet-connected application is secure coding.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s