13 March

Write-up CTFZone Quals 2019: Chicken

BI.ZONE corporate blogInformation SecurityCTF
Несмотря на перенос конференции OFFZONE 2020, финалу соревнований CTFZone быть! В этом году он впервые пройдет в онлайн-режиме и будет активно транслироваться в социальных сетях.

О подробностях мы объявим позже, а пока предлагаем изучить райтап веб-таска с отборочного этапа. Разбор решения нам прислал Devand MacLean из Канады. Специально для «Хабра» мы перевели текст райтапа и приглашаем узнать, с какой цепочкой уязвимостей столкнулись участники и при чем здесь курица.



Общая информация


Автор таска: Павел Сорокин
Баллы: 470
Количество команд, решивших таск: 2

Полезные ссылки:



Описание таска


На первый взгляд, веб-приложение содержало:


  • домашнюю страницу (/Home/Index);
  • страницу про куриц (/Home/Hens);
  • страницу с контактами (/Home/Contact);
  • страницу для входа (/Auth/Login).

Позднее, в процессе анализа, также были обнаружены:


  • страница для изменения пароля пользователя;
  • второй интерфейс с API для входа, восстановления и изменения пароля.

Решение таска


На странице /Home/Hens содержатся ссылки на загрузку «паспортов» куриц.


После декодирования Base64 находим параметр filename: 1.txt. С помощью CyberChef кодируем /etc/passwd в Base64 и переходим по ссылке:

http://web-chicken.ctfz.one/File/Download?filename=L2V0Yy9wYXNzd2Q=

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

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


Увидев, что ошибка ссылается на переменную среды ASPNETCORE_ENVIRONMENT, мы начинаем изучать макеты веб-приложений ASP.NET Core. И видим следующее:


Чтобы преобразовать нужные пути и имена файлов и извлечь их в Base64, пишем небольшой скрипт на Python (fetch.py):


Используя эти знания о структуре веб-приложения MVC, созданного в ASP.NET Core, с помощью fetch.py получаем исходный код следующих файлов:


  • ../Views/Shared/_Layout.cshtml
  • ../Views/Home/Index.cshtml
  • ../Views/Home/Contact.cshtml
  • ../Views/Home/Hens.cshtml
  • ../Views/Auth/Login.cshtml

Сделав это и изучив исходный код каждой страницы, мы находим интересную деталь в Hens.cshtml


В строке 1 есть оператор включения @ using StackHenneryMVCAppProject, который в ASP.NET означает ссылку на DLL-файл.

Открываем ../StackHenneryMVCAppProject.dll — но не через утилиту Python в Kali Linux, а в браузере на Windows, потому что собираемся декомпилировать этот файл в Windows. Используем для этого такой URL:

http://web-chicken.ctfz.one/File/Download?filename=Li4vU3RhY2tIZW5uZXJ5TVZDQXBwUHJvamVjdC5kbGw=

Открыв DLL-файл в dnSpy (декомпиляторе .NET), сразу видим, что в методе Initialize() класса Config.Configuration, который выполняется при запуске веб-приложения, считывается содержимое файла chicken_domains_internal.txt. С помощью скрипта Python извлекаем содержимое этого файла:

web-chicken-flag
web-chicken-auth

Метод Initialize() подключается к web-chicken-flag через порт 4321 и получает несколько параметров: secret_token, RSA_key и flag.

К сожалению, инициировать подключение к web-chicken-flag не получается. Но, когда мы задаем локальную DNS-запись hosts, чтобы перевести web-chicken-auth на внешний IP-адрес сервера (34.89.232.240), нам удается открыть http://web-chicken-auth/ с интерфейсом Swagger UI, который используется для описания и выполнения команд через REST API.


Еще покопавшись в декомпилированном DLL-файле, можно заметить, что у контроллера AuthController был не только метод Login(), но и метод Change_Password_Test():


При переходе к /Auth/Change_Password_Test мы видим такую форму:


При изучении метода Change_Password_Test() мы понимаем, что он обрабатывает четыре параметра типа String через запрос HTTP POST:


  • username;
  • new_password;
  • old_password;
  • base_url.


Чуть ниже в методе Change_Password_Test() видим: не получив значения для base_url, метод берет значение conf.auth_server, то есть web-chicken-auth, а затем добавляет /auth/login в конец значения base_url, назначая получившуюся строку переменной requestUri:


В следующей важной части кода строковые значения параметров username и old_password из запроса HTTP POST, а с ними и secret_token из конфигурации вставляются в строку JSON. Затем из этих данных создается объект JSON StringContent, который отправляется как данные HTTP POST в переменную requestURI, созданную на предыдущем шаге.


Теперь сервер ждет, чтобы запрос HTTP POST к requestUri вернул объект JSON с параметром code, равным 0. Если этого не происходит, мы попадаем в ветвь кода, которая начинается со строки 6 и возвращает представление /Views/Auth/Login с ошибкой Invalid login/password.

Если запрос HTTP POST все-таки возвращает объект JSON с параметром code, равным 0, мы переходим в ветку кода со строки 11. Тогда переменная requestUri2 получает значение http://web-chicken-auth, создается еще один объект JSON StringContent с параметрами из исходного запроса HTTP POST, как в предыдущей части, и эти данные отправляются на адрес requestUri2 в запросе HTTP PUT. Наконец, клиенту возвращается сообщение Password changed.


В двух словах: весь процесс начинается с запроса HTTP POST к /Auth/Change_Password_Test, который ожидает определенное число параметров. Он берет эти значения и создает объект JSON, чтобы отправить его в <base_url>/auth/login через HTTP POST. Если в ответ он получает JSON с параметром code, равным 0, то создает еще один объект JSON и отправляет запрос HTTP PUT на http://web-chicken-auth/auth/change_password.

При изучении REST API в Swagger UI мы видим, что в декомпилированном DLL-файле были не только маршруты /auth/login и /auth/change_password, но и маршрут для /auth/password_recovery.


Этот API-маршрут ожидал объект JSON с двумя параметрами: email и secret_token. Если он его получал, то возвращал объект JSON со свойством code, равным 0, и строками message и token.


Получается, что если мы как-то заставим метод Check_Password_Test() сразу запросить маршрут /auth/password_recovery вместо /auth/login, то для входа в ветку смены пароля достаточно будет ввести действительный электронный адрес.

Мы снова изучаем часть кода, в которой метод Check_Password_Test() присваивает значение requestUri в исходном запросе HTTP POST, и понимаем, что нужно отправить значение http://web-chicken-auth/auth/password_recovery# как параметр base_url, и requestUri в конечном итоге получит значение http://web-chicken-auth/auth/password_recovery#/auth/login.

Символ # в HTTP URL указывает, что часть адреса с запросом закончилась. Поэтому сервер, который получит запрос, просто проигнорирует часть /auth/login.


Это, конечно, хорошо, но нам осталось придумать, как REST endpoint, куда мы теперь перенаправляем запрос, получит значение email. Без этого значения запрос завершится ошибкой, и дальше мы не продвинемся.

Внимательно приглядываемся к тому, как метод Check_Password_Test() создает данные JSON, и видим, что входные данные совсем никак не очищаются, а значит, можно запросто внедрять свои параметры. Достаточно отправить следующие параметры в запросе HTTP POST:

username = admin
old_password = pwned","email":"admin@chicken.ctf.zone","lol":"
new_password = p0tat0
base_url = http://web-chicken-auth/auth/password_recovery#

Созданный в результате объект JSON отправляется в /auth/password_recovery:


С этими данными JSON мы выполнили требования, по которым /auth/password_recovery должен вернуть ответ, после чего сервер войдет в ветку изменения пароля (объект JSON содержит свойство email). Теперь значение old_password нам уже не нужно. В этой ветке кода мы предоставили все значения, необходимые для изменения пароля (свойство username имеет значение admin, а свойство passwordp0tat0). Запрос выполнен, и мы получаем Password changed в ответ.


Осталось только ввести новое имя пользователя и пароль на странице входа!


И вот наш флаг:

Tags:ctfCTFZonesecurityинформационная безопасностьoffzone
Hubs: BI.ZONE corporate blog Information Security CTF
+6
722 8
Leave a comment