#!/bin/sh (Вступление)
Как вы знаете, я парень в Белой шляпе, не хакер в белой шляпе, а парень в белой шляпе. Я должен пройти собеседование, чтобы устроиться на работу. Эта статья посвящена одному из моих интервью, которое помогло мне стать лучше.
О себе: Я очень ленивый человек и хочу учиться тому, чему хочу, а не тому, что должен. Вы знаете, как сложно бросить аниме и начать чему-то учиться. Я не смог достичь этого, я не достигну этого, я не хочу этого достигать. Мне нравится то, что я делаю, я заставляю себя делать больше, и это одна из таких ситуаций.
Вся эта статья основана на том, что я понимаю, и в основном предназначена для тех, кто хочет ознакомиться с моей базовой дорожной картой GraphQL!
Уровень: Легкий-средний
Требования: API (немного), знание простых веб-уязвимостей, логика
История
Моя подруга сказала, что может устроить мне собеседование в одном из мест, и попросила мое резюме. Итак, угадайте, что сделал этот гений? Я написал в своем резюме то, чего не знаю, НЕ ДЛЯ ТОГО, ЧТОБЫ ЛГАТЬ ИМ О СВОИХ ЗНАНИЯХ, я сделал это, чтобы заставить себя выучить то, что я написал.
Честно говоря, я думал, что они со мной не свяжутся. Странная вещь произошла, они это сделали! Это было собеседование на стажировку, я и раньше проходил много стажировок, но никогда нигде не был принят на работу. Я не виню других, если бы я был достаточно силен, никто бы меня не отпустил. И я уже был на некоторых собеседованиях раньше, я всегда ждал вопроса "Вы закончили X стажировок, почему вас не приняли на работу?". Я ждал этого, и наконец это произошло.
Вопросы:
~ Расскажи мне о себе
# Я такой и есть....
~ Вы закончили X стажировок, почему вас не приняли на работу?
# Я говорил отговорки, которые были правдой, но все равно это отговорки.... Мои оправдания были разумными и правдивыми (я ненавижу лгать), так что, думаю, в какой-то момент он, возможно, понял меня.
~ Хорошо, приходи завтра на техническое собеседование.
# Спасибо. Я приду!
Процесс
Одна из тем, которую я написал в своем резюме и о которой не знал, - это "GraphQL". Поэтому мне пришлось изучать GraphQL.
Как вам следует научиться взламывать что-либо?
1. Научитесь создавать его.
2. Научитесь уничтожать его.
Как я обычно поступаю?
Я взламываю без знания о создании.
На этот раз все было по-другому, я начал с урока LinkedIn GraphQL -
Когда я узнаю что-то, связанное с WEB, я обычно проверяю PentesterLAB / PentesterAcademy / Portswigger. На этот раз я не пользовался PentesterLAB (срок действия моей лицензии истек, и я предпочел кофе вместо того, чтобы продлевать ее).
Mы начнем с базовых знаний GraphQL, а затем перейдем к атакам.
GraphQL [Intro]
В этом разделе я напишу то, что я "понял", а не словарную копи-паст-у.
Мне нравится, когда все организовано, поэтому вот вопросы и ответы.
Изображение [1]: Объяснение работы API (источник
Это основные вещи, которые вам следует знать.
Какие типы API существуют?
Честно говоря, я не хотел писать о них, но рано или поздно вы столкнетесь с ними (я абсолютно уверен). Поэтому я думаю, что, по крайней мере, все мы должны обладать базовыми знаниями о них. Я не хочу усложнять этот процесс для читателей, поэтому вот таблица, которую я составил (так как не смог найти ту, которая мне нравится).
Очевидно, что есть больше различий, больше объяснений по этому поводу, я просто попытался подвести итог, основываясь на моих собственных мыслях о том, "Что может быть важным?". Возможно, я ошибаюсь, и вы вольны исправить эту таблицу.
На вашем месте, без предыстории, я бы не понял эту таблицу. Итак, позвольте мне объяснить это таким образом.
Что же такое GraphQL?
GraphQL - это язык запросов для API, и основное отличие GraphQL заключается в том, что он отвечает только теми данными, которые нужны пользователю, что предотвращает использование дополнительной полосы пропускания (bandwidth).
Мы собираемся использовать эту игровую площадку для тестовых заданий: snowtooth.moonhighway[точка]com
Изображение [2]: Документы игровой площадки
Документы показывают нам запросы, изменения и подписки, которые мы можем cделать.
Сначала я этого не знал, но есть 2 способа получить информацию: с использованием слова "query" и без его использования. Но оба они считаются "query". Давайте запросим данные обо всех лифтах. Для этого мы сначала должны проверить документы и посмотреть доступные "типы" данных.
Изображение [3]: Информация обо всех лифтах
Прежде чем продолжить, давайте рассмотрим это повнимательнее. Типами данных являются id,name,status,capacity... Восклицательный знак, означает, что значение не может быть нулевым. Ниже мы можем видеть "Аргументы", это аргументы, которые мы можем передать, чтобы получить некоторые конкретные данные. В нашем случае этот аргумент "status". Я буду отправлять все запросы со словом "query", потому что так мне удобнее. Давайте запросим id,name,status.
Вот как мы создаем простой запрос, легко, не так ли? Теперь давайте создадим запрос и используем аргумент "status". Enum - это аббревиатура от слова "enumeration". Перечисления (enumeration) - это особый тип для перечисления всех возможных значений в поле. Проще говоря, "enum" - это ограниченный список опций для поля. Если вы этого не поняли, не волнуйтесь, дело в практике, и вы это поймете. Чтобы просмотреть информацию об аргументе, мы должны нажать на него. После нажатия на "LiftStatus" мы видим доступные перечисления "OPEN/CLOSED/HOLD".
Изображение [4]: Информация об аргументе "allLifts"
Время запрашивать данные! Аргументы используются внутри круглых скобок "()". Давайте запросим все лифты со статусом "OPEN"
Хорошо, теперь возникает вопрос, что делать, если я хочу запросить 2 статуса одновременно. Что делать, если я хочу видеть как статус "HOLD", так и статус "OPEN".
Прежде чем сделать это, давайте снова запросим информацию, но на этот раз я буду использовать "liftCount" внутри "query". "liftCount" также использует "LiftStatus" в качестве аргумента.
Изображение [5]: Информация об аргументе "liftCount"
На этот раз мы использовали "
Переведите, чтобы понять причину этой ошибки.
Теперь, если мы хотим запросить с помощью 2 разных перечислений, мы можем использовать "alias". Это позволяет нам присвоить аргументам любое имя, и таким образом мы можем написать 2 аргумента с разными "enum".
Kей, в предыдущих случаях нам приходилось использовать "enum" в самом запросе, но на самом деле мы можем отправить запрос в формате json, содержащий переменные запроса. Чтобы иметь возможность отправить запрос, нажмите на "QUERY VARIABLES" в левом нижнем углу.
Теперь возникает вопрос, что, если мы отправим 2 переменные "xsstatus" с разными параметрами.
Результат содержал количество лифтов только со статусом закрыто, это означает, что если мы отправим переменную с 2 разными ключами (опциями), то будет учтен последний.
Запросы, которые мы отправляем, являются "анонимными", потому что мы не дали им имен. Давайте скопируем и вставим наш запрос 2 раза и посмотрим, что произойдет, прежде чем давать запросам имена! В этом случае, когда мы пытаемся запустить, мы видим 2 "<Unnamed>". Это потому, что оба наших запроса анонимны.
Изображение [6]: Кнопка запуска для 2 анонимных запросов.
Теперь пришло время присвоить имена обоим запросам, чтобы не возникло ошибки и все было правильно. Я называю первый запрос "xss1", а второй - "xss2". Как креативно, а? Вы можете нажать на любой из них. Если вы хотите, чтобы запрос xss1 работал, вы должны нажать на xss1, если вы хотите, чтобы запрос xss2 работал, вы должны нажать на xss2. В нашем случае оба они одинаковы.
Изображение [7]: Кнопка запуска для 2 запросов.
Это базовые знания о запросах GraphQL, которые вы можете получить от меня, дальнейшее вы можете изучить самостоятельно. Вот ключевые слова тем, которые я затронул перед изучением атак, вы можете учиться на их основе, если хотите:
Самоанализ (Introspection) - это способность сервера GraphQL предоставлять информацию о своей собственной схеме. Это позволяет клиентам запрашивать у сервера подробную информацию о типах, полях и других элементах. В случае, если мы сможем выполнять запросы GraphQL, мы будем использовать самоанализ для получения информации, это похоже на "документы", которые мы использовали.
Метаполя (Meta Fields) содержат метаданные о схеме. Например, мета-поле __type позволяет вам запрашивать саму схему, чтобы получить информацию о типах, определенных в этой схеме.
Давайте отправим запрос на самоанализ, чтобы понять "query" "Lift".
Чтобы понять, почему мой "запрос" выглядит именно так, взгляните на "документы".
Изображение [8]: запрос "type" в "документах".
Я не собираюсь говорить о XSS, SQLi, RCE или о чем-либо еще, чего вы можете достичь с помощью GraphQL. Логика здесь такая же, как и в любом другом веб-приложении. Главная уязвимость, которую нужно понять, - это "самоанализ", что не так уж сложно.
Прежде чем продолжить, это ваше домашнее задание:
Итак, давайте рассмотрим 2 простых случая. К сожалению, у меня нет кода, чтобы поделиться с вами этими лабораториями. Вы можете спросить: "Если у нас нет кода, и вы просто показываете решение labs, к которому мы, вероятно, не будем прикасаться, в чем смысл?" Ну, у вас есть лаборатории "portswigger", которые похожи на эти, я не хочу объяснять иx, как я делал это в предыдущем случае, потому что решение и так есть. В предыдущем случае я решил portswigger lab только потому, что это был аналог моего собственного случая.
Лаборатория № 1
Цель: Получить флаг
Если вы хотите знать, откуда взялась эта лаборатория, не стесняйтесь писать мне в личку.
Как обычно, сначала мы открываем веб-сайт. Вы можете это не читать.
Изображение [9]: Открытие лаборатории
Буду честен, ребята, я не знал, что делать, первое, что приходит на ум, это прочитать error и попытаться найти параметр, через который мы можем отправить запрос GraphQL.
Изображение [10]: Отправка пустого запроса
Я угадал с параметром. Если вы знаете, какие еще параметры мы можем использовать, напишите об этом в комментариях, пожалуйста, это может быть очень полезным знанием на случай, если кто-то вроде меня захочет автоматизировать весь процесс. Если бы мне пришлось угадывать параметры, я бы использовал: query, q, graphql, graph, api. И, возможно, я бы добавил цифры от 1 до 5 после каждого параметра (на всякий случай) Например: api1, api2.....
Я бы добавил цифры в конце только потому, что, возможно, какая-то система будет иметь другой API или обновится до нового API (например, от REST к GraphQL), просто добавив номер в конце конечной точки. Не осуждайте меня за то, что я говорю, что "Это глупая идея". Убедившись, что заголовки типа "X-Forwarded-For" работают как обход в системах, я не думаю, что найдется что-то глупее.
В любом случае, в нашу лабораторию, чтобы решить эту проблему, мы должны отправить запрос на самоанализ, но в случае, если раньше мы знали название "query", в то время как в этом случае мы ничего не знаем. Вы можете просто пропустить все и использовать это:
Чтобы сделать это вручную, мы должны сначала отправить запрос
Схема (__schema) - это описание данных (types) , которые клиенты могут запрашивать. Типы могут представлять типы объектов, скалярные типы , типы перечислений и многое другое. "name" - означает, что мы запрашиваем имена типов, определенных в схеме.
Изображение [11]: Получение именТеперь у нас есть название "NashFlag", и мы можем сделать запрос.
Изображение [12]: Получение имен полей
Теперь у нас есть имя "query" и имя "field", которых достаточно для отправки запроса.
Изображение [13]: Получение флага
Вторая лаборатория такая же, как и первая, я не вижу причин это объяснять.
Как бы я искал graphql в больших областях?
Я бы использовал "waybackurls" для получения URL-адресов домена / поддоменов. Позже я бы создал пользовательский инструмент, который удалит значение любого параметра, заменит его на GraphQL introspection и проверит ответ.
ChatGPT TIME!
Я не программист, как я писал в своей предыдущей статье, я не пишу код, это делает ChatGPT, я просто исправляю его.
Что должен делать наш код?
Наш код должен удалить все значения параметров и заменить их на {__schema{types{name}}}, и если результат содержит __schema, query или mutation, он должен вывести это как "успех".
Изображение [14]: GraphQL.sh
Моменты, которые, как мне кажется, я рассмотрел:
Множество параметров
Моменты, которые, как мне кажется, я не учел:
Threading
Брандмауэры
Разные ответы
Если я получу несколько комментариев от людей, которые хотят использовать это как обычный инструмент. Я подумаю о том, чтобы сделать что-нибудь приятное для белого использования.
Я ничего не обещаю. Если я что-то обещаю, то делаю это либо потому, что злюсь, либо потому, что я больше уверен в себе, чем в логотипе Кали.
Несколько полезных ссылок:
Как вы знаете, я парень в Белой шляпе, не хакер в белой шляпе, а парень в белой шляпе. Я должен пройти собеседование, чтобы устроиться на работу. Эта статья посвящена одному из моих интервью, которое помогло мне стать лучше.
О себе: Я очень ленивый человек и хочу учиться тому, чему хочу, а не тому, что должен. Вы знаете, как сложно бросить аниме и начать чему-то учиться. Я не смог достичь этого, я не достигну этого, я не хочу этого достигать. Мне нравится то, что я делаю, я заставляю себя делать больше, и это одна из таких ситуаций.
Вся эта статья основана на том, что я понимаю, и в основном предназначена для тех, кто хочет ознакомиться с моей базовой дорожной картой GraphQL!
Уровень: Легкий-средний
Требования: API (немного), знание простых веб-уязвимостей, логика
История
Моя подруга сказала, что может устроить мне собеседование в одном из мест, и попросила мое резюме. Итак, угадайте, что сделал этот гений? Я написал в своем резюме то, чего не знаю, НЕ ДЛЯ ТОГО, ЧТОБЫ ЛГАТЬ ИМ О СВОИХ ЗНАНИЯХ, я сделал это, чтобы заставить себя выучить то, что я написал.
Честно говоря, я думал, что они со мной не свяжутся. Странная вещь произошла, они это сделали! Это было собеседование на стажировку, я и раньше проходил много стажировок, но никогда нигде не был принят на работу. Я не виню других, если бы я был достаточно силен, никто бы меня не отпустил. И я уже был на некоторых собеседованиях раньше, я всегда ждал вопроса "Вы закончили X стажировок, почему вас не приняли на работу?". Я ждал этого, и наконец это произошло.
Вопросы:
~ Расскажи мне о себе
# Я такой и есть....
~ Вы закончили X стажировок, почему вас не приняли на работу?
# Я говорил отговорки, которые были правдой, но все равно это отговорки.... Мои оправдания были разумными и правдивыми (я ненавижу лгать), так что, думаю, в какой-то момент он, возможно, понял меня.
~ Хорошо, приходи завтра на техническое собеседование.
# Спасибо. Я приду!
Процесс
Одна из тем, которую я написал в своем резюме и о которой не знал, - это "GraphQL". Поэтому мне пришлось изучать GraphQL.
Как вам следует научиться взламывать что-либо?
1. Научитесь создавать его.
2. Научитесь уничтожать его.
Как я обычно поступаю?
Я взламываю без знания о создании.
На этот раз все было по-другому, я начал с урока LinkedIn GraphQL -
https://www.linkedin[точка]com/learning/learning-graphql-11292553. Я не эксперт по GraphQL, у меня просто была ночь, чтобы проверить это. Я не закончил его за 1 час, как намеревался, работал около 3-4 часов, практикуясь.Когда я узнаю что-то, связанное с WEB, я обычно проверяю PentesterLAB / PentesterAcademy / Portswigger. На этот раз я не пользовался PentesterLAB (срок действия моей лицензии истек, и я предпочел кофе вместо того, чтобы продлевать ее).
Mы начнем с базовых знаний GraphQL, а затем перейдем к атакам.
GraphQL [Intro]
В этом разделе я напишу то, что я "понял", а не словарную копи-паст-у.
Мне нравится, когда все организовано, поэтому вот вопросы и ответы.
- Что такое язык запросов?
- Это язык для поиска, модификации и удаления информации из некоторой базы данных.
- Что такое API?
- "API stands for Application Programming Interface" - шучу. API - это механизм (программное обеспечение / код), который позволяет приложениям взаимодействовать друг с другом. Насколько я понимаю, API берет данные из backend / ОС и представляет их во внешнем интерфейсе приложения
- Как работает API?
- Я не хочу это описывать. Либо попросите ChatGPT "Объяснить, как работает API для человека, который никогда не прикасался к компьютеру", либо посмотрите на эту картинку. (Кстати, не поймите меня неправильно, именно так я обычно пишу в ChatGPT)
Изображение [1]: Объяснение работы API (источник
postman[точка]com)Это основные вещи, которые вам следует знать.
Какие типы API существуют?
Честно говоря, я не хотел писать о них, но рано или поздно вы столкнетесь с ними (я абсолютно уверен). Поэтому я думаю, что, по крайней мере, все мы должны обладать базовыми знаниями о них. Я не хочу усложнять этот процесс для читателей, поэтому вот таблица, которую я составил (так как не смог найти ту, которая мне нравится).
| NULL | REST | SOAP | RPC | WebHook | GraphQL |
| Форматы данных | JSON/XML | XML | JSON/XML/Protobuf/Thrift/MessagePack | JSON/XML/TEXT | JSON |
| Различия |
|
|
|
|
|
| Варианты использования | Веб-сервисы/Мобильные приложения/Интернет вещей (IoT) | Коммуникационный протокол/Корпоративная интеграция/Веб-сервисы | Распределенные системы/Клиент-серверные приложения | Коммуникация/Автоматизация/Рабочие процессы, управляемые событиями | Веб-приложения/Мобильные приложения/Приложения с интенсивным использованием данных |
Очевидно, что есть больше различий, больше объяснений по этому поводу, я просто попытался подвести итог, основываясь на моих собственных мыслях о том, "Что может быть важным?". Возможно, я ошибаюсь, и вы вольны исправить эту таблицу.
На вашем месте, без предыстории, я бы не понял эту таблицу. Итак, позвольте мне объяснить это таким образом.
Что же такое GraphQL?
GraphQL - это язык запросов для API, и основное отличие GraphQL заключается в том, что он отвечает только теми данными, которые нужны пользователю, что предотвращает использование дополнительной полосы пропускания (bandwidth).
Мы собираемся использовать эту игровую площадку для тестовых заданий: snowtooth.moonhighway[точка]com
Изображение [2]: Документы игровой площадки
Сначала я этого не знал, но есть 2 способа получить информацию: с использованием слова "query" и без его использования. Но оба они считаются "query". Давайте запросим данные обо всех лифтах. Для этого мы сначала должны проверить документы и посмотреть доступные "типы" данных.
Изображение [3]: Информация обо всех лифтах
| Запрос | Ответ |
query {
allLifts {
id
name
status
}
} | {
"data": {
"allLifts": [
{
"id": "astra-express",
"name": "Astra Express",
"status": "OPEN"
},
{
"id": "jazz-cat",
"name": "Jazz Cat",
"status": "OPEN"
},
{
"id": "jolly-roger",
"name": "Jolly Roger",
"status": "OPEN"
},
{
"id": "neptune-rope",
"name": "Neptune Rope",
"status": "OPEN"
},
{
"id": "panorama",
"name": "Panorama",
"status": "HOLD"
},
{
"id": "prickly-peak",
"name": "Prickly Peak",
"status": "OPEN"
},
{
"id": "snowtooth-express",
"name": "Snowtooth Express",
"status": "OPEN"
},
{
"id": "summit",
"name": "Summit",
"status": "CLOSED"
},
{
"id": "wallys",
"name": "Wally's",
"status": "HOLD"
},
{
"id": "western-states",
"name": "Western States",
"status": "CLOSED"
},
{
"id": "whirlybird",
"name": "Whirlybird",
"status": "HOLD"
}
]
}
} |
Вот как мы создаем простой запрос, легко, не так ли? Теперь давайте создадим запрос и используем аргумент "status". Enum - это аббревиатура от слова "enumeration". Перечисления (enumeration) - это особый тип для перечисления всех возможных значений в поле. Проще говоря, "enum" - это ограниченный список опций для поля. Если вы этого не поняли, не волнуйтесь, дело в практике, и вы это поймете. Чтобы просмотреть информацию об аргументе, мы должны нажать на него. После нажатия на "LiftStatus" мы видим доступные перечисления "OPEN/CLOSED/HOLD".
Изображение [4]: Информация об аргументе "allLifts"
Время запрашивать данные! Аргументы используются внутри круглых скобок "()". Давайте запросим все лифты со статусом "OPEN"
| Запрос | Ответ |
query {
allLifts (status: OPEN){
id
name
status
}
} |
{
"data": {
"allLifts": [
{
"id": "astra-express",
"name": "Astra Express",
"status": "OPEN"
},
{
"id": "jazz-cat",
"name": "Jazz Cat",
"status": "OPEN"
},
{
"id": "jolly-roger",
"name": "Jolly Roger",
"status": "OPEN"
},
{
"id": "neptune-rope",
"name": "Neptune Rope",
"status": "OPEN"
},
{
"id": "prickly-peak",
"name": "Prickly Peak",
"status": "OPEN"
},
{
"id": "snowtooth-express",
"name": "Snowtooth Express",
"status": "OPEN"
}
]
}
}
|
Хорошо, теперь возникает вопрос, что делать, если я хочу запросить 2 статуса одновременно. Что делать, если я хочу видеть как статус "HOLD", так и статус "OPEN".
Прежде чем сделать это, давайте снова запросим информацию, но на этот раз я буду использовать "liftCount" внутри "query". "liftCount" также использует "LiftStatus" в качестве аргумента.
Изображение [5]: Информация об аргументе "liftCount"
| Запрос | Ответ |
query {
liftCount(status:OPEN)
allLifts {
id
name
status
}
}
|
{
"data": {
"liftCount": 6,
"allLifts": [
{
"id": "astra-express",
"name": "Astra Express",
"status": "OPEN"
},
{
"id": "jazz-cat",
"name": "Jazz Cat",
"status": "OPEN"
},
{
"id": "jolly-roger",
"name": "Jolly Roger",
"status": "OPEN"
},
{
"id": "neptune-rope",
"name": "Neptune Rope",
"status": "OPEN"
},
{
"id": "panorama",
"name": "Panorama",
"status": "HOLD"
},
{
"id": "prickly-peak",
"name": "Prickly Peak",
"status": "OPEN"
},
{
"id": "snowtooth-express",
"name": "Snowtooth Express",
"status": "OPEN"
},
{
"id": "summit",
"name": "Summit",
"status": "CLOSED"
},
{
"id": "wallys",
"name": "Wally's",
"status": "HOLD"
},
{
"id": "western-states",
"name": "Western States",
"status": "CLOSED"
},
{
"id": "whirlybird",
"name": "Whirlybird",
"status": "HOLD"
}
]
}
}
|
На этот раз мы использовали "
liftCount(status:OPEN)" вместо использования "allLifts (status: OPEN)". Пожалуйста, обратите внимание, что количество "liftCount" отображается только для статуса открыто, в то время как список "liftCount" показывает все. Теперь вы можете подумать, что мы можем просто использовать "liftCount(status:OPEN)" и"liftCount(status:HOLD)", но это не сработает.| Запрос | Ответ |
query {
liftCount(status:OPEN)
liftCount(status:HOLD)
allLifts {
id
name
status
}
}
|
{
"error": {
"errors": [
{
"message": "Fields \"liftCount\" conflict because they have differing arguments. Use different aliases on the fields to fetch both if this was intentional.",
"locations": [
{
"line": 2,
"column": 3
},
{
"line": 3,
"column": 3
}
],
"extensions": {
"code": "GRAPHQL_VALIDATION_FAILED",
"exception": {
"stacktrace": [
"GraphQLError: Fields \"liftCount\" conflict because they have differing arguments. Use different aliases on the fields to fetch both if this was intentional.",
" at Object.SelectionSet (/app/node_modules/graphql/validation/rules/OverlappingFieldsCanBeMergedRule.js:67:29)",
" at Object.enter (/app/node_modules/graphql/language/visitor.js:323:29)",
" at Object.enter (/app/node_modules/graphql/utilities/TypeInfo.js:370:25)",
" at visit (/app/node_modules/graphql/language/visitor.js:243:26)",
" at Object.validate (/app/node_modules/graphql/validation/validate.js:69:24)",
" at validate (/app/node_modules/apollo-server-core/dist/requestPipeline.js:221:34)",
" at Object.<anonymous> (/app/node_modules/apollo-server-core/dist/requestPipeline.js:118:42)",
" at Generator.next (<anonymous>)",
" at fulfilled (/app/node_modules/apollo-server-core/dist/requestPipeline.js:5:58)",
" at runMicrotasks (<anonymous>)"
]
}
}
}
]
}
}
|
Переведите, чтобы понять причину этой ошибки.
Теперь, если мы хотим запросить с помощью 2 разных перечислений, мы можем использовать "alias". Это позволяет нам присвоить аргументам любое имя, и таким образом мы можем написать 2 аргумента с разными "enum".
| Запрос | Ответ |
query {
otkrit: liftCount(status:OPEN)
jdyom: liftCount(status:HOLD)
allLifts {
id
name
status
}
}
|
{
"data": {
"otkrit": 6,
"jdyom": 3,
"allLifts": [
{
"id": "astra-express",
"name": "Astra Express",
"status": "OPEN"
},
{
"id": "jazz-cat",
"name": "Jazz Cat",
"status": "OPEN"
},
{
"id": "jolly-roger",
"name": "Jolly Roger",
"status": "OPEN"
},
{
"id": "neptune-rope",
"name": "Neptune Rope",
"status": "OPEN"
},
{
"id": "panorama",
"name": "Panorama",
"status": "HOLD"
},
{
"id": "prickly-peak",
"name": "Prickly Peak",
"status": "OPEN"
},
{
"id": "snowtooth-express",
"name": "Snowtooth Express",
"status": "OPEN"
},
{
"id": "summit",
"name": "Summit",
"status": "CLOSED"
},
{
"id": "wallys",
"name": "Wally's",
"status": "HOLD"
},
{
"id": "western-states",
"name": "Western States",
"status": "CLOSED"
},
{
"id": "whirlybird",
"name": "Whirlybird",
"status": "HOLD"
}
]
}
}
|
Kей, в предыдущих случаях нам приходилось использовать "enum" в самом запросе, но на самом деле мы можем отправить запрос в формате json, содержащий переменные запроса. Чтобы иметь возможность отправить запрос, нажмите на "QUERY VARIABLES" в левом нижнем углу.
| Запрос | Ответ |
query ($xssstatus: LiftStatus){
liftCount(status:$xssstatus)
allLifts {
id
name
status
}
}
|
{
"data": {
"liftCount": 6,
"allLifts": [
{
"id": "astra-express",
"name": "Astra Express",
"status": "OPEN"
},
{
"id": "jazz-cat",
"name": "Jazz Cat",
"status": "OPEN"
},
{
"id": "jolly-roger",
"name": "Jolly Roger",
"status": "OPEN"
},
{
"id": "neptune-rope",
"name": "Neptune Rope",
"status": "OPEN"
},
{
"id": "panorama",
"name": "Panorama",
"status": "HOLD"
},
{
"id": "prickly-peak",
"name": "Prickly Peak",
"status": "OPEN"
},
{
"id": "snowtooth-express",
"name": "Snowtooth Express",
"status": "OPEN"
},
{
"id": "summit",
"name": "Summit",
"status": "CLOSED"
},
{
"id": "wallys",
"name": "Wally's",
"status": "HOLD"
},
{
"id": "western-states",
"name": "Western States",
"status": "CLOSED"
},
{
"id": "whirlybird",
"name": "Whirlybird",
"status": "HOLD"
}
]
}
}
|
| Переменные запроса |
{
"xssstatus": "OPEN"
}
|
Теперь возникает вопрос, что, если мы отправим 2 переменные "xsstatus" с разными параметрами.
| Запрос | Ответ |
query ($xssstatus: LiftStatus){
liftCount(status:$xssstatus)
allLifts {
id
name
status
}
}
|
{
"data": {
"liftCount": 2,
"allLifts": [
{
"id": "astra-express",
"name": "Astra Express",
"status": "OPEN"
},
{
"id": "jazz-cat",
"name": "Jazz Cat",
"status": "OPEN"
},
{
"id": "jolly-roger",
"name": "Jolly Roger",
"status": "OPEN"
},
{
"id": "neptune-rope",
"name": "Neptune Rope",
"status": "OPEN"
},
{
"id": "panorama",
"name": "Panorama",
"status": "HOLD"
},
{
"id": "prickly-peak",
"name": "Prickly Peak",
"status": "OPEN"
},
{
"id": "snowtooth-express",
"name": "Snowtooth Express",
"status": "OPEN"
},
{
"id": "summit",
"name": "Summit",
"status": "CLOSED"
},
{
"id": "wallys",
"name": "Wally's",
"status": "HOLD"
},
{
"id": "western-states",
"name": "Western States",
"status": "CLOSED"
},
{
"id": "whirlybird",
"name": "Whirlybird",
"status": "HOLD"
}
]
}
}
|
| Переменные запроса |
{
"xssstatus": "OPEN",
"xssstatus": "CLOSED"
}
|
Результат содержал количество лифтов только со статусом закрыто, это означает, что если мы отправим переменную с 2 разными ключами (опциями), то будет учтен последний.
Запросы, которые мы отправляем, являются "анонимными", потому что мы не дали им имен. Давайте скопируем и вставим наш запрос 2 раза и посмотрим, что произойдет, прежде чем давать запросам имена! В этом случае, когда мы пытаемся запустить, мы видим 2 "<Unnamed>". Это потому, что оба наших запроса анонимны.
Изображение [6]: Кнопка запуска для 2 анонимных запросов.
| Запрос | Ответ |
query ($xssstatus: LiftStatus){
liftCount(status:$xssstatus)
allLifts {
id
name
status
}
}
query ($xssstatus: LiftStatus){
liftCount(status:$xssstatus)
allLifts {
id
name
status
}
}
|
{
"error": {
"errors": [
{
"message": "This anonymous operation must be the only defined operation.",
"locations": [
{
"line": 1,
"column": 1
}
],
"extensions": {
"code": "GRAPHQL_VALIDATION_FAILED",
"exception": {
"stacktrace": [
"GraphQLError: This anonymous operation must be the only defined operation.",
" at Object.OperationDefinition (/app/node_modules/graphql/validation/rules/LoneAnonymousOperationRule.js:28:29)",
" at Object.enter (/app/node_modules/graphql/language/visitor.js:323:29)",
" at Object.enter (/app/node_modules/graphql/utilities/TypeInfo.js:370:25)",
" at visit (/app/node_modules/graphql/language/visitor.js:243:26)",
" at Object.validate (/app/node_modules/graphql/validation/validate.js:69:24)",
" at validate (/app/node_modules/apollo-server-core/dist/requestPipeline.js:221:34)",
" at Object.<anonymous> (/app/node_modules/apollo-server-core/dist/requestPipeline.js:118:42)",
" at Generator.next (<anonymous>)",
" at fulfilled (/app/node_modules/apollo-server-core/dist/requestPipeline.js:5:58)",
" at runMicrotasks (<anonymous>)"
]
}
}
},
{
"message": "This anonymous operation must be the only defined operation.",
"locations": [
{
"line": 10,
"column": 1
}
],
"extensions": {
"code": "GRAPHQL_VALIDATION_FAILED",
"exception": {
"stacktrace": [
"GraphQLError: This anonymous operation must be the only defined operation.",
" at Object.OperationDefinition (/app/node_modules/graphql/validation/rules/LoneAnonymousOperationRule.js:28:29)",
" at Object.enter (/app/node_modules/graphql/language/visitor.js:323:29)",
" at Object.enter (/app/node_modules/graphql/utilities/TypeInfo.js:370:25)",
" at visit (/app/node_modules/graphql/language/visitor.js:243:26)",
" at Object.validate (/app/node_modules/graphql/validation/validate.js:69:24)",
" at validate (/app/node_modules/apollo-server-core/dist/requestPipeline.js:221:34)",
" at Object.<anonymous> (/app/node_modules/apollo-server-core/dist/requestPipeline.js:118:42)",
" at Generator.next (<anonymous>)",
" at fulfilled (/app/node_modules/apollo-server-core/dist/requestPipeline.js:5:58)",
" at runMicrotasks (<anonymous>)"
]
}
}
}
]
}
}
|
| Переменные запроса |
{
"xssstatus": "OPEN"
}
|
Теперь пришло время присвоить имена обоим запросам, чтобы не возникло ошибки и все было правильно. Я называю первый запрос "xss1", а второй - "xss2". Как креативно, а? Вы можете нажать на любой из них. Если вы хотите, чтобы запрос xss1 работал, вы должны нажать на xss1, если вы хотите, чтобы запрос xss2 работал, вы должны нажать на xss2. В нашем случае оба они одинаковы.
Изображение [7]: Кнопка запуска для 2 запросов.
| Запрос | Ответ |
query xss1 ($xssstatus: LiftStatus){
liftCount(status:$xssstatus)
allLifts {
id
name
status
}
}
query xss2 ($xssstatus: LiftStatus){
liftCount(status:$xssstatus)
allLifts {
id
name
status
}
}
|
{
"data": {
"liftCount": 6,
"allLifts": [
{
"id": "astra-express",
"name": "Astra Express",
"status": "OPEN"
},
{
"id": "jazz-cat",
"name": "Jazz Cat",
"status": "OPEN"
},
{
"id": "jolly-roger",
"name": "Jolly Roger",
"status": "OPEN"
},
{
"id": "neptune-rope",
"name": "Neptune Rope",
"status": "OPEN"
},
{
"id": "panorama",
"name": "Panorama",
"status": "HOLD"
},
{
"id": "prickly-peak",
"name": "Prickly Peak",
"status": "OPEN"
},
{
"id": "snowtooth-express",
"name": "Snowtooth Express",
"status": "OPEN"
},
{
"id": "summit",
"name": "Summit",
"status": "CLOSED"
},
{
"id": "wallys",
"name": "Wally's",
"status": "HOLD"
},
{
"id": "western-states",
"name": "Western States",
"status": "CLOSED"
},
{
"id": "whirlybird",
"name": "Whirlybird",
"status": "HOLD"
}
]
}
}
|
| Переменные запроса |
| { "xssstatus": "OPEN" } |
Это базовые знания о запросах GraphQL, которые вы можете получить от меня, дальнейшее вы можете изучить самостоятельно. Вот ключевые слова тем, которые я затронул перед изучением атак, вы можете учиться на их основе, если хотите:
mutations, subscriptions, fragment, @skip, @include, if, Meta Fields, __type, __typename, __kind.Самоанализ (Introspection) - это способность сервера GraphQL предоставлять информацию о своей собственной схеме. Это позволяет клиентам запрашивать у сервера подробную информацию о типах, полях и других элементах. В случае, если мы сможем выполнять запросы GraphQL, мы будем использовать самоанализ для получения информации, это похоже на "документы", которые мы использовали.
Метаполя (Meta Fields) содержат метаданные о схеме. Например, мета-поле __type позволяет вам запрашивать саму схему, чтобы получить информацию о типах, определенных в этой схеме.
Давайте отправим запрос на самоанализ, чтобы понять "query" "Lift".
| Запрос | Ответ |
query {
__type(name: "Lift") {
name
kind
fields {
name
type {
name
kind
description
}
}
}
}
|
{
"data": {
"__type": {
"name": "Lift",
"kind": "OBJECT",
"fields": [
{
"name": "id",
"type": {
"name": null,
"kind": "NON_NULL",
"description": null
}
},
{
"name": "name",
"type": {
"name": null,
"kind": "NON_NULL",
"description": null
}
},
{
"name": "status",
"type": {
"name": "LiftStatus",
"kind": "ENUM",
"description": "An enum describing the options for `LiftStatus`: `OPEN`, `CLOSED`, `HOLD`"
}
},
{
"name": "capacity",
"type": {
"name": null,
"kind": "NON_NULL",
"description": null
}
},
{
"name": "night",
"type": {
"name": null,
"kind": "NON_NULL",
"description": null
}
},
{
"name": "elevationGain",
"type": {
"name": null,
"kind": "NON_NULL",
"description": null
}
},
{
"name": "trailAccess",
"type": {
"name": null,
"kind": "NON_NULL",
"description": null
}
}
]
}
}
}
|
Чтобы понять, почему мой "запрос" выглядит именно так, взгляните на "документы".
Изображение [8]: запрос "type" в "документах".
Прежде чем продолжить, это ваше домашнее задание:
https://portswigger[точка]net/web-security/graphql/lab-graphql-reading-private-postshttps://portswigger[точка]net/web-security/graphql/lab-graphql-accidental-field-exposurehttps://portswigger[точка]net/web-security/graphql/lab-graphql-find-the-endpointИтак, давайте рассмотрим 2 простых случая. К сожалению, у меня нет кода, чтобы поделиться с вами этими лабораториями. Вы можете спросить: "Если у нас нет кода, и вы просто показываете решение labs, к которому мы, вероятно, не будем прикасаться, в чем смысл?" Ну, у вас есть лаборатории "portswigger", которые похожи на эти, я не хочу объяснять иx, как я делал это в предыдущем случае, потому что решение и так есть. В предыдущем случае я решил portswigger lab только потому, что это был аналог моего собственного случая.
Лаборатория № 1
Цель: Получить флаг
Если вы хотите знать, откуда взялась эта лаборатория, не стесняйтесь писать мне в личку.
Как обычно, сначала мы открываем веб-сайт. Вы можете это не читать.
Я не мистер Робот, чтобы запускать "cURL", анализировать заголовки, использовать "whatweb" и любые дополнительные функции перед входом на веб-сайт. Я просто открываю его на виртуальной машине в браузере, подобном "chromium", и если в этом случае кто-то причинит вред моему компьютеру, респект, парень хороший! Я просто попытаюсь связаться с этим парнем и спросить его, какие темы мне следует затронуть, чтобы самосовершенствоваться.
Изображение [9]: Открытие лаборатории
Изображение [10]: Отправка пустого запроса
Я бы добавил цифры в конце только потому, что, возможно, какая-то система будет иметь другой API или обновится до нового API (например, от REST к GraphQL), просто добавив номер в конце конечной точки. Не осуждайте меня за то, что я говорю, что "Это глупая идея". Убедившись, что заголовки типа "X-Forwarded-For" работают как обход в системах, я не думаю, что найдется что-то глупее.
В любом случае, в нашу лабораторию, чтобы решить эту проблему, мы должны отправить запрос на самоанализ, но в случае, если раньше мы знали название "query", в то время как в этом случае мы ничего не знаем. Вы можете просто пропустить все и использовать это:
https://gist.github[точка]com/localh0t/240a8037922a0b168ea85fd8fef7bded или вы можете попробовать сделать все это вручную, как я предпочитаю.Чтобы сделать это вручную, мы должны сначала отправить запрос
{__schema{types{name}}}. Давайте разберем это на части.Схема (__schema) - это описание данных (types) , которые клиенты могут запрашивать. Типы могут представлять типы объектов, скалярные типы , типы перечислений и многое другое. "name" - означает, что мы запрашиваем имена типов, определенных в схеме.
Изображение [11]: Получение имен
query {
__type(name: "NashFlag") {
name
kind
fields {
name
type {
name
kind
description
}
}
}
}
Изображение [12]: Получение имен полей
query {
NashFlag {
value
}
}
Изображение [13]: Получение флага
Вторая лаборатория такая же, как и первая, я не вижу причин это объяснять.
Как бы я искал graphql в больших областях?
Я бы использовал "waybackurls" для получения URL-адресов домена / поддоменов. Позже я бы создал пользовательский инструмент, который удалит значение любого параметра, заменит его на GraphQL introspection и проверит ответ.
ChatGPT TIME!
Я не программист, как я писал в своей предыдущей статье, я не пишу код, это делает ChatGPT, я просто исправляю его.
Что должен делать наш код?
Наш код должен удалить все значения параметров и заменить их на {__schema{types{name}}}, и если результат содержит __schema, query или mutation, он должен вывести это как "успех".
#!/bin/bash
# Function to replace parameter values with "{__schema{types{name}}}"
function replace_params() {
local url="$1"
local replaced_url=""
IFS='?' read -ra parts <<< "$url" # Split URL into base and query parts
if [ "${#parts[@]}" -eq 2 ]; then
replaced_url="${parts[0]}?" # Base URL
IFS='&' read -ra params <<< "${parts[1]}" # Split query parameters
for param in "${params[@]}"; do
IFS='=' read -ra param_parts <<< "$param" # Split parameter into name and value
param_name="${param_parts[0]}"
replaced_url="${replaced_url}${param_name}=%7b%5f%5fschema%7btypes%7bname%7d%7d%7d&"
done
replaced_url="${replaced_url%&}" # Remove the trailing '&' if present
else
replaced_url="$url" # No query parameters, just use the base URL
fi
echo "$replaced_url"
}
# Function to check if response contains "__schema" or "name":"Query" or "name":"Mutation"
function check_response() {
local response="$1"
if [[ "$response" == *'{"__schema":'* || "$response" == *'"name":"Query"'* || "$response" == *'"name":"Mutation"'* ]]; then
echo "Success"
else
echo "Failure"
fi
}
# Read file name from user input
read -p "Enter the file name containing URLs: " file_name
# Check if the file exists
if [ ! -f "$file_name" ]; then
echo "File not found!"
exit 1
fi
# Process each URL in the file
while IFS= read -r url; do
replaced_url=$(replace_params "$url")
response=$(curl -s "$replaced_url") # Send a GET request to the modified URL
check_result=$(check_response "$response")
echo "Original URL: $url"
echo "Modified URL: $replaced_url"
echo "Check result: $check_result"
echo
done < "$file_name"
# Function to replace parameter values with "{__schema{types{name}}}"
function replace_params() {
local url="$1"
local replaced_url=""
IFS='?' read -ra parts <<< "$url" # Split URL into base and query parts
if [ "${#parts[@]}" -eq 2 ]; then
replaced_url="${parts[0]}?" # Base URL
IFS='&' read -ra params <<< "${parts[1]}" # Split query parameters
for param in "${params[@]}"; do
IFS='=' read -ra param_parts <<< "$param" # Split parameter into name and value
param_name="${param_parts[0]}"
replaced_url="${replaced_url}${param_name}=%7b%5f%5fschema%7btypes%7bname%7d%7d%7d&"
done
replaced_url="${replaced_url%&}" # Remove the trailing '&' if present
else
replaced_url="$url" # No query parameters, just use the base URL
fi
echo "$replaced_url"
}
# Function to check if response contains "__schema" or "name":"Query" or "name":"Mutation"
function check_response() {
local response="$1"
if [[ "$response" == *'{"__schema":'* || "$response" == *'"name":"Query"'* || "$response" == *'"name":"Mutation"'* ]]; then
echo "Success"
else
echo "Failure"
fi
}
# Read file name from user input
read -p "Enter the file name containing URLs: " file_name
# Check if the file exists
if [ ! -f "$file_name" ]; then
echo "File not found!"
exit 1
fi
# Process each URL in the file
while IFS= read -r url; do
replaced_url=$(replace_params "$url")
response=$(curl -s "$replaced_url") # Send a GET request to the modified URL
check_result=$(check_response "$response")
echo "Original URL: $url"
echo "Modified URL: $replaced_url"
echo "Check result: $check_result"
echo
done < "$file_name"
Изображение [14]: GraphQL.sh
Множество параметров
Моменты, которые, как мне кажется, я не учел:
Threading
Брандмауэры
Разные ответы
Если я получу несколько комментариев от людей, которые хотят использовать это как обычный инструмент. Я подумаю о том, чтобы сделать что-нибудь приятное для белого использования.
Я ничего не обещаю. Если я что-то обещаю, то делаю это либо потому, что злюсь, либо потому, что я больше уверен в себе, чем в логотипе Кали.
Несколько полезных ссылок:
https://github[точка]com/swisskyrepo/PayloadsAllTheThings/blob/master/GraphQL%20Injection/README.md#common-graphql-endpointshttps://chat.openai[точка]comАвтор grozdniyandy
Источник https://xss.pro/
Последнее редактирование: