Создание GraphQL Bombs, 2022 версии Zip Bombs

Эта статья была первоначально опубликована на blog.escape.tech.

Zip-бомбы ушли в прошлое, но концепция, лежащая в их основе, по-прежнему актуальна в наши дни. Действительно, ваше GraphQL-приложение может быть уязвимо к тому, что в этой статье мы будем называть GraphQL-бомбами. Читайте дальше, чтобы узнать, уязвимы ли вы и как защитить свое GraphQL-приложение!

Как работают почтовые бомбы?

Прежде чем погрузиться в эту тему, давайте уделим немного времени пониманию концепции, лежащей в основе zip-бомб.

Zip-файлы — это сжатые без потерь архивы, наиболее распространенный алгоритм сжатия которых называется deflate. Он работает путем поиска повторяющихся шаблонов в данных, а затем заменяет эти шаблоны гораздо более короткими маркерами.

Таким образом, повторяющаяся последовательность байтов после сжатия становится намного короче. Создание zip-бомбы заключается в тщательном составлении последовательности байтов, которые очень хорошо сжимаются, на несколько порядков. Затем, когда жертва распаковывает zip-файл, полученные данные будут намного больше, чем исходный архив.

Псевдослучайные запросы

GraphQL — это мощный язык с множеством малоизвестных особенностей. Одной из таких возможностей является возможность псевдозапросов.

Рассмотрим простой блог с запросом article(id: Int!). Если бы нам нужно было получить одну статью, мы бы сделали это следующим образом:

query {
  article(id: 1) {
    title
    author
  }
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Получится что-то вроде этого:

{
  "article": {
    "title": "Hello World!",
    "author": "John Doe"
  }
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Но в GraphQL есть хорошая функция, позволяющая разработчикам запрашивать один и тот же резолвер несколько раз с разными именами возврата:

query {
  first:  article(id: 1) { title author }
  second: article(id: 2) { title author }
  third:  article(id: 3) { title author }
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Это даст аналогичный результат, но article теперь будет псевдонимом first, second и 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"
  }
}
Вход в полноэкранный режим Выход из полноэкранного режима

Возможно, вы уже что-то заметили, и действительно, вы уже можете разработать первую уязвимость для использования этой особенности — алиасинг облегчает перебор:

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!
}
Вход в полноэкранный режим Выход из полноэкранного режима

Загрузка файлов

Вторая часть уязвимости требует включения функции загрузки файлов через GraphQL.

Многие популярные движки GraphQL поддерживают загрузку файлов в GraphQL, а некоторые даже нативно.

Спецификация GraphQL multipart описывает, как реализовать загрузку файлов в GraphQL. Если обычные запросы GraphQL отправляются в виде application/json, то загрузка файлов отправляется в виде multipart/form-data. Это означает, что тело HTTP-запроса состоит из нескольких частей, и их функции, описанные в спецификации, можно свести к следующему:

  1. Операционная часть содержит GraphQL-запрос. Это та часть, которая обычно отправляется в виде application/json.
  2. Часть map помогает серверу найти данные в теле запроса.
  3. Любая другая часть может быть использована в части операций, если она правильно отображена. Эти части могут содержать любой тип данных, с которыми может работать сервер, но обычно это изображения или двоичные данные.

Вот как выглядит загрузка фотографии профиля:

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--
Вход в полноэкранный режим Выход из полноэкранного режима

Здесь видно, что часть, имеющая name="file1", сопоставлена с variables.picture, что позволяет серверу найти файл в теле запроса.

Загрузка файлов в GraphQL работает почти так же, как и загрузка файлов в REST, по крайней мере, с точки зрения HTTP, а это значит, что уязвимости, существующие в REST, могут быть использованы в GraphQL. Но, к сожалению, это еще не все…

Бомбы GraphQL

Как вы уже догадались, GraphQL bombs объединяет две предыдущие функции, представленные в статье. Концепция заключается в следующем: обращение к одному и тому же файлу несколько раз с помощью псевдослучайных запросов.

Представим, что мы вызываем updateUserPicture(picture: File!) тысячу раз, используя псевдонимы, причем все вызовы ссылаются на один и тот же файл размером 1 МБ:

mutation {
  a1: updateUserPicture(picture: $picture)
  a2: updateUserPicture(picture: $picture)
  a3: updateUserPicture(picture: $picture)
  # ...
  a1000: updateUserPicture(picture: $picture)
}
Вход в полноэкранный режим Выход из полноэкранного режима

Этот запрос будет меньше 2 МБ, но в результате серверу придется обработать 1 ГБ данных.

В зависимости от того, что сервер делает с данными, этот запрос может вызвать истощение памяти или процессора, что приведет к снижению производительности или даже сбою сервера.

Смягчение последствий

Для устранения этой уязвимости необходимо предпринять несколько шагов:

  1. Правильно настройте ограничения сервера на загрузку файлов.
    • Для Apollo с graphqlUploadExpress
    • Для GraphQL Yoga
  2. Ограничьте использование пакетной обработки и алиасинга с помощью GraphQL Armor, проекта с открытым исходным кодом, разработанного Escape — GraphQL Security для устранения наиболее распространенных уязвимостей GraphQL.
  3. Если GraphQL Armor пока не поддерживает ваш движок, вы также можете попробовать graphql-no-batched-queries и graphql-no-alias.

Оцените статью
devanswers.ru
Добавить комментарий