What better day to discuss a new class of attacks onto new API protocol tech than Friday the 13th! Beware of smoke covers and multiple requests mascaraing as one!
In this third installment of the series, we will continue to explore the popular GraphQL API technology. See the first and the second articles of the series for more information about GraphQL security. Contrary to the expectation of transparency, GraphQL introduces unexpected quirks to web application behavior.
One of these documented but not commonly used behaviors is the ability to send multiple queries with a single GraphQL request, a.k.a. batching—something never explored by security researchers before. We will call attempts to explore this behavior “GraphQL Batching Attacks”.
For those very familiar with application security already, an easy way to understand this attack vector is to compare it with XMLRPC Bruteforce Amplification, which plagued WordPress for years. Attack Batching is quite similar but for GraphQL. And not only for it.
The technique of batching the requests is described in the GraphQL spec published in June 2018 in section 6.3.1 and it describes this behavior as Normal and Serial Execution (Link: https://graphql.github.io/graphql-spec/June2018/#sec-Normal-and-Serial-Execution).
Here is a description from GraphQL.org official website:
GraphQL is designed in a way that allows you to write clean code on the server, where every field on every type has a focused single-purpose function for resolving that value. However, without additional consideration, a naive GraphQL service could be very “chatty” or repeatedly load data from your databases.
This is commonly solved by a batching technique, where multiple requests for data from a backend are collected over a short period of time and then dispatched in a single request to an underlying database or microservice by using a tool like Facebook’s DataLoader.
You can see how easy it is in an example of the serial execution in accordance with the spec:
The executor will execute the following serially:
There are several issues facing the developer using GraphQL when working on developing secure code. As mentioned before, it’s mainly related to bypassing limits and abusing rates.
First, not all implementations follow the spec to a tee. One example that we referenced previously was the ability in some implementations to turn off introspection, while others do not have that capability. This inconsistency of implementations is not uncommon in newer technologies like GraphQL.
Second, mutation operations that work with user authentication, must introduce a limit for the number of attempts. How can you limit the number of attempts if a single API call can request 10 000 attempts to enter a password, 2FA token, item request, etc?
It’s pretty clear that this check will need to exist at the code level and it’s up to a developer to verify a number of such attempts. This is a perfect place for mistakes and inconsistencies to occurs. As per Murphy’s law – “Anything that can go wrong will go wrong”.
Third, for the tools responsible for securing web applications, like WAFs and RASPs, it is challenging to identify abnormal server activity when each of the API requests can encapsulate thousands of malicious requests composing an attack.
Here are a few examples Wallarm has encountered when analyzing how to protect GraphQL applications
Authentication through GraphQL API with simultaneously sending many queries with different credentials to check it. It’s a classic brute force attack, but now it’s possible to send more than one login/password pair per HTTP request because of the GraphQL batching feature. This approach would trick external rate monitoring applications into thinking all is well and there is no brute-forcing bot trying to guess passwords.
We’ve already mentioned this at the beginning of this article, as something similar to XMLRPC Bruteforce Amplification attack for WordPress and other web applications.
Below you can find the simplest demonstration of an application authentication request, with 3 different email/passwords pairs at a time. Obviously it’s possible to send thousands in a single request in the same way:
As we can see from the response screenshot, the first and the third requests returned null and reflected the corresponding information in the error section. The second mutation had the correct authentication data and the response has the correct authentication session token.
In this specific application, the limit on authentication attempts was implemented on the web-server side, which is why we were not able to send a large number of requests to the authentication server.
Simply put, GraphQL allows us to send several mutation requests to receive session authentication for the application and try to guess the correct password with multiple attempts.
While application authentication is done by GraphQL, it’s not uncommon to implement two-factor authentication (2FA). Using the GraphQL batching attack, it’s possible to completely bypass one of the common second authentication factors, OTP (One Time Password), by sending all the tokens variants in a single request.
You can find this GraphQL request sample below:
The response screenshot shows three simultaneous attempts of inputting OTP in response to a single request. The correct code is only transmitted in the third mutation, while both the first and the second mutation return null and reflect the corresponding information in the error section.
Note that vulnerable GraphQL web application processed all the 3 “one-time” tokens at the same time, found a valid one, and logged us inside.
There is a new attack surface when the app tech stack includes GraphQL. How can these apps be protected?
The first line of defense is secure coding. One should be careful following the specification and pay attention when implementing specific methods that might require data filtration and rate limits. Not that this is anything new, but it’s always a good practice. Now you should think about that from your business logic perspective more than it was before because GraphQL is much closer to business logic.
Our basic advice is to think about every public function you wrote as an Internet-faced API endpoint.
But sometimes it’s not enough, especially when we are talking about business logic issues, rate limits, and some other things such as Introspection query disabling (we covered it in a previous blogpost). In such cases, we recommend using cloud-native API Web Application Firewalls like Wallarm.