Главная » Хабрахабр » Пентест приложений с GraphQL

Пентест приложений с GraphQL

Технологию используют такие компании, как: Facebook, Twitter, PayPal, Github и другие, а это значит, что пора разобраться, как тестировать такое API. В последнее время GraphQL набирает всё большую популярность, а вместе с ней растёт и интерес со стороны специалистов информационной безопасности. Этот язык запросов активно развивается и всё больше компаний находят ему практическое применение. В этой статье мы расскажем о принципах этого языка запросов и направлениях тестирования на проникновение приложений с GraphQL.
Зачем нужно знать GraphQL? В рамках программ Bug Bounty популярность этого языка тоже растёт, интересные примеры можно посмотреть здесь, здесь и здесь.

Подготовка

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

Для взаимодействия с различными API лучше использовать IDE для GraphQL:

Последнее IDE мы и рекомендуем: в Insomnia удобный и простой интерфейс, есть множество настроек и автодополнение полей запросов.

Перед тем, как перейти непосредственно к общим методам анализа безопасности приложений c GraphQL, вспомним основные понятия.

Что такое GraphQL?

GraphQL — язык запросов для API, призванный обеспечить более эффективную, мощную и гибкую альтернативу REST. В его основе лежит декларативная выборка данных, то есть клиент может точно указать, какие именно данные ему нужны от API. Вместо нескольких конечных точек API (REST) GraphQL представляет единую конечную точку, которая предоставляет клиенту запрашиваемые данные.

Основные различия между REST и GraphQL

Обычно в REST API вам необходимо получать информацию с разных конечных точек. В GraphQL для получения тех же данных вам необходимо сделать один запрос с указанием данных, которые вы хотите получить.

Опять же, GraphQL выдаёт точно запрашиваемую информацию.
Полезным дополнением будет то, что в GraphQL есть схема, описывающая, как и какие данные клиент может получить. REST API предоставляет ту информацию, которую в API заложит разработчик, то есть в случае, если вам необходимо получить больше или меньше информации, чем предполагает API, то нужны будут дополнительные действия.

Виды запросов

В GraphQL существует 3 основных вида запросов:

  • Query
  • Mutation
  • Description

Query

Запросы Query используются для получения/чтения данных в схеме.

Пример такого запроса:

query
}

В запросе указываем, что хотим получить имена всех пользователей. Помимо имени мы можем указать и другие поля: age, id, posts и др. Чтобы узнать, какие именно поля мы можем получить, нужно нажать Ctrl+Пробел. В данном примере мы передаём параметр, с которым приложение вернёт первые две записи:

query { allPersons(first: 2) { name }
}

Mutation

Если тип query нужен для чтения данных, то тип mutation нужен для записи, удаления и изменения данных в GraphQL.

Пример такого запроса:

mutation { createPerson(name:"Bob", age: 37) { id name age }
}

В этом запросе мы создаём пользователя с именем Bob и возрастом 37 (эти параметры передаются как аргументы), во вложении (фигурных скобках) указываем, какие данные мы хотим получить от сервера после создания пользователя. Это нужно для того, чтобы понять, что запрос выполнен успешно, а также для получения данных, которые сервер генерирует самостоятельно, такие как id.

Subscription

Он нужен для оповещения пользователей о каких-либо изменениях, произошедших в системе. Ещё один вид запросов в GraphQL — subscription. Работает это так: клиент подписывается на какое-то событие, после чего с сервером устанавливается соединение (обычно через WebSocket), и, когда это событие происходит, сервер отсылает клиенту уведомление по установленному соединению.

Пример:

subscription { newPerson { name age id }
}

Когда создастся новый Person, сервер отошлёт клиенту информацию. Наличие запросов subscription в схемах встречается реже, чем query и mutation.

Стоит отметить, что все возможности по query, mutation и subscription создаёт и настраивает разработчик конкретной API.

Факультатив

На практике разработчики часто используют alias и OperationName в запросах для внесения ясности.

Alias

GraphQL для запросов предусматривает возможность alias, которая может облегчить понимание того, что именно запрашивает клиент.

Предположим, что у нас есть запрос вида:

{ Person(id: 123) { age }
}

который выведет имя пользователя с id 123. Пусть имя этого пользователя будет Vasya.

Чтобы в следующий раз не ломать голову, что выведет данный запрос, можно сделать вот так:

{ Vasya: Person(id: 123) { age }
}

OperationName

Помимо alias в GraphQL используется OperationName:

query gettingAllPersons { allPersons { name age }
}

OperationName нужен для пояснения того, что именно делает запрос.

Пентест

После того, как мы разобрались с основами, переходим непосредственно к пентесту. Как понять, что приложение использует GraphQL? Вот пример запроса, в котором есть GraphQL-запрос:

POST /simple/v1/cjp70ml3o9tpa0184rtqs8tmu/ HTTP/1.1
Host: api.graph.cool
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:65.0) Gecko/20100101 Firefox/65.0
Accept: */*
Accept-Language: ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Referer: https://api.graph.cool/simple/v1/cjp70ml3o9tpa0184rtqs8tmu/
content-type: application/json
Origin: https://api.graph.cool
Content-Length: 139
Connection: close {"operationName":null,"variables":{},"query":"{\n __schema {\n mutationType {\n fields {\n name\n }\n }\n }\n}\n"}

Некоторые параметры, по которым можно понять, что перед вами GraphQL, а не что-то иное:

  • в теле запроса имеются слова: __schema, fields, operationName, mutation и др.;
  • в теле запроса много символов "\n". Как показывает практика, их можно убрать, чтобы было удобнее читать запрос;
  • часто путь отправки запроса на сервер: ⁄graphql

Отлично, нашли и определили. Но куда вставлять кавычку как узнать, с чем нам нужно работать? На помощь придёт интроспекция.

Интроспекция

В GraphQL предусмотрена схема интроспекции, т.е. схема с описанием данных, которые мы можем получить. Благодаря этому мы можем узнать, какие запросы существуют, какие аргументы им можно/нужно передавать и многое другое. Заметим, что в отдельных случаях разработчики намеренно не разрешают возможность интроспекции их приложения. Тем не менее, основное большинство всё же оставляет такую возможность.

Рассмотрим основные примеры запросов.

Получение всех видов запросов Пример 1.

query { __schema { types { name fields { name } } }
}

Формируем запрос query, указываем, что хотим получить данные по __schema, а в ней типы, их имена и поля. В GraphQL существуют служебные имена переменных: __schema, __typename, __type.

В ответе мы получим все типы запросов, их имена и поля, которые существуют в схеме.

Получение полей для конкретного вида запроса (query, mutation, description) Пример 2.

query { __schema { queryType { fields { name args { name } } } }
}

Ответом на данный запрос будут все возможные запросы, которые мы можем выполнить к схеме для получения данных (вид query), и возможные/необходимые аргументы для них. Для некоторых запросов указание аргумента(ов) обязательно. Если выполнить такой запрос без указания обязательного аргумента, сервер должен выдать сообщение с ошибкой, что необходимо его указать. Вместо queryType мы можем подставлять mutationType и subscriptionType для получения всех возможных запросов по мутациям и подпискам соответственно.

Получение информации о конкретном типе запроса Пример 3.

query { __type(name: "Person") { fields { name } }
}

Благодаря такому запросу мы получим все поля для типа Person. В качестве аргумента вместо Person мы можем передавать любые другие имена запросов.

Теперь, когда мы можем разобраться с общей структурой тестируемого приложения, давайте определим, что же мы будем искать.

Information disclosure

Именно поэтому при тщательной интроспекции можно найти что-нибудь интересное, например: ФИО пользователей, номера их телефонов и другие критичные данные. Чаще всего приложение, использующее GraphQL, состоит из множества полей и типов запросов, и, как известно многим, чем сложнее и больше приложение, тем сложнее его настраивать и следить за его безопасностью. Так в рамках пентеста в одном из приложений были обнаружены данные пользователей: ФИО, номер телефона, дата рождения, некоторые данные карт и пр. Поэтому если вы хотите найти что-то подобное, то рекомендуем проверять все возможные поля и аргументы приложения.

Пример:

query { User(id: 1) { name birth phone email password }
}

Перебирая значения id, мы сможем получить информацию о других пользователях (а, может, и нет, если всё настроено правильно).

Injections

А где есть БД — там могут быть и SQL-injections, NoSQL-injections и другие виды инжектов. Стоит ли говорить, что практически везде, где есть работа с большим объёмом данных, есть и базы данных?

Пример:

mutation { createPerson(name:"Vasya'--+") { name }
}

Здесь элементарная SQL-инъекция в аргументе запроса.

Authorization bypass
Допустим, мы можем создавать пользователей:

mutation { createPerson(username:"Vasya", password: "Qwerty1") { }
}

Предположив, что есть некий параметр isAdmin в обработчике на сервере, мы можем отправить запрос вида:

mutation { createPerson(username:"Vasya", password: "Qwerty1", isAdmin: True) { }
}

И сделать пользователя Vasya администратором.

DoS

Помимо заявленного удобства в GraphQL есть и свои недочеты с точки зрения безопасности.

Рассмотрим пример:

query { Person { posts { author { posts { author { posts { author ... } } } } } }
}

Как видите, мы создали зацикленный вложенный запрос. При большом количестве таких вложений, например, в 50 тыс., мы можем отправить запрос, который будет очень долго обрабатываться сервером или вообще «уронит» его. Вместо обработки валидных запросов сервер будет занят распаковкой гигантской вложенности запроса-пустышки.

Такой запрос тоже может вызвать затруднения в обработке на сервере. Помимо большой вложенности, запросы сами по себе могут быть «тяжелыми» — это когда у одного запроса масса полей и внутренних вложений.

Вывод

Итак, мы рассмотрели основные принципы тестирования на проникновение приложений с GraphQL. Надеемся, вы узнали что-то новое и полезное для себя. Если вам интересна данная тема, и вы хотите изучить её глубже, то рекомендуем следующие ресурсы:
И не забывайте: practice makes perfect. Удачи!


Оставить комментарий

Ваш email нигде не будет показан
Обязательные для заполнения поля помечены *

*

x

Ещё Hi-Tech Интересное!

Слушаем SID-музыку через OPL3 на современных ПК

Кто-то может подумает, что это будет что-то ужасное, а оказывается если сделать простой маппер, то можно получить весьма хорошее звучание, как это сделали несколько разработчиков в программе LLSID ещё в далеком 2007 году. Наверное не все любители чиптюн музыки знают, ...

Пользователь в Docker

В новой статье он рассказывает, как создать пользователей в Docker. Андрей Копылов, наш технический директор, любит, активно использует и пропагандирует Docker. Правильная работа с ними, почему пользователей нельзя оставлять с root правами и, как решить задачу несовпадения идентификаторов в Dockerfile. Это кажется очень удобно, ведь ...