НОВОСТИ Write-up CTFZone Quals 2019: Chicken

BDFINFO2.0
Оффлайн
Регистрация
14.05.16
Сообщения
11.398
Реакции
501
Репутация
0
Несмотря на перенос конференции , финалу соревнований быть! В этом году он впервые пройдет в онлайн-режиме и будет активно транслироваться в социальных сетях.

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

qqkwum4pqnrjnrgqxa9__wofqec.png


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


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

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


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



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

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


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

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

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


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

7nnc1nhfmbr0yfkok8ra-hqi1fo.png


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



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

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

ocausuexy92ii3ackgqn2fnh-ta.png


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

droz7stwr6mkzt3buptphi6yt88.png


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

aqnvofypmm8iiotr8xq0koyybtg.png



Используя эти знания о структуре веб-приложения 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

7wkpmqusqncbmjsdigxelqxxy2c.png


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

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



Открыв 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), нам удается открыть с интерфейсом Swagger UI, который используется для описания и выполнения команд через REST API.

zyjqherp-esnmlziil2jnubkram.png


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

tzzb1cui_v-acwn19zzbvi-5kbq.png


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

hx-lwesr4_cxhuu-ryjzb2ku76w.png



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

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

qxikqtcwxuiix9mslp_stalz9ea.png


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

-daaujfp1ocf8i6l9gtf1rtvemi.png


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

3wqd5b7hjj1ftppsvtcxvfdpdyi.png


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

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

cj8ywhk0pr5vmdm8uyabhg26cf0.png


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

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

ohkn3pbbiptuoxndp_xxo-in92q.png


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

qwa1j37cbbppj5gpzr8iz6vdl2q.png


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

Мы снова изучаем часть кода, в которой метод Check_Password_Test() присваивает значение requestUri в исходном запросе HTTP POST, и понимаем, что нужно отправить значение как параметр base_url, и requestUri в конечном итоге получит значение .

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

-daaujfp1ocf8i6l9gtf1rtvemi.png


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

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

username = admin
old_password = pwned","email":"[email protected]","lol":"
new_password = p0tat0
base_url =

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

02h84-3pz0w1c7_bg2hlyqt4k7o.png


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

dlpanwrefhtrjpvetawk6ed2nva.png


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

eezxiswaxjxezkq8cezfrbinggs.png


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

yygeiwxjauomdcsnyqp2pabdcou.png
 
Сверху Снизу