Read Time:4 Minute, 18 Second

We at Escape have been scanning GraphQL APIs for vulnerabilities for more than two years. In this post, we will share the most common GraphQL vulnerabilities, affecting close to all GraphQL APIs we have scanned. We strongly recommend you to check your GraphQL APIs for these vulnerabilities.

Unlimited query complexity

GraphQL engines ship without complexity limits by default, allowing you to ship complex applications rapidly, but also enabling attackers to perform expensive queries. Cycles in the graph can lead to arbitrarily deep queries, which cause degraded performance or even denial of service.

# An exemple of cycle allowing an infinitely deep query
query { article(id: 1) { authors { articles { authors { articles { authors { articles { # ... } } } } } } }

You should set a sensible depth limit, depending on the performance and dependability of your servers. A maximum depth of 5 nested fields gives lot of room for frontend development and should work for most applications, while blocking unambiguously malicious queries.

📖 Read more about this vulnerability and possible remediations

Injections of all sorts

GraphQL is a query language with user-supplied input. This means that your API might be vulnerable to software injections targeting the underlying database, file system, operating system or even network.

{ "query": "mutation Login ($username: String!, $password: String!) { login(username: $username, password: $password) { token }}", "variables": { "username": "admin", // SQL injection on the password field "password": "admin' OR 1=1 --" }

To prevent injections, you should sanitize user inputs with the appropriate tools and not generic escaping functions.

Missing rate limiting

If your API is public, you should limit the number of request authenticated and unauthenticated users can perform each minute.

There are various ways to implement rate limiting, and choosing one depends on what you are trying to achieve:

  • Whole API rate limiting: limit the number of incoming request, regardless of their content. The validation should happen before the GraphQL engine is called, to prevent attackers from initiating request parsing and thus affecting performance.
  • Per query rate limiting: limit the number of resolution for specific (usually expensive) queries and mutations. For instance, this solution is relevant if your goal is to enforce third-party rate limiting.

📖 Read more about implementation details

Properly configured HTTP headers are a security quick win. They can prevent a lot of attacks, such as cross site request forgery (CSRF), MIME sniffing, and more, with just a few lines of code.

The most important headers are:

  • Access-Control-Allow-Origin: gives your users additional security in their browser, by preventing other websites from performing requests to your API, thus preventing CSRF attacks.
  • X-Content-Type-Options: nosniff disallows browsers from interpreting the response as a different content type than the usual application/json used for GraphQL.
  • Content-Security-Policy: script-src 'none' disallows browsers from executing scripts in the response, preventing XSS attacks.

There are many libraries that can help you configure these headers, such as helmet and cors.

Debug mode

This is a very common mistake because it is easy to make. When developing your API, you might want to enable the debug mode of your GraphQL engine, to get more information about the queries and mutations that are being executed. When something goes wrong, a GraphQL engine in debug mode will give the whole stacktrace as part of the response, disclosing precious details about your API structure.

The common solution for this vulnerability is to have several environments, usually at least a development and a production environment, set by an environment variable.

📖 Read more about this vulnerability and possible remediations

GraphQL Bombs

Escape’s security research released this vulnerability in August 2022. It affects APIs that implement GraphQL file uploads.

GraphQL Bombs are about creating an abnormal amount of work out of a single HTTP request to a GraphQL endpoint, leveraging GraphQL uploads and aliasing.

📖 Read the vulnerability announcement, detailing exploitation and remediation

Missing access control

A simple programming mistake can lead to a serious vulnerability. Take the time to review the code of your resolvers for missing or incorrect access control.

For instance, if you are using Pothos, your resolvers will look like this:

// A mutation resolver that allows users to create articles
builder.mutationField('createArticle', (t) => t.field({ type: ArticleType, args: { title: t.arg.string(), body: t.arg.string(), }, authScopes: { user: true }, resolve: async (_, { id }) => { // ... }, })
); // A mutation resolver that allows users (oops) to delete articles
builder.mutationField('deleteArticle', (t) => t.field({ type: ArticleType, args: { id:, }, // `authScopes` is missing, anyone can delete any article resolve: async (_, { id }) => { // ... }, })

Zombie objects and legacy resolvers

While not a vulnerability as such, zombie objects are the proof of a bad design. A zombie object is an object that is defined in a schema, but not used by any resolver. They can be leftovers from previous versions of the API, or simply a mistake. Take the time to remove all legacy code from your API as the lack of maintenance can lead to vulnerabilities.

Closing words

We hope you enjoyed this article and that you found it interesting. Feel free to share your experience and your tips where you found this article, we are eager to read you.


WP Ad Inserter plugin for WordPress