Exploiting GraphQL Batching Attacks Using Turbo Intruder
What Are Batching Attacks In GraphQL?
GraphQL allows for multiple queries to be sent to the server in one single request in order to reduce the number of requests [1] that the server has to process. Since GraphQL requests often happen in quick succession, this is a handy feature that many GraphQL libraries support. However, for a malicious actor, this feature is ripe for abuse depending on the queries and mutations implemented by an application.
A batching attack refers to abusing this batch query feature to perform many GraphQL operations within one single web request. The batching attack helps facilitate brute force attacks by reducing the total number of potential requests needed to be successful, reducing the overall attack time and complexity. Sensitive functionality such as verification of authentication codes (Two-Factor Authentication), login functionality, username enumeration, and identifier enumeration are prime candidates to be abused in GraphQL.
For additional details and examples around batching attacks refer to the OWASP Cheatsheet series [2].
GraphQL Batching Attacks
While researching GraphQL Batching Attacks, I found a couple of examples on the internet mostly related to proof of concepts for password brute forcing [3] and bypassing MFA [4] by sending all codes at once. While these are excellent examples, they did not truly demonstrate a full attack proof of concept exploit against these services, enumerating all possible values in a short period of time.
During a recent engagement, I observed an API built with GraphQL which allowed for the ability to retrieve Patient Health Information (PHI) when a valid “sequence number” was provided in a GraphQL query. This sequence number was randomly generated per account; however, it was not a lengthy unique user id (UUID) and could easily be enumerated. The only downside to this enumeration was that the keyspace was rather large, a nine (9) digit number (0-9) for the individual digits, resulting in 10^9 possible combinations (one billion). This would take quite a bit of time to bruteforce one by one. Instead, we turn to the GraphQL batching attack.
Initially, I created a simple python script which would generate the GraphQL query, append it to the JSON structure for the web request body, and send the POST request. This script would then have to have additional code added for threading in order to speed up this enumeration process. Fortunately, my coworker Tib3rius reminded me of the existence of Turbo Intruder and I could easily pop my python script into the plugin and perform my enumeration at scale.
Batch Attack Limitations
Most of the current GraphQL batching attack articles that I discovered in my research kept alluding to the fact that you would be able to send thousands and thousands [4] of queries via the batching attack, which got me really excited for this attack. If I could batch 10,000 sequence numbers for every POST request, rather than one number, that would allow me to reduce the number of possible web requests by a significant measure. 100,000 requests is a bit more manageable to send to a server in a short period of time compared to a billion. Of course, this is not stealthy, but that’s not the point.
However, when you actually try to craft a valid batching attack request, you are limited by a couple of factors:
- The total size of the POST request that the server will handle
- The size of the individual query you are trying to request. The length of the name and any arguments plays a significant role in the total amount of requests you can bulk send.
Fortunately, GraphQL allows you to easily strip out most of the data [5] in a given request and allows you to minify the request fairly well. If you are attempting to perform a batching attack, I highly recommend you perform the following optimizations:
- Return only the field that determines a valid or invalid state for the data type you are requesting. E.g, if you were requesting MFA codes, only return whether the code is valid or invalid
- In our real-world example, we were returning PHI such as name, date of birth, location, etc., alongside the valid sequence number. Since we just need valid sequence numbers, we can remove these PHI fields.
- Remove any newlines and as many spaces as possible from the query
- If possible, move variables inline into the function without using the variables structure
Original length: 273
Minified length: 143 (47% reduction)
In practice, I was not able to send 10,000 batch requests at once. I wasn’t able to get anywhere close to that, instead I could send less than 1,000 per query. While this still speeds up the bruteforce, it’s not as amazing as I thought it was going to be for my given scenario.
In order to identify my “limit” I simply did the following process:
- Generate 10,000 queries and added them to the body of a GraphQL Request
- Observed if the server would accept that many queries
- Reduced or increased the query size by a factor of 2 until I found my maximum allowed query size
In my case, I was only able to send around 500 queries with one batching request, which is much better than sending one per request.
Turbo Intruder
Now that I had my minified query and maximum allowed operations, it was time to start batching up the requests. Fortunately, Turbo Intruder, a Burp Suite extension [6], is fairly easy to use and allows for python scripts to be nearly dropped in without too many headaches if you’re not using a custom package. At its most basic design, a Turbo Intruder script can be broken down into the request operation and the response operation.
Within Turbo Intruder, we can simply format the POST request using the %s identifier.
For the request, we simply define our max operation size, the range of our sequence number in our example (which could be a MFA code, password list, etc.), and perform some simple string replacements for our JSON structure. While we could have actually formatted our GraphQL as a JSON object in Python, it’s a bit unnecessary for the simplicity of our data since we’ll be sending a string as the POST body. Lastly, we generate our payload over the range of our data and enqueue the request once we have generated the large string containing our maximum operations size.
For the response, we determine if an identifier in our response body is valid. This can be as custom as you would like it to be, but since we’re simply observing if a valid sequence number is returned, that is what we will monitor our response for and add a simple label to the Turbo Intruder table so we can quickly sort through the valid requests.
Finally, we run the attack and start observing batches of 500 sequence numbers, with several requests indicating valid sequence numbers were identified:
The sequence number is returned in the response of the Turbo Intruder request:
Since we were able to batch 500 requests for sequence number enumeration for every one web request, as well as perform multi-threaded requests for the server with Turbo Intruder, we were able to reduce the total number of requests by a significant factor. We would have to send nearly 2,000,000 HTTP requests if we wanted to completely enumerate the 9 digit sequence number. This is no longer out of the realm of possibility and much better than the billion requests we originally had to send performing the enumeration one by one. While the time savings are not instantaneous, we have moved the needle by a fairly significant measure and have come up with a more efficient and real world attack scenario.
Exploiting GraphQL Batching Attacks Using Turbo Intruder
GraphQL Batching Attacks can be very powerful to help drastically reduce the number of requests for a bruteforce attack. In the context of ID enumeration, depending on the size of the number of digits you are trying to enumerate, it is extremely easy to perform this type of enumeration. Without any additional lockout policies to prevent bruteforcing, GraphQL provides a handy interface for malicious actors to obtain sensitive information, at scale. However, this scale is significantly limited by the size of the query you are attempting to send as well as the maximum size the server will process.
Our team recommends organizations using GraphQL to look into preventions against batch queries, especially when GraphQL is used during authentication or data access controls without unique lengthy identifiers.
MORE FROM OUR TECHNICAL BLOG
Cyber Advisors specializes in providing fully customizable cyber security solutions & services. Our knowledgeable, highly skilled, talented security experts are here to help design, deliver, implement, manage, monitor, put your defenses to the test, & strengthen your systems - so you don’t have to.
Read more from our technical experts...
More Info Please
Sources
- https://www.apollographql.com/blog/apollo-client/performance/query-batching/ – GraphQL Query Batching Overview
- https://cheatsheetseries.owasp.org/cheatsheets/GraphQL_Cheat_Sheet.html#batching-attacks – OWASP GraphQL Guide
- https://github.com/nicholasaleks/CrackQL – GraphQL Batch Exploit Tool
- https://lab.wallarm.com/graphql-batching-attack/ – GraphQL Batching Attack Overview
- https://graphql.org/learn/queries/ – GraphQL Query Overview
- https://github.com/PortSwigger/turbo-intruder – Turbo Intruder Burp Suite Extension