Join our newsletter

Defeating Controls with Alias-based Query Batching

Defeating Controls with Alias-based Query Batching

Shahar Binyamin & Dolev Farhi·

GraphQL isn’t immune to vulnerabilities, it may suffer from them just like any other API technologies such as REST, SOAP, gRPC, or others, but there are some unique and interesting possibilities that open up to hackers when GraphQL is present on the target they are interested in compromising.

Vulnerability classes such as Injection, Denial of Service, Forgery, Lack of Resource & Rate Limiting as well as others, can all apply to GraphQL APIs. However, what’s interesting in GraphQL is the new ways it allows threat actors to optimize certain attacks, specifically those that fall under the category of denial of service and defeating authentication mechanisms.

Secured GraphQL.png

Aliases in GraphQL allow clients to rename response keys of fields and use the same query operation more than once. Let’s first see what happens when a client asks for simple information such as the ID of users:

query {
  users {
    id
  }
}

And the response:

{
  "data": {
    "users": [
      {
        "id": "1"
      },
      {
        "id": "2"
      }
    ]
  }
}

If a client attempts to fetch the same information more than once this way:

query {
  users {
    id
  }
  users {
    id
  }
}

Then the response returned by the GraphQL server will only return a single users key in the response:

{
  "data": {
    "users": [
      {
        "id": "1"
      },
      {
        "id": "2"
      }
    ]
  }
}

The JSON structure we get back from the GraphQL server returns a single key, so the response of two operations is consolidated to a single response key.

Aliases in GraphQL allow you to rename the operation name (and hence the JSON key in the response). Aliases can be configured by prefixing the field of interest with some named alias:

query {
  nameOne: users {
    id
  }
  nametwo: users {
    id
  }
}

Which results in the GraphQL server returning the following response:

{
  "data": {
    "nameOne": [
      {
        "id": "1"
      },
      {
        "id": "2"
      }
    ],
    "nametwo": [
      {
        "id": "1"
      },
      {
        "id": "2"
      }
    ]
  }
}

This allows you to run multiple queries and rename their response key, which allows you to get separate responses.

GraphQL APIs just like any other API can perform sensitive actions such as:

  1. User login
  2. User password resets
  3. User signup
  4. Token verification
  5. Code verification

This list is just the tip of the iceberg, there could be more options, but you get the point.

Back to aliases - aliases allow you to “stuff” multiple operations in a single HTTP request. Does that ring any of your security bells? It should, because traditional security controls could struggle identifying malicious behavior if they rely on certain characteristics that are more relevant to non-GraphQL based APIs. For example:

  • HTTP Method (POST/GET)
  • API Route (e,g, /v1/login)
  • Number of clients requests made (e.g. 100 per minute)
  • The returned HTTP code

Looking at all of these as indicators of potential compromise attempt makes sense in the context of security: If a client makes a POST request to the /v1/login endpoint 100 times in 5 seconds and the response code is 401 Unauthorized, maybe this client is attempting to brute force accounts on your application. These heuristics make sense in non-GraphQL APIs. In GraphQL, the heuristics are a little different.

In GraphQL, the client intention is not expressed by the HTTP verb, route, or the returned code (in many cases) but by the query payload. Determining how the GraphQL server handled the query is expressed in the response versus any HTTP headers.

So, if you have a login GraphQL query that takes in an email and password, it would look like the following:

query {
  login(email:"[email protected]", password:"abc") {
    token
  }
}

With aliases, we can now perform multiple login attempts and send those over a single HTTP request:

query {
 attemptOne: login(email:"[email protected]", password:"abc") {
    token
 }
 attemptTwo: login(email:"[email protected]", password:"def") {
    token
 }
 attemptThree: login(email:"[email protected]", password:"geh") {
    token
 }
}

This query structure could circumvent security controls that perform rate limiting that bases its heuristics on the volume of client requests combined with the HTTP method and API route being used. As GraphQL adopters, it’s important to find a solution that can protect your APIs and identify malicious behavior more tailored to GraphQL APIs.

Aliases can be used to batch any query (or mutation) but not both at the same time. In the next post, we will cover another method of query batching that allows mixing these two types together.