Common Security Flaws in GraphQL

GraphQL is a great technology to empower your frontend developers to build truly reliable software with end-to-end type safety, but are we accidentally exposing our servers with security vulnerabilities by implementing the technology?

This article will briefly address the most common ways that GraphQL servers differs from REST APIs when it comes to security, and different strategies to use to mitigate the vulnerabilities.

Costly requests

Since it is up to the clients to ask for what data they need, you could accidentally make it possible to create extremely expensive requests. The GraphQL server could for example generate a large number of requests to underlying services or databases if the calls are made from a field that is being returned in a list.

To mitigate this, you could do any (or all) of the following strategies:

Keep an allow-list for production requests

The most secure way to protect against costly requests is to manually review requests in pre-production environment, before adding them to an allow-list for production. Requests not on the allow-list would be flat-out rejected.

Pros

  • You have full control over what queries are being sent

Cons

  • Slows down frontend developers
  • Forgetting to update the allow-list before release might lead to your app breaking
  • Tooling could be better

Assign a “cost” to each query based on a heuristic of what you know of the system

When a request lands on your GraphQL server, you will programmatically check each field that is being requested in order to see if it looks malicious or not.

Pros

  • Sets some sort of reasonable upper limit

Cons

  • Hard to maintain as the schema evolves
  • It can be hard to create a good heuristic of real-world cost
  • Adds overhead to each request

HTTP level limits (timeout + body size)

If your slowest expected GraphQL request takes at most two seconds, then you could set a timeout of 5 seconds. Any request that exceeds this limit will trigger an emergency shutdown, and will therefore avoid any further expensive request.

In the same configuration you can often also set an upper limit to the payload size of the request body, to avoid megabytes of GraphQL requests coming in on the same HTTP request.

Pros

  • Easy to implement

Cons

  • If you set the limit too low you can trigger false positives
  • Can’t allow one kind of request to be slow/big, and require others to be fast/small.

Make expensive calls impossible

If you wrap all expensive calls in some sort of memoization, like Dataloaders, chances are that it will be extremely difficult to construct queries that will create much load of your systems.

Pros

  • Dataloaders are good practice anyway
  • Does not make security an afterthought, but as part of the core work

Cons

  • Parsing and validation of queries will still be possible
  • It is naive to think that you will protect all fields as the schema evolves

Exposing too much data

In GraphQL, since the clients can ask for “anything they want”, you might think that they could ask for things they are not allowed to read. That is not true if you implement your server with security in mind, but you might make mistakes.

The most common ways to protect your data is to either let underlying services do the security checks for you, or do it programmatically in resolvers. The mistakes often occur with the second strategy, where human error leads to forgetting to do the proper checks. One mistake I’ve seen has been to return protected data in the “unauthorized” error response, which clearly is a bad idea.

To mitigate this, you could do any (or all) of the following strategies:

Protect fields and types on a Schema level

You can protect your data using custom directives or other server-side abstractions if your runtime supports it. That way you could say something like the User type should only be accessible to admins.

Pros

  • Clear who can access what

Cons

  • You might leak more information about your security models than ideal
  • Too naive in most applications to contain all nuance of authorization

Good schema design

In the example I mentioned earlier about asking for protected data in an unauthorized response, the mutation response type was a type containing errors and data. If the mutation response instead was a union with errors OR data, then that could not have happened.

Pros

  • A schema which is more obvious regarding what data is being returned
  • Makes impossible states impossible

Cons

  • Can be a frustrating design limitation

Summary

GraphQL is not inherently more or less secure than regular REST APIs, as it comes down to doing it right. However, GraphQL is ever growing and a lot of GraphQL developers are still quite junior. That might lead a lot of human errors leaking through to the final API.

You should be monitoring your GraphQL server for what kinds of requests are being sent to make sure that you have no ongoing attack. You could also make sure to take note of existing slow requests to see how they could be modified for an attack. Hubburu is one tool you could use for this purpose. Go here to sign up.

Peter Nycander
Peter Nycander