LIMB
Вход Регистрация

тема

SSTIхия сломанных. Погружаемся в природу Server-Side Template Injection в среде PHP.

lumminar · · 0 ответов · 608 просмотров

P.S.: небольшое лирическое отступление. В связи с некоторыми достаточно весомыми обстоятельствами, на данный момент я нахожусь в режиме полу-AFK, и хочу отдельно принести свои извинения тем, кто от этого так или иначе пострадал. Вынужден сообщить, что на некоторый промежуток времени мне придется сбавить темпы, это коснется и проверки: проверять новый материал я буду два раза в неделю(глобально). На моменты моего полного отсутствия я, вероятно, настрою автозалив с тем контентом, что уже сделан и готов к публикации. А пока что, пишем, братья, графоманим, авось не долбанемся. Peace!

Вновь поприветствую всех заинтересованных телеслушателей и радиозрителей, что в очередной раз кликнули на мою статейку. Наше маленькое дружное сообщество становится все больше с каждым днем, и это ли не повод внести посильный вклад в его развитие каждому, кто в состоянии это сделать? 
С таким началом и приступим к данному разбору, посвященному великому и могучему множеству всевозможных вебовых фейлов, растущему в геометрической прогрессии в связи с нынешними тенденциями(в коих каждый желающий, купивший подписку на клод с его вырвиглазной рекламной кампанией, уже эксплоитдев в 0 лет, делающий 300 кк в наносекунду). И коснемся в рамках этого разбора мы еще одной, на мой взгляд, довольно недооцененной уязвимости класса "инъекций" - инъекции темплейта со стороны сервера. ИИ теперь все таки и у разрабов тоже давно трудоустроен)).

Не удалось прогрузить медиа


Содержание
1) Введение и немножко матчасти: знакомимся с уязвой в более широком формате
2) Пример уязвимости "из дикой природы"
3) Что произошло? Причины со стороны сорца
4) Особенности работы с язвой; актуальные практики эксплуатации и поиска с обеих сторон барикад
5) Заключение


Введение и немножко матчасти: знакомимся с уязвой в более широком формате.
Если рассматривать уязвимость, речь о которой пойдет в этом разборе, с точки зрения ее местечка в общей овасповой таксонометрии, можно увидеть некую родственность с SQLi, столь желанной для многих, кто уже давно в теме поиска вебовых дыр: обе уязвимости по сути своей из одной семьи "инъекций", и семья эта согласно последнему топ-тену от OWASP сейчас определяется категорией на 5-ой позиции популярности:

Не удалось прогрузить медиа


Сами уважаемые т-рищи овасповцы определяют категорию инъекций как категорию типажей небезопасного поведения веб-приложения, при котором непроверенные(и потенциально опасные в рассуждении с точки зрения хакера) пользовательские данные каким-то образом попадают к технологии, способной их интерпретировать. Тем самым, создается сценарий, при котором от пользователя в сторону внутренностей веб-приложения могут прийти не обыденные данные, а команда или код. Базированно, не так ли? Но коснуться подобных определений полезно на будущее.

И, ежели перейти в эту категорию, можно увидеть подтверждение верности этого обобщенного определения: большинство соединенных с категорией Common Weakness можно назвать проявлениями "нарушенной фильтрации", но в разных местах. Среди основных приведенных к категории типических уязвимых паттернов есть и паттерны, свойственные конкретным уязвимостям, например, CWE-89, недостаточная нейтрализация спец.элементов в SQL-дате:


Не удалось прогрузить медиа


Но в сухом итоге, все заведомо уязвимые паттерны описывают собой недостаток проверки/валидации исходящей от юзера даты, и в зависимости от места, где валидация недостаточна, рознится и итоговый тип уязвимости(будь то кроссайтовый скриптинг в отраженной - Reflected - форме, когда часть входящих данных просто экранируется, или вполне себе типичные query-параметры, позволяющие прокидывать кавычки).
Но если цель XSS(который тоже инъекция) - "просочиться" в отображаемую страницу, дабы сериализоваться, а цель SQLi - прогнать конкретные SQL-строки в бекенд, то что являет собой SSTI?
Если идти по учебнику, SSTI - Server-Side Template Injection - некий класс уязвимостей, вызванных характерным паттерном, при котором юзак может задать такие входные данные, что сервер посчитает их валидной частью шаблона и/или шаблон, принятый сервером, будет передан в шаблонизатор. За этим последует его интерпретация, после чего выполнение конкретных логических инструкций, вложенных во входные данные(сделанные таковыми, очевидно, юзаком намеренно).

Но это объяснение слишком уж общее, и совсем не раскрывает тему для новичка, который и по названию со словом "темплейт" - считай, шаблон, и со словом "инъекция" примерно то же самое мог понять. И начать раскрытие этой, как мне кажется, довольно интересной темы стоит с самого начала. А спонсором такого рода уязвимостей является такая технология, как шаблонизация.

Шаблонизация - это технология, которая позволяет отделить мух от котлет, а описание и выдачу вывода от алгоритмов, которые этот вывод, собственно, и формируют. И в качестве вывода у нас выступают такие фронтовые вещи, как HTML, или, например, тело писем/вид PDF. Шаблоном, или же темплейтом, в данном случае выступает смесь статики(основной шаблон) с динамикой - местами для подстановки(к примеру, $username) и/или прочими конструкциями, вид которых зависит от конкретных условий. Условия и берутся из "логической части" приложения, в то время как шаблонизация позволяет по предельно простой аналогии воспользоваться трафаретом. Если бы у нас были условные трафареты для рисования цветочка, мы могли бы сами выбрать баллончик краски любого цвета, с которого долбанем графити на стену через этот трафарет.
И именно технология, названная шаблонизацией, и является в контексте художника(бекенда) трафаретом. В зависимости от данных, полученных, например, из бдшки после авторизации, меняется та самая заветная строка "Здравствуйте, это бебрабанк, $username, когда вернете 2700 гривень?". И юзернейм здесь - лишь место для подстановки, значение которого меняется в зависимости от пользователя.

Шаблонизаторы есть ничто иное, как программные компоненты, которые выполняют рендеринг шаблонов - иначе говоря, процесс получения нужного файла из шаблона и параметров, и реализуют логические инструкции, которые можно назвать языком шаблонов. Зачастую, о чем говорят многие разработчики со стажем, язык шаблонов можно считать чем-то неразделимым с шаблонизатором. Основные требования к шаблонизаторам у граждан-разрабов не столь высоки:
  • Базовое экранирование
  • Возможность прописать наследование, то есть, возможность создать взаимосвязанную цепочку шаблонов
  • Работа с динамическими данными и их интеграцией в произвольную часть шаблона
  • Базовый арсенал функций для работы с конвертацией данных в нужные форматы
В сухом же итоге, о шаблонизаторе можно рассуждать как о небольшом интерпретаторе со своей структурой и своим синтаксисом, позволяющим работать с переменными и условиями, применять фильтры и функции, работать с более сложной логикой(например, циклами, наследованием, разветвленной системой условий).
Вполне очевидно, зачем разработчики применяют подобного рода фреймворки в своем стеке: грамотный шаблонизатор решает целый ворох проблем, являющихся той еще костью в горле у разработчиков веб-приложений. Хоть технология и довольно старая, сама по себе она находится в процессе постоянного совершенствования; стоит заметить, что согласно нынешним подсчетам от разных издательств, все популярнее становятся именно шаблонизаторы client-side формата действия: в таком формате работы рендеринг проходит уже со стороны клиента-браузера. Клиенту передается килотонна(да, проблемы с загрузкой часты при неправильной архитектуре и конфигурации клиент-сайд рендеринга ;)) материала в формате пустого HTML-ника-шаблона и кучи JS-кишлака, дабы его оживить. Примером подобной "технологии нового века" станет тот же самый Angular или SolidJS.

Не удалось прогрузить медиа


Задачи, решаемые подобного рода технологическими изысками, очевидны многим, кто имел/имеет отношение не то, что к веб-разработке, а даже к веб-пентесту или системному администрированию:
1) Не нужно писать кучу отдельных веб-страниц под каждую возможную "поисковую строчку" юзера на сайте. Да-да, отчасти "интерактивным шаблонам", меняющимся в зависимости от наших действий, мы обязаны именно этой технологии. Задача фронтендера - лишь прописать трафарет с "дырочками для вставки" из нужных данных; и шаблон будет переиспользоваться, каждый раз превращаясь в нужную страницу с нужной информацией.
2) Скорость разработки.. и да, она сильно возросла. Больше не нужно в каждый HTMLник встраивать "интерактивные части" из JSов и прочей мути, которые помогали бы работать с выдачей данных от той части, что отвечает за внутреннюю бизнес-логику приложения. Хватает нарезать шаблонов и расставить нужные маркеры.
3) Безопасность(да, именно так). Автоматическое экранирование(в том числе всяких опасных конструкций, которыми потенциальный хакер будет кормить все формы ввода и все параметры, что только увидит со своего arjun), и сразу куча угроз, возникающих от неграмотного фронтенда, испаряется. Это и XSS-атаки reflected-типа(отражение конкретной конструкции только тем юзерам, что выполнили условия, например, вкинули определенный ввод в какое-то вводное поле), и защита от прочего класса угроз в формате встроенного в страницу JSного скрипта. Атаки на фронтенд возрастают в своей сложности, при грамотной настройке, комбинации с другими средствами защиты и вовсе невозможны.

Клиент-сайдовый рендеринг шаблонов же, упомянутый выше, решает эти же задачи, но со своими, новыми преимуществами, как более "совершенное" поколение технологий:
1) Рендеринг на клиент-сайде существенно экономит трафик и снижает нагрузку на веб-серверы, балансировщики и прочую "внешнюю инфру", встречающуюся нос к носу с клиентами. Все сводится к одной стартовой загрузке и инициализации нужных элементов, после чего общение с клиентским "клоном" веб-приложения происходит посредством короткой JSON-переписки. Подобная фича преобразует внешнюю платформу, существенно снижая количество точек отказа в формате "а заставлю-ка я 500 ботов постоянно бить в поисковой запрос %random3%, истощая шину сервера". И сервер с покорностью выдрессированной служебной собаки будет носить тяжеленные, разрисованные формы с продуктовыми карточками(например), вместо того, чтобы отправлять коротенькие JSON-отписки.
2) Снижение нагрузки из-за работы шаблонизатора с тысячами клиентов параллельно, нагрузка способна накапливаться весомая, и преимущество более чем очевидно.
3) Становящийся ну очень красивым фронтенд. Интерактивные анимации, околовсяческого рода перетаскивалки-клацалки-нажималки, все становится лишь ответственностью, как говорится, кого-то, но не нас. Все рисует сам браузер. И этот же браузер в данном случае обеспечивает куда более, как говорят американцы, 'user-friendly" интерфейс.

И, как всем(даже мало касающимся веб-пентеста) может стать понятно из банальной логики, с SSTI связана именно первая категория шаблонизаторов - технологии, работающие со стороны сервера(капитан очевидность). И да, как и многие другие уязвимости из отрасли веба, TI тоже бывает двух разрядностей: со стороны сервера и со стороны клиента.
Но зная эти сведения, мы уже примерно понимаем, как уязвимость работает в сути своей и в чем отличие клиентской ее формы от серверной. В то время, как шаблонизатор со стороны сервера - это "маленький интепретатор" вне всяких браузерных песочниц, который может исполнить что-то ради нашей выгоды, максимум, который мы выжмем с клиент-сайда - это интеграция на место для данных в одном из шаблонов какой-то своей произвольной конструкции. И становится понятно, что ничего, кроме как XSS-атаки или клиентского DoS, мы этим серьезного не добьемся, что нас совсем не интересует, так как тут собрались не ради багхантинга во имя получения своих заморских баксов.

Так почему же в итоге в теме статьи указан PHP? Ответов несколько. И первый из них, и, кстати, довольно очевидный, популярность. Структуры на вордпрессе - это и те самые "шопы", которые мы все так обожаем(привет вордпрессникам), и в целом немалая оставшаяся часть интернета, которую можно ломануть. И если мы готовы искать в довольно популярных плагинах под популярные CMS новые нарушения санитизации SQL-трафика, почему бы не посматривать на еще один вектор одним глазком?
Шаблонизаторы с теми или иными нарушениями конфигурации довольно часто можно встретить в тех или иных местах, а единственное требование для того, чтобы язва стала язвой, а не просто мисконфигурацией, - это лишь нарушенная санитизация. И об этом же нам говорят классические, "мапнутые" CWE, связанные с SSTI как уязвимостью: все они завязаны на слове "improper". А таких, к слову, несколько: CWE-74, CWE-94, CWE-150(наиболее классическая и точная, т.к. описывает пропуск спец.символов по типу "{", о чем - позже), CWE-1336(наиболее обобщенная формулировка "под ключ").

Не удалось прогрузить медиа


А второй критерий здесь - это простота. Это так. PHP понятен, не требует титанических мучений в реверсе, и многие и так прекрасно уже в нем ориентируются, приветики оптово-промышленным искателям цифрового золота в снифферах ;)
И, вопреки паникерских крикам товарищей этичЪных, язва эта, по опыту говоря, не столь и истощается. Да и класс инъекций в принципе никогда редкостью не славился.. вот, заходим мы в CVE-алерт, ищем по тэгу, и получаем следующую картину:

Не удалось прогрузить медиа


Если уж цве часто и активно постятся по сей день, а это цве, то что уж говорить о легких фейлах кодеров, встречаемых отдельно даже чаще? Думаю, актуальность сегодняшней темы более чем оправдана.
Соберем простенькую лабораторию, дабы понять, как здесь и что работает, и попробовать новую технику на вкус. Использовать я буду популярный в широких PHP-кругах сервер-сайд шаблонизатор Twig. Он прост в интеграции, что и есть, наверное, один из основных критериев в развертке лабораторного стенда.
Структура будет примитивна и без наворотов:

SSTI-LAB:
    ==> Public
         ==> assets
              ==> style.css(цссная оформа)
         ==> index.php(основная и уязвимая логика)
    ==> templates
         ==> base.twig
         ==> home.twig
         ==> 404.twig (набор собственно шаблонов, с которыми и идет работа)
    ==> Dockerfile
    ==> docker-compose.yml(для легкой и быстрой развертки лабы без испоганивания основной системы ненужным скачиванием)

Основная суть более чем простая:

<?php
declare(strict_types=1);
require __DIR__ . '/../vendor/autoload.php';
use Twig\Environment;
use Twig\Loader\ArrayLoader;
use Twig\Loader\ChainLoader;
use Twig\Loader\FilesystemLoader;
$loader = new FilesystemLoader(__DIR__ . '/../templates');
$twig = new Environment($loader, [
'cache' => false,
'autoescape' => 'html',
]);
$products = [
['name' => 'Сосиски Классические', 'price' => 149, 'unit' => 'уп.'],
['name' => 'Майонез Провансаль 67%', 'price' => 119, 'unit' => 'банка'],
['name' => 'Гречка ядрица', 'price' => 89, 'unit' => 'уп.'],
['name' => 'Молоко 3.2%', 'price' => 99, 'unit' => 'бутылка'],
['name' => 'Хлеб черный', 'price' => 49, 'unit' => 'буханка'],
];
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$path = is_string($path) ? $path : '/';
$path = $path !== '/' ? rtrim($path, '/') : '/';
if ($path === '/' || $path === '/index.php') {
echo $twig->render('home.twig', [
'title' => 'Продуктовый магазин',
'products' => $products,
]);
exit;
}
if ($path === '/search') {
$query = isset($_GET['q']) ? (string) $_GET['q'] : '';
$matched = array_values(array_filter(
$products,
static fn (array $item): bool => $query === '' || stripos($item['name'], $query) !== false
));
$templateSource = "{% extends 'base.twig' %}\n"
. "{% block content %}\n"
. " <h2>Результаты поиска</h2>\n"
. " <p class=\"muted\">Запрос: " . $query . "</p>\n"
. " {% if items|length == 0 %}\n"
. " <p>Товары не найдены.</p>\n"
. " {% else %}\n"
. " <ul class=\"list\">\n"
. " {% for p in items %}\n"
. " <li><strong>{{ p.name }}</strong> — {{ p.price }} ₽ / {{ p.unit }}</li>\n"
. " {% endfor %}\n"
. " </ul>\n"
. " {% endif %}\n"
. "{% endblock %}\n";
$searchTwig = new Environment(new ChainLoader([
new ArrayLoader([
'search_dynamic' => $templateSource,
]),
$loader,
]), [
'cache' => false,
'autoescape' => 'html',
]);
echo $searchTwig->render('search_dynamic', [
'title' => 'Поиск',
'items' => $matched,
]);
exit;
}
http_response_code(404);
echo $twig->render('404.twig', ['title' => 'Не найдено']);

Лаба собрана на коленке, и нужна лишь для того, чтобы наглядно продемонстрировать процесс эксплуатации от и до. И логический смысл здесь во многом идентичен прочим инъекциям, работающим на стороне сервера, по типу SQLi. Что мы здесь видим:
1) Взаимодействие с бизнес-логикой происходит через GET-параметр 'q' от модуля search.
2) Входной путь отдельно отмечен в коде, это путь "search". Далее мы ищем, собственно, сам товар. И эта логика так же безопасна: даже если там будет что-то плохое, оно не пройдет через интерпретатор, это лишь PHP-логика.
3) Происходит формирование шаблона для Twig. Однако, мы явно видим конкатенацию значения параметра query внутрь шаблона, который еще и указан отдельными строками, а не в формате отдельного артефакта для взаимодействия. И конкатенация, происходящая на отрывке <p class=\"muted\">Запрос: " . $query . "</p>\n", является подконтрольной для юзера точкой входа. Через нее он может взаимодействовать с интерпретатором через его официальный синтаксис.
4) Испоганенный темплейт, он же шаблон, передается на рендеринг в сам Twig. Так как в него конкатенируется полный формат значений query, даже не прошедших санитизацию, это предельно опасно.
Подытожим. На примере довольно популярного шаблонизатора Twig можно построить такую образную схему для описания общей механики работы таких штук в формате server-side рендеринга:


Не удалось прогрузить медиа



Юзеру в итоге возвращается готовый, собранный из шаблона красивый HTML с результатами его поиска, ценами и прочей чепухой, свойственной продуктовым магазинам. Запускаем лабораторию, и смотрим, что может случиться.

Не удалось прогрузить медиа


Видим примитивнейший сайт-каталог, без каких-то изысков в оформлении. Пробуем что-то поискать:

Не удалось прогрузить медиа


А теперь юзер, увидев, что HTML изменчив в соответствии с его действиями(в данном случае - поиском), решает проверить базовейший вектор, который до сих пор много где встречается без игры с кодировками - {{7*7}}. Именно: часто шаблонизаторы хорошо реагируют на двойные фигурные скобки, внутри - простейшая арифметическая операция. И что же юзер увидит в ответ?

Не удалось прогрузить медиа


Как мы видим, отображен был не изначальный value нашего параметра-поисковика. В результате мы видим итог этой арифметической операции. И, далее мы можем проверить различные векторы "продолжения" этой атаки. Полезные нагрузки вариируются в зависимости от применяемого шаблонизатора, но мы знаем, что у нас Twig.
Пэйлод выглядит примерно так:
{{ ['id']|filter('system') }}

Что мы видим в итоге?

Не удалось прогрузить медиа


Ровно потому эта уязвимость и является во всех связанных с ней CVE столь высоко оцениваемой: мы получаем RCE в один запрос на веб-сервер. И здесь все просто: так как мы встроили команду интерпретатору напрямую в темплейт, который пройдет через цикл рендеринга, мы видим и результат отработавшей команды id: шаблонизатор думает, что в страницу ему надо поставить именно вывод команды 'id'.

И так, мы можем вывести свое, более понятное и конкретизируемое определение для этой уязвимости. SSTI - Server-Side Template Injection - это уязвимость, характеризуемая недостаточной, неподобающей или вовсе отсутствующей санитизацией пользовательского ввода, отражаемого на веб-страницах с помощью технологий шаблонизации, способных интерпретировать произвольные логические конструкции, оставленные в вводе пользователем.
Уязвимость критична.. но так ли она опасна в дикой природе?

Пример уязвимости "из дикой природы"

Представим такую ситуацию. Некий пентестер нашел веб-сайт, побрутил его веб-директории, внешне его разведал и собрал полную карту. Сайт довольно немаленький, вполне себе инфраструктура, в данном случае - хостинг средней руки из Словении:

Не удалось прогрузить медиа


Идентифицировал используемые технологии, увидел там стек из PHP, MODX в роли CMS, в качестве WAF выступал Cloudflare.

Не удалось прогрузить медиа


И среди множества параметров нашелся довольно обыденный, частый GET-параметр 'search' на богом забытом роуте: https://introserv.com/sl/tutorials/tutorials-search/?search=helloworld!
Атакующий, производящий поиск уязв в классическом формате blackbox, решает позакидывать внутрь параметра всякие непотребства, включая довольно таки классические пайлоады, будь то типичные XSS-конструкции, кавычки и еще бог знает что.
Логика понятна: каждый раз выдается новая HTMLка, значение на ней отражается и зависит от ввода атакующего, через F12 можно узнать, что это - не работа CSR(Client-Side Rendering), и что запрос каждый раз получает ответ с сервера в нужном формате. Кто-то со стороны сервера рисует нам новые страницы по одинаковому шаблону, и так как дело мы имеем с PHP-шной цмской и с самим PHP, стоит проверить основные пайлоады для PHP сервер-сайд шаблонизаторов. И вот, перебор нагрузок доходит до SSTI-шаблонов из Payload-all-the-Things.
Как и с XSS, здесь тоже есть некие "решения-полиглоты", однако, по большей части, пайлоады отличаются в зависимости от применимой технологии:

Не удалось прогрузить медиа



И в один момент происходит следующее:

Не удалось прогрузить медиа


Атакующему удается получить RCE - в данном случае, прочитать одну из важных линуксовых начинок. Но стоит помнить, что делать что-то более серьезное, например, прокидывать сразу реверс-шелл, использовать всякие пайплайны-однострочники с curl | bash и так далее в простом формате через как минимум Cloudflare вряд ли выйдет: даже на бесплатном плане такие попытки эффективно срезаются, что эффективно показала одна только ситуация с React RCE, где сервера, у коих не смогли найти истинный айпи(Origin IP), оставались невредимыми.

И здесь до полной компрометации у атакующего остается всего один трюк, часто применимый, когда через командную инъекцию надо усадить имплант/прокинуть реверс-шелл или совершить любое другое действие. Враги тут - и пробелы, которые надо передать, и распознавание со стороны WAF. Трюк прост:
echo "base64_payload" | base64 -d | bash

Пэйлод проносится под бейзом, и так часто используемым в немалом количестве веб-приложений. Не надо и мучиться с передачей отступов и всяких символов вроде "&", и с WAF колдовать. Что происходит в итоге?

Полная компрометация в один запрос.

Не удалось прогрузить медиа


Сначала кодируется пэйлоад, потом:

Не удалось прогрузить медиа


Инъекция проходит, полезная нагрузка выполняется. Стоит заметить, что в base64 нередки такие символы, как тот же "+", но нельзя просто вставить в ссылку "+" - он воспримется пробелом, на чем, кстати, основано немало байпассов, - те же тамперы на sqlmap ;) Нужно использовать URL-кодировку, что здесь и видно. Вот вам в помощь, тыц.

Атакующий ловит реверс-шелл, допустим, на Metasploit(почему бы и нет? Хороший инструмент.

Не удалось прогрузить медиа


Дальше сюжет прост, избит, понятен. Это и байпасс авторизации в админке MOD-X простым PHP-скриптом, совершающим принудительную аутентификацию под первым попавшимся админом, и все остальные вытекающие действия(жаль, тогда атакующий не знал про CopyFail...).

Не удалось прогрузить медиа


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

Что произошло? Причины со стороны сорца
А теперь часть более интересная, часть, посвященная описанию причины подобного поведения веб-приложения из реальной жизни. Скачав все, что только можно было, и приступив к анализу, мы узнаем, что на данном инстансе MODX был установлен аддон SimpleSearch.

Не удалось прогрузить медиа


SimpleSearch - расширение, которое, согласно заявленному, позволяет облегчить работу с веб-поиском и его реализацией всевозможным лентяям-цмсникам. И здесь именно оно, согласно структуре, отвечало за работу функционала, связанного с GET-параметром 'search'. Покопаем чуть глубже и посмотрим, что конкретно могло произойти такого, что произошла SSTI. Как мы уже заключили чуть ранее, чаще всего причина кроется в недостаточной или вовсе отсутствующей санитизации.

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

subl components/simplesearch/elements/snippets/simplesearch.php

Не удалось прогрузить медиа


Как мы видим, ничего необычного. У нас есть конкретная цель в самом начале: надо создать экземпляр поискового двигла. После того, как двигло уже есть, мы говорим ему, что слушать надо параметр "search", и также говорим, что в случаях, когда бизнес-логика ответа на вопрос юзера не сыщет, нужно брать конкретный "шаблон-404"(хобана, сервер-сайд шаблонизация). Видим функцию parseSearchString, которая тут вызывается на серч индекс. Ту самую величину, которая берется конкретно из запроса юзера в гет-параметр 'search'. Прыгнем на нее.

subl components/simplesearch/src/simplesearch.php

Не удалось прогрузить медиа


Смотрим на различия в директориях: components - классическая "общая" директория для кишков MODX, внутри simplesearch - две поддиректории. Одна - snippets, и она содержит актуально работающие на сайте объекты PHP, уже привязанные к какому-то функционалу. В то же время, /src/ содержит гольные исходники, объявляющие весь нужный функционал(позже импортируемый в те же сниппеты по функции).
Картинка же внутри становится чуть яснее. Теперь мы понимаем, где берет начало наша отработавшая цепочка эксплуатации. Все дело в том, что санитизация, изображенная здесь, не может называться "достаточной". Мы видим фильтрацию специализированных PHP/HTML тегов и конструкций. Здесь это происходит отдельно от основных методов санитизации из MODX, в этом фрагменте так же применимых, и является чем-то, судя по всему, нацеленным на защиту от XSS в разных формах(так как аддон отвечает за поиск, а динамичные параметры, работающие с ним - лакомая цель, логично это предположить). Режем тяжелые запросы, режем "[" и "]", являющихся зарезервированными для MODX-тегов, стоит заметить, что есть и разбивка, то есть, скули будут проблематичны. А вот от '{' мы не фильтруем. Здесь мы взяли начало. Но куда мы пошли дальше?

Логика правильна. Нам следует искать энтрипоинт, который выведет запрос пользователя в обработку, под процесс шаблонизации. И здесь это происходит довольно скучно.
$response = $search->getSearchResults($searchString, $scriptProperties);
$placeholders = array('query' => $searchString);
$resultsTpl = array('default' => array('results' => array(), 'total' => $response['total']));

Так формируется содержимое темплейта с результатом. Видим передачу результатов, общего количества('total'), прочего наполняющего для темплейта. Так:


if (empty($_REQUEST[$searchIndex])) {
$output = $search->getChunk($noResultsTpl, array(
'query' => '',
));

return $search->output($output, $toPlaceholder);
}
$searchString = $search->parseSearchString($_REQUEST[$searchIndex]);
if (!$searchString) {
$output = $search->getChunk($noResultsTpl, array(
'query' => $searchString,
));

return $search->output($output, $toPlaceholder);
}

система определяет, если строчка пустая изначально(без значения)/пустая по результатам правил из parseSearchString, рассмотренной ранее как функции. Если условия выполнены, будет подгружен тот самый "шаблон-404".

В сухом итоге, существует функция, которая пытается вызвать метод из уже другого элемента - pdoTools. В контексте нашего маленького расследования его стоит называть "оркестратором" для процесса серверсайд-рендеринга наших шаблонов. Почему? Узнаем совсем скоро.

Не удалось прогрузить медиа


Здесь и происходит делегация процесса рендеринга из нашего гетчанка в гетчанк пдошный. Он уже и является ответственным за руководство над процессом рендеринга. Передаем имя шаблона, который будет использован за основу, передаем массив данных, которые будут частью уже заполненного шаблона. Однако, суть кроется в том, что сам по себе pdoTools как элемент будет пытаться вызвать Fenom, если такая опция включена:
     
$properties = array_merge($data['properties'], $properties);
$content = $this->config['useFenom']
? $this->getFenom()->process($data, $properties)
: $data['content'];

А уже Fenom в конфигурации оказался включенным. Стоит сразу сказать, что Fenom - еще один популярный игрок рынка двиглов-шаблонизаторов, и он действительно часто используется. От себя скажу так, он специфичен, но нам это не сильно важно. Важнее для нас другое. Как мы уже проследили, наш поток проходит санитизацию, попадает в массив, которым будет наполнен в последствии шаблон, и передается в феном.
А феном уже решает на основании своего метода-чекера, будет ли он такое обрабатывать:
public function process($chunk, array $properties = [])
{
$content = is_array($chunk)
? trim($chunk['content'])
: trim($chunk);
if (empty($this->pdoTools->config('useFenom')) || !preg_match($this->pdoTools->config('fenomSyntax'), $content)) {
return $content;
}

Чекеру важны лишь несколько условий. Включен ли феном(что уже ранее проверялось в pdoTools в момент разрешения вопроса, будет ли делегация), соответствует ли вводняк синтаксису фенома. Сам же вводняк - это массив из чанков, упомянутых ранее.
Но только вот Fenom может обрабатывать конструкции по типу {...}.

Схема не столь и сложна. А по факту, это уже можно спокойно записать в отдельную CVE, связанную с отдельным аддоном и MODX всецело(благо на этой недоцмс сидит не так много народу, как на старшем брате-вордпрессе).
И сюда же применимы CWE, упомянутые в самом начале(CWE, связанные с SSTI как отдельной, самостоятельной уязвимостью).

Особенности работы с язвой; актуальные практики эксплуатации и поиска
Как и всегда, идти мы можем с двух возможных сторон. С одной стороны, никто не мешает нам скармливать в хорошие SAST-движки(обзор на пару из них уже от меня был чуть ранее)) кучи плагинов, аддонов, фреймворков, дабы набирать себе знаний и готовых эксплоитов, с другой стороны, во время блекбокс-тестирования какого-то ресурса никто не мешает прописать кастом-чек для Acunetix/темплейт для Nuclei с функцией фаззинга, которые бы спокойно справились с такой задачкой.

Что можно посоветовать при тестировании снаружи?
1. Важный момент - собрать как можно больше информации о внешнем периметре касательно вопросов "куда можно что-то ввести". Отлично помогают с этими целями такие инструменты, как arjun(для раскрытия "потайных параметров", да, такое бывает), katana(просто хороший, универсальный во многих смыслах краулер, который поможет аккурат пролезть сквозь сайт, собрать все инпут-формы и дефолтные пути), waymore(инструмент, работающий на API, стероидная замена старому-доброму GAU, который тоже так то неплох, однако, waymore поддерживает интеграцию с urlfinder - ценнейшим в таких целях ресурсом).

Пример:
export target=<вашассылочканацель>
echo $target | proxychains katana -d 5 -jc  (где под proxychains мы записываем 127.0.0.1:8080, тем самым расширяя себе поверхность для атаки в удобных Caido/Burp)

echo $target | gau | httpx-toolkit -mc 200 -silent - массово собрать под изучение веб-директорий в формате пассивной разведки. Запросов кратно меньше, чем при брутфорсе директорий или краулинге, - из взаимодействий только валидация с помощью httpx-toolkit.

2. В Burp никогда не помешает добавить словарик с пэйлодами-полиглотами и пэйлодами на распространенные фреймворки. Опять же, вопрос лишь усидчивости, гуглинга и внимательности при изучении популярных платформ-агрегаторов. Разумеется, популярный Payload-All-The-Things - один из ресурсов, для новичка в вопросе к изучению обязательный. Ищем те эндпоинты, где ответ отражает запрос.
3. Хорошим помощником будет и какой-то сортировщик, даже если это grep: куда удобнее изначально отфильтровывать параметры, чтобы потом только их, точечно, так сказать, скормить в нуклей на темплейты.

4. Acunetix справляется с задачей не слишком хорошо, и к нему лучше писать кастомный скан. Этим мы займемся в одной из следующих статей, но а так, в этот раз он условно справился ;)



Что можно сказать о тестировании "изнутри" - по сорцу?
1. Обязательно просматривайте те логические части, что отвечают за санитизацию. Если через них проходит то, что потом в потенциале отразится на веб-странице, взаимодействие с какой-то технологией шаблонизации более, чем очевидно. А именно это нас и интересует.
2. Свой маленький ruleset на opengrep/semgrep - в целом, хорошая идея, и я до сих пор использую по большей части случаев именно их в анализе всего вебового. Если и не смотреть на них с парадигмы "универсальной открывашки", как минимум, ими можно очень удобно "размотать клубок" и найти взаимосвязи. Куда быстрее, чем вручную в sublime долбить Ctrl+F и скакать по функциям, зарабатывая укачивание.
3. Многие полезности уже были в разделах "issue" на Github, порой подолгу.. кто бы их использовал. В целом, тут стоит гуглить как можно больше.. синтаксис, функционал, применение.. все, что можно.
4. Кавычки, скобки, фигурные скобки, плюсы.. слушайте братьев старших-скулистов, они вам расскажут, как санитизацию наизнанку на предмет дырок проглядывать, и не только ее.

Заключение
Чаще всего SSTI - невероятно сильная находка. Как бы, вы все видели сами, тут я думаю сильно умника включать не надо, RCE за 1 запрос - это в целом-то приятно, приятно всем вне зависимости от отрасли ;)
И опять же, виноваты ни в коем случае не сами по себе фреймворки, они при правильном конфиге, так то, более чем безопасны. Но никогда не надо отметать хоть призрачный, но шанс найти что-то вкусное в каком-то очередном плагине, чтобы потом массово за этим охотиться. А так, заключение здесь довольно простое. Не едиными скулями живы, и на самом деле, на рынке "мелких, но вкусных" еще не перевелись легкие жертвы. Если приноровиться их колоть, можно и навыки свои расширить, и половить довольно сочные в моменте проды, которые либо собраны на коленке, либо конфигурированы на коленке. Инъекции почти во всех формах еще долго будут живы.
Оставлю лишь маленькое напоминание исследователям, никогда не стоит ограничивать свой конкретный лист задач на тестировании отдельного фреймворка в отдельном формате. Никогда не помешает посмотреть на задачу со стороны "коллективного общего": часто именно в таких вещах кроются подобные атаки - один не отфильтровал, как надо, другой как надо не обработал. Ближайшие недели 2 - плодотворны для RCE-атак.. к нам в руки попал копифейл.
👍56 ❤️9 🔥11 😁14 😮7 🤔14

Гостевой просмотр. Чтобы отвечать и ставить реакции, войдите или зарегистрируйтесь.