The 8 most common GraphQL vulnerabilities

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 check your GraphQL APIs for these vulnerabilities.

The 8 most common GraphQL vulnerabilities
The 8 most common GraphQL vulnerabilities you should be afraid of

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

Badly configured HTTP headers

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: https://frontend.example.com 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: t.arg.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.


Is my GraphQL API vulnerable to one of these vulnerabilities?

Given that GraphQL is relatively new and may lack adequate tooling, numerous development teams are neglecting security measures. In practical terms, how can you ensure that your development team consistently avoids these 8 vulnerabilities over time and facilitates the ongoing evolution of your codebase?

You can always test your endpoints with Escape! Our state-of-the-art GraphQL scanner allows security engineers to build a comprehensive inventory of their GraphQL APIs and find and fix bugs in GraphQL applications during the development lifecycle (in CI/CD) before they even reach production!

It understands the business logic of GraphQL APIs and looks for more than 50 kinds of vulnerabilities so that you never have to worry again about:

  • Resolver performance (N+1 issues, cyclic queries, query complexity DOS…)
  • Tenant isolation (access control, data segregation between users…)
  • Sensitive data leaks (personally identifiable information, tokens, stack traces, secrets…)
  • Injections (SQL, NoSQL, XSS…) and requests forgery
  • Compliance & reporting: PCI-DSS, ISO-27001, HIPAA, etc.
  • … and more than 50+ advanced security issues!
Escape's app interface

We constantly update our engine with state-of-the-art GraphQL Security research, so that you never have to worry about the security of your GraphQL application again!

With no agent needed to be installed, you can get your results in one minute!

Want to learn more?

💡Interested in learning more about GraphQL? Check out the articles below:

💡
Do you prefer hands-on learning about GraphQL Security? Start your lessons with our API Security Academy focused on GraphQL and learn how to build safe GraphQL APIs.