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.
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 usualapplication/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 can streamline this process by quickly identifying issues across your API endpoints and providing remediation snippets—all in as little as 15 minutes, without complex integrations or traffic monitoring.
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!
💡Interested in learning more about GraphQL? Check out the articles below:
- Avoid GraphQL Denial of Service attacks
- SQL Injection in GraphQL
- What are Insecure Direct Object References (IDOR) in GraphQL, and how to fix them
- Cross-Site Scripting (XSS) in GraphQL
- GraphQL Cyclic Queries and Depth Limiting