Join our newsletter

Defeating Controls with Array-based Query Batching

Defeating Controls with Array-based Query Batching

Shahar Binyamin & Dolev Farhi·

In the previous post, we discussed the way threat actors can optimize attacks to defeat authentication controls using query batching based on GraphQL aliases.

Aliases are great because they are part of the GraphQL specification as shown in section 2.5. So, when you run into a GraphQL API, you should expect to have aliases available at your fingertips.

Aliases have one disadvantage - they can only be applied to either queries, or mutations, but not both at the same time in a single HTTP request. Another approach to query batching is by using arrays.

It is possible to batch queries together by adding them to an array and sending them to a GraphQL server. This option is not available in all implementations and also isn’t specifically mentioned in the GraphQL specification.

It is quite easy to test whether a GraphQL API support arrays, here is how you can do this in Python using the requests library:

import requests

queries = [
  {"query":"query { __typename }"},
  {"query":"query { __typename }"}
]

requests.post('http://localhost:5013/graphql', json=queries)

If you prefer cURL, you can test whether the GraphQL server supports arrays like so:

curl -X POST http://example.inigo.local/graphql -H "Content-Type: application/json" -d '[{"query":"query { __typename }"}, {"query":"query { __typename }"}]' 

The response that comes back to queries sent in an array would include multiple data keys, each containing a separate response:

[
   {"data":{"__typename":"Query"}},
   {"data":{"__typename":"Query"}}
]

When GraphQL servers see an array (and when they support them), they will process each query one after another in sequence, and return a response once all of them are resolved. Here is how the Graphene-django implementation handles arrays.

Array GraphQL.png

The advantage of arrays is that you can mix queries and mutations together, like so:

import requests

queries = [
  {"query":"query { __typename }"},
  {"query":"mutation { createUser(user:”user1”) { id } }}"}
]

requests.post('http://example.inigo.local/graphql', json=queries)

So, if you have an API flow that requires sending a mix of queries and mutations together to get to some desired state, arrays make it easier. Aliases on the other hand can only be used to batch queries of the same root type.

Arrays have one major disadvantage - they aren’t available everywhere. We recommend reading the documentation of the GraphQL implementation in use in your production environment to learn if arrays are supported (or simply try a query against it to figure it out) and whether they can be disabled.

Whether you are using a popular GraphQL server or you’ve built your own, using Inigo’s GraphQL Security and Management platform works independently and is completely server-agnostic, which protects any GraphQL APIs and helps introduce security controls no matter what GraphQL server you use.