DEV Community

Cover image for Forging GraphQL Bombs, the 2022 version of Zip Bombs
Gautier
Gautier

Posted on • Updated on • Originally published at blog.escape.tech

Forging GraphQL Bombs, the 2022 version of Zip Bombs

Zip Bombs are a thing of the past, but the concept behind them is still relevant nowadays. Indeed, your GraphQL application might be vulnerable to what we'll call GraphQL Bombs in this article. Read on to know if you're vulnerable and how to secure your GraphQL application!

How do zip bombs work?

Before diving into the topic, let's take a moment to understand the concept behind zip bombs.

Zip files are lossless compressed archives whose most common compression algorithm is named deflate. It works by finding repeating patterns in the data, then replacing these patterns with a much shorter token.

Zip compression works by replacing repeating patterns with tokens.

Therefore, a repetitive sequence of bytes will be much shorter after compression. Creating a zip bomb consists of meticulously crafting a sequence of bytes that compress very well, by several orders of magnitude. Then, when the victim decompresses the zip file, the resulting data will be much larger than the original archive.

Aliased queries

GraphQL is a powerful language with many not-well-known features. One of these features is the ability to alias queries.

Let's consider a simple blog with an article(id: Int!) query. If we were to fetch one article, we would do it like this:

query {
  article(id: 1) {
    title
    author
  }
}
Enter fullscreen mode Exit fullscreen mode

This will produce something like this:

{
  "article": {
    "title": "Hello World!",
    "author": "John Doe"
  }
}
Enter fullscreen mode Exit fullscreen mode

But GraphQL has a nice feature allowing developers to query the same resolver several times with a different return name:

query {
  first:  article(id: 1) { title author }
  second: article(id: 2) { title author }
  third:  article(id: 3) { title author }
}
Enter fullscreen mode Exit fullscreen mode

This will produce a similar result, but article is now aliased as first, second and third:

{
  "first": {
    "title": "Hello World!",
    "author": "John Doe"
  },
  "second": {
    "title": "Yay, second article!",
    "author": "Jane Doe"
  },
  "third": {
    "title": "That's a lot of articles",
    "author": "Jaune D'œuf"
  }
}
Enter fullscreen mode Exit fullscreen mode

You may already notice something there, and indeed, you can already design a first vulnerability to exploit this feature – aliasing facilitates brute-force:

mutation {
  a1: login(user: "john", password: "password") { id }
  a2: login(user: "john", password: "qwerty") { id }
  a3: login(user: "john", password: "123456") { id }
  # Let's try the most common passwords!
}
Enter fullscreen mode Exit fullscreen mode

File uploads

The second part of the vulnerability requires having enabled file uploads over GraphQL.

Many popular GraphQL engines support uploads in GraphQL, some even natively.

The GraphQL multipart specification describes how to implement file uploads in GraphQL. While usual GraphQL queries are sent as application/json, file uploads are sent as multipart/form-data. This means that the HTTP request body has multiple parts, and their functions, described in the specification, can be summarized as follows:

  1. The operations part contains the GraphQL query. This is the part that is usually sent as application/json.
  2. The map part helps the server to find data in the request body.
  3. Any other part can be used in the operations part as long as it is correctly mapped. These parts may contain any type of data the server can handle but are usually images or binary data.

Here is what a profile picture upload looks like:

POST /graphql HTTP/1.1
Connection: keep-alive
Content-Length: 78346
Content-Type: multipart/form-data; boundary=----boundaryMGv2RzA6GpOE3Hry
Host: example.com

------boundaryMGv2RzA6GpOE3Hry
Content-Disposition: form-data; name="operations"

{
  "query": "mutation ($picture: File!) {updateUserPicture(picture: $picture)}",
  "variables": { "picture": null }
}
------boundaryMGv2RzA6GpOE3Hry
Content-Disposition: form-data; name="map"

{
  "file1": ["variables.picture"]
}
------boundaryMGv2RzA6GpOE3Hry
Content-Disposition: form-data; name="file1"; filename="gautier.jpg"
Content-Type: image/jpeg

(77 kB of binary data)
------boundaryMGv2RzA6GpOE3Hry--
Enter fullscreen mode Exit fullscreen mode

You can see here that the part having name="file1" is mapped to variables.picture, allowing the server to find the file in the request body.

GraphQL file uploads work almost the same as file uploads in REST, at least from the HTTP perspective, which means that vulnerabilities that exist in REST can be exploited in GraphQL. But, unfortunately, that's not all…

GraphQL bombs

As you might have guessed, GraphQL bombs combine the two previous features introduced in the article. The concept is the following: referencing the same file several times using aliased queries.

Let's consider that we call updateUserPicture(picture: File!) one thousand times, using aliases, with all calls referencing the same 1 MB file:

mutation {
  a1: updateUserPicture(picture: $picture)
  a2: updateUserPicture(picture: $picture)
  a3: updateUserPicture(picture: $picture)
  # ...
  a1000: updateUserPicture(picture: $picture)
}
Enter fullscreen mode Exit fullscreen mode

GraphQL bombs consist in referencing the same file several times.

This request would be less than 2 MB but would result in 1 GB of data for the server to process.

Depending on what the server does with the data, this request may cause memory or CPU exhaustion, leading to decreased performance or even a server crash.

Mitigation

There are several steps necessary to mitigate this vulnerability:

  1. Properly configure the server limits to file uploads.
  2. Limit the usage of batching and aliasing with GraphQL Armor, an open-source project developed by Escape - GraphQL Security to address the most common GraphQL vulnerabilities.
  3. If GraphQL Armor does not support your engine yet, you may also try graphql-no-batched-queries and graphql-no-alias.

Top comments (2)

Collapse
 
dwhitz profile image
dwhitz

Great article

Collapse
 
tristankalos profile image
Tristan Kalos

This research blew my mind. Imagine using Zip attacks on GraphQL and it works...