
JetEngine <= 3.7.7 — CCT REST API के माध्यम से बिना प्रमाणीकरण के संग्रहीत क्रॉस-साइट स्क्रिप्टिंग
दिनांक: 11 मार्च 2026 CVSS स्कोर: 7.1 (उच्च) CVSS वेक्टर: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:L प्रभावित प्लगिन: JetEngine <= 3.7.7 प्लगिन स्लग: jet-engine संस्करण जिसमें ठीक किया गया: 3.7.8 CVE: CVE-2025-67923 CWE: CWE-79 शोधकर्ता: Bonds (Patchstack के माध्यम से) रिपोर्ट किया गया: 19 अक्टूबर 2025 सार्वजनिक किया गया: जनवरी 2026 परीक्षण किया गया WordPress संस्करण: नवीनतम
CVE-2025-67923 JetEngine WordPress प्लगिन में एक बिना प्रमाणीकरण के स्टोर्ड क्रॉस-साइट स्क्रिप्टिंग भेद्यता है जो 3.7.7 तक के सभी संस्करणों को प्रभावित करती है। एक बिना प्रमाणीकरण वाला हमलावर सार्वजनिक REST API के माध्यम से एक कस्टम कंटेंट टाइप (CCT) टेक्स्ट फ़ील्ड में मनमाना HTML/JavaScript लिख सकता है, जिसे बिना सैनिटाइज़ किए JavaScript innerHTML सिंक के माध्यम से Maps Listing विजेट में DOM में इंजेक्ट किया जाता है, जब कोई पीड़ित उस विजेट वाले किसी भी पृष्ठ पर जाता है।
इस हमले के लिए न तो प्रमाणीकरण की आवश्यकता है और न ही किसी विशेषाधिकार की। इसके लिए केवल इतना आवश्यक है:
public पर सेट हो।✅ भेद्यता की पुष्टि हुई — स्टोर्ड XSS पेलोड लिखा और निष्पादित किया गया
पुष्टि किया गया प्रभाव:
यह भेद्यता दो स्वतंत्र कमज़ोरियों का संयोजन है:
इनपुट सैनिटाइज़ेशन की कमी (स्टोर): CCT आइटम हैंडलर की sanitize_field_value() विधि में text-प्रकार के फ़ील्ड के लिए कोई सैनिटाइज़ेशन पथ नहीं है। default: केस केवल टाइमस्टैम्प परिवर्तित करता है — कच्चा HTML गुज़र जाता है और डेटाबेस में संग्रहीत हो जाता है।
DOM-आधारित XSS सिंक (रेंडर): Maps Listing विजेट get_marker_label() के माध्यम से संग्रहीत फ़ील्ड मान पढ़ता है, इसे data-markers HTML विशेषता के लिए htmlspecialchars(json_encode(...)) में लपेटता है, और JavaScript फ्रंटएंड विशेषता को getAttribute() (जो HTML इकाइयों को डिकोड करता है) के साथ पढ़ता है, फिर markerData.label को सीधे innerHTML के माध्यम से इंजेक्ट करता है — जिससे कोई भी एम्बेडेड HTML/JavaScript निष्पादित हो जाता है।
| फ़ाइल | समस्या |
|---|---|
includes/modules/custom-content-types/inc/rest-api/public-controller.php:424 | create_item_permissions_check तब true लौटाता है जब CCT rest_put_access = 'public' हो — कोई प्रमाणीकरण आवश्यक नहीं |
includes/modules/custom-content-types/inc/item-handler.php:489 | sanitize_field_value() default: शाखा — text प्रकार के फ़ील्ड के लिए कोई HTML सैनिटाइज़ेशन नहीं |
includes/modules/maps-listings/inc/render.php:233 | get_marker_label() बिना esc_html() के कच्चा मेटा मान लौटाता है |
includes/modules/maps-listings/inc/render.php:247 | htmlspecialchars(json_encode($result)) — केवल विशेषता सीमा की रक्षा करता है, innerHTML इंजेक्शन की नहीं |
includes/modules/maps-listings/assets/js/frontend-maps.js:112 | pinData.content = general.marker.html.replace('_marker_label_', markerData.label) — कच्चा लेबल HTML स्ट्रिंग में डाला जाता है |
includes/modules/maps-listings/assets/js/public/mapbox-maps.js:175 | el.innerHTML = data.content — XSS निष्पादन सिंक |
includes/modules/maps-listings/assets/js/public/leaflet-maps.js:34 | contentHtml.innerHTML = content — XSS निष्पादन सिंक |
CCT सार्वजनिक REST नियंत्रक check_user_permissions() के माध्यम से अनुमतियाँ जाँचता है:
// 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; // कोई प्रमाणीकरण आवश्यक नहीं
} else {
return current_user_can( $cap );
}
}
public function create_item_permissions_check( $request ) {
return $this->check_user_permissions( $request, 'rest_put_access' );
}
जब कोई CCT rest_put_access = 'public' (सार्वजनिक-सामना करने वाले फ़ॉर्म के लिए एक समर्थित, प्रलेखित कॉन्फ़िगरेशन) के साथ कॉन्फ़िगर किया जाता है, तो एंडपॉइंट पूरी तरह से बिना प्रमाणीकरण के होता है। कोई भी HTTP क्लाइंट /wp-json/jet-cct/{slug} पर POST कर सकता है।
REST हैंडलर $handler->update_item($params) को कॉल करता है जो sanitize_field_value() तक पहुँचता है:
// item-handler.php:489-562
public function sanitize_field_value( $value, $field ) {
$type = isset( $field['type'] ) ? $field['type'] : false;
switch ( $type ) {
case 'repeater': // उप-फ़ील्ड सैनिटाइज़ करता है
// ...
case 'checkbox': // बूलियन सरणियों को संभालता है
case 'checkbox-raw':
// ...
case 'media':
case 'gallery': // मीडिया JSON सैनिटाइज़ करता है
// ...
case 'wysiwyg':
$value = jet_engine_sanitize_wysiwyg( $value ); // सैनिटाइज़ किया गया
break;
default:
// टेक्स्ट प्रकार यहाँ आता है — केवल टाइमस्टैम्प रूपांतरण, कोई HTML सैनिटाइज़ेशन नहीं
$value = $this->factory->maybe_to_timestamp( $value, $field );
}
return $value;
}
एक text-प्रकार का फ़ील्ड default: शाखा में आता है। maybe_to_timestamp() गैर-दिनांक स्ट्रिंग्स के लिए मान को अपरिवर्तित लौटाता है। XSS पेलोड `` शब्दशः संग्रहीत हो जाता है।
जब Maps Listing विजेट रेंडर होता है, get_marker_label() फ़ील्ड पढ़ता है:
// 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 );
// यहाँ कोई esc_html() नहीं — कच्चा मान लौटाया गया
}
break;
}
return $result; // "" लौटाता है
}
लौटाया गया मान मार्कर डेटा सरणी में रखा जाता है:
// render.php:231-237
$result[] = array(
'id' => $post_id,
'latLang' => $latlang,
'label' => $this->get_marker_label( $post, $settings ), // कच्चा XSS पेलोड
// ...
);
मार्कर सरणी को HTML विशेषता के लिए एन्कोड किया जाता है:
// render.php:247
return htmlspecialchars( json_encode( $result ) );
json_encode `` को स्ट्रिंग "" के रूप में क्रमबद्ध करता है। htmlspecialchars फिर पूरे JSON को HTML-एन्कोड करता है, जिससे उत्पन्न होता है:
[{...,"label":"<img src=x onerror=alert(1)>",...}]
यह data-markers HTML विशेषता में लिखा जाता है। एन्कोडिंग केवल विशेषता सीमा की रक्षा करती है। जब JavaScript विशेषता पढ़ता है, तो ब्राउज़र HTML इकाइयों को डिकोड करता है, मूल वर्णों को पुनर्स्थापित करता है:
// ब्राउज़र dataset / getAttribute के माध्यम से पढ़ते समय स्वचालित रूप से इकाइयों को डिकोड करता है
const markers = JSON.parse(el.dataset.markers);
// markers[0].label === ''
frontend-maps.js में, कच्चा लेबल स्ट्रिंग सीधे HTML कंटेंट स्ट्रिंग में डाला जाता है:
// frontend-maps.js:112
pinData.content = general.marker.html.replace( '_marker_label_', markerData.label );
// pinData.content = '<div class="jet-map-marker-wrap">
//
// </div>'
यह कंटेंट स्ट्रिंग फिर मार्कर तत्व के innerHTML के रूप में सेट की जाती है: