Addressing the Security concerns of GraphQL Aliases

GraphQL aliasing is a powerful feature. But with great power comes great vulnerability: batch attacks and DoS. In this post, we explain how it works and how to remediate it in your GraphQL API.

Addressing the Security concerns of GraphQL Aliases

Some of your users start complaining that your web application is not secure and that someone managed to log into their accounts… You take a look at the user history and network activity but nothing looks wrong. However, you notice that your server is slower than usual.

Two days later, your application is down due to a DoS attack, i.e. too many requests crashed your service and it is not available anymore. But your network activity seems pretty quiet and you have a firewall in place that should have prevented the DoS. That’s weird, isn’t it?

From GraphQL Aliases to Batch Attacks

It comes from a super useful feature of GraphQL: aliases.

What are GraphQL aliases?

When making a query using GraphQL, the response has the same format as the query. But what about querying the same field with different attributes? Without aliases, this isn’t possible as JSON field names must be unique. Here is the example from the GraphQL documentation:

In the above example, the two hero fields would have conflicted, but since we can alias them to different names, we can get both results in one request.

This feature allows multiple queries to be sent with a single GraphQL request. This technique is called batching. Only one batch will transit on the network and then all queries will be executed sequentially.

Batch attacks with aliases

It is an easy way to bypass attempt and rate limits. Indeed, limits are usually set up on the number of API calls, but what if a single API call can create 10,000 database requests?

Tools responsible for securing web applications like firewalls and rate limiters cannot detect abnormal activities: they only monitor API calls…

Therefore, all your common protection mechanisms will fail to detect and deny a brute-force attack. So, an attacker can simply make one API call with hundreds of attempts to find user credentials (email, passwords). They can also bypass two-factor authentication (2FA) by sending all the token variants of the one-time password (OTP).

Here is a simple example that an attacker could use to try different credentials.

mutation {
	login(username: "Tom", password: "password")
    second: login(username: "Tom", password: "password123")
    third: login(username: "Tom", password: "TomTheBest")
}

And this is the server response.

{
	"login": null,
    "second": null,
    "third": "U12hshjy7187GFST67sljsqfyzj19snSHDghjQSzFsj"
}

In this case, the third password was the correct one and the attacker could now log in as the legitimate user with the authentication token.

One other consequence of this type of attack is Denial of Service (DoS). Indeed, if the attacker uses a huge number of aliases, your server will have a lot of work to do and may not be able to handle it. This causes a slowdown or even a complete shutdown, i.e. your server becomes unresponsive (DoS). And again your firewall can’t prevent this.


If you want to catch DoS vulnerabilities and 100+ other GraphQL security vulnerabilities before it's too late, checkout Escape. Run hundreds of security scans in your CI/CD 🚀


Protecting your GraphQL API against Batch Attacks

The alias functionality cannot be directly disabled (at least not in the current implementation), and as it is a useful feature it will not be the best option anyway. Instead, you must implement a validation process to ensure the query doesn’t contain more aliases than the chosen maximum.

Hopefully, different libraries have been created to solve this problem.

The GraphQL Query Complexity Analysis is a library that analyses the query and rejects it if it is too complex. To compute the complexity it takes into account several parameters and not only the number of aliases. Therefore, it can be really helpful but quickly hard to configure and if it is misconfigured, it will deny legitimate queries…

Perhaps the best solution for the aliases problem is the GraphQL No Alias library. It is simple to use and allows you to configure a maximum number of aliases for each query field. It can be used through directive or configuration options.

An example of using configuration options as it results in better performance (the schema is not analysed looking for directives):

const express = require('express')
const { graphqlHTTP } = require('express-graphql')
const { buildSchema } = require('graphql')

const { createValidation } = require('graphql-no-alias')

const permissions = {
  Query: {
    '*': 1, // default value for all queries
    getAnotherUser: 5 // custom value for specific query
  },
  Mutation: {
    '*': 1 //default value for all mutations
  }
}

// get the validation function
const { validation } = createValidation({ permissions })

const schema = buildSchema(/* GraphQL */ `
  type Query {
    getUser: User
    getAnotherUser: User
  }
  type User {
    name: String
  }
`)

const app = express()

app.use(
  '/graphql',
  graphqlHTTP({
    schema: schema,
    rootValue: root,
    graphiql: true,
    validationRules: [validation] //add the validation function
  })
)

app.listen(4000)

A good security practice is to deny by default. Here, it means denying aliases except when they are really useful. Therefore, in the configuration options, set the default value for query and mutation to 1 (i.e. '*': 1) and then add custom values for specific fields if necessary.

Finally, make sure you configure this library correctly for user authentication, as it is one of the biggest risks!

References