JS Help Desk – AI-Powered Support & Ticketing System <= 3.0.9 - Missing Authorization
Description
The JS Help Desk – AI-Powered Support & Ticketing System plugin for WordPress is vulnerable to unauthorized access due to a missing capability check on a function in versions up to, and including, 3.0.9. This makes it possible for unauthenticated attackers to perform an unauthorized action.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:NTechnical Details
<=3.0.9What Changed in the Fix
Changes introduced in v3.1.0
Source Code
WordPress.org SVN# Vulnerability Research Plan: CVE-2026-48887 (JS Help Desk) ## 1. Vulnerability Summary The **JS Help Desk – AI-Powered Support & Ticketing System** plugin (versions <= 3.0.9) contains a **Missing Authorization** vulnerability. The plugin registers several AJAX actions that fail to implement appro…
Show full research plan
Vulnerability Research Plan: CVE-2026-48887 (JS Help Desk)
1. Vulnerability Summary
The JS Help Desk – AI-Powered Support & Ticketing System plugin (versions <= 3.0.9) contains a Missing Authorization vulnerability. The plugin registers several AJAX actions that fail to implement appropriate capability checks (using current_user_can()) or ownership verification. This allows unauthenticated attackers to perform unauthorized actions, specifically deleting ticket attachments, by sending crafted requests to the admin-ajax.php endpoint.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - Action:
js_ticket_delete_attachment(Note: In this plugin, AJAX actions are routed through a controller that maps them to model functions). - Vulnerable Function:
JSSTattachmentModel::deleteAttachment(inferred fromincludes/includer.phpmodule mapping). - Parameter:
id(The database ID of the attachment to be deleted). - Authentication: Unauthenticated (via
wp_ajax_nopriv_hooks usually registered in the main controller). - Severity: Medium (CVSS 5.3) - Allows unauthorized modification (deletion) of data.
3. Code Flow
- Entry Point: An AJAX request is sent to
admin-ajax.phpwithaction=js_ticket_delete_attachment. - Routing: The plugin's main controller (referenced in
includes/includer.phpasmodules/js-support-ticket-controller.php) catches the action. - Model Loading: The controller uses
JSSTincluder::getJSModel('attachment')to load the attachment model. - Processing: The model's deletion function is called.
- Missing Check: The function proceeds to execute a SQL query (targeting the
js_ticket_attachmentstable defined inincludes/activation.php) and unlinks the file (using paths derived inincludes/classes/uploads.php) without verifying if the current requester is the owner of the ticket or an administrator.
4. Nonce Acquisition Strategy
While the vulnerability is "Missing Authorization," the plugin frequently uses nonces for AJAX requests to prevent CSRF.
- Identify Script Handle: According to
includes/classes/customfields.php, the plugin enqueues scripts with the handlejs-support-ticket-main-js. - Create Trigger Page: The plugin's main functionality is triggered by the
[jssupportticket]shortcode.- Command:
wp post create --post_type=page --post_title="Support" --post_status=publish --post_content='[jssupportticket]'
- Command:
- Navigate and Extract:
- Navigate to the newly created page.
- The plugin localizes data into a JavaScript object, often named
js_support_ticket_ajaxorjssupportticket. - Based on
customfields.phpwhich useswp_create_nonce("is-field-required-..."), other nonces are likely in the same global scope.
- Target Nonce: Search for a nonce related to attachments or a general "security" nonce.
browser_eval("window.jssupportticket?.nonce")browser_eval("window.jsst_ajax?.nonce")
5. Exploitation Strategy
We will attempt to delete an attachment created during setup.
Step-by-Step Plan:
- Identify Attachment ID: Use
wp_clito find an existing ID in thewp_js_ticket_attachmentstable. - Craft AJAX Request:
- URL:
http://localhost:8080/wp-admin/admin-ajax.php - Method:
POST - Headers:
Content-Type: application/x-www-form-urlencoded - Body:
action=js_ticket_delete_attachment&id=[TARGET_ID]&_ajax_nonce=[NONCE]
- URL:
- Analyze Response: A successful deletion usually returns a JSON success status or a "1".
6. Test Data Setup
- Activate Plugin: Ensure
js-support-ticketis active. - Create Ticket Page:
wp post create --post_type=page --post_title="Support" --post_status=publish --post_content='[jssupportticket]'
- Insert Dummy Attachment: Since we need an ID to delete, manually insert a record into the custom table defined in
includes/activation.php.wp db query "INSERT INTO wp_js_ticket_attachments (id, ticketid, filename, created) VALUES (1337, 1, 'secret_config.pdf', NOW())"- Note: The table name uses the
wp_js_ticket_attachmentsformat based onjssupportticket::$_db->prefix . "js_ticket_attachments".
7. Expected Results
- The HTTP response from
admin-ajax.phpshould indicate a successful operation. - The file record with ID
1337should be removed from thewp_js_ticket_attachmentstable.
8. Verification Steps
After the attack, verify the deletion via wp-cli:
wp db query "SELECT COUNT(*) FROM wp_js_ticket_attachments WHERE id = 1337"
If the count is 0, the "Missing Authorization" vulnerability is confirmed as we performed a deletion without valid session credentials.
9. Alternative Approaches
If js_ticket_delete_attachment fails or requires high privileges in the current version, investigate:
- Action:
jsst_remove_attachment - Action:
js_ticket_remove_attachment - Custom Fields Data:
getDataForVisibleField(fromcustomfields.php). While likely for data retrieval, if it accepts an ID and returns sensitive user-defined field data, it could constitute a different unauthorized access path (Confidentiality impact). - Direct Controller Routing: If the main controller handles requests via a
requestparameter:action=jsticket_ajax&request=deleteAttachment&id=1337
Summary
The JS Help Desk plugin for WordPress is vulnerable to unauthorized data deletion in versions up to 3.0.9 due to missing capability checks on AJAX actions. Unauthenticated attackers can exploit this to delete ticket attachments by sending crafted requests to the admin-ajax.php endpoint.
Security Fix
@@ -201,8 +201,8 @@ ('tplink_faqs_user', '0', 'tplink', 'faq'), ('show_breadcrumbs', '1', 'default', NULL), ('productcode', 'jsticket', 'default', NULL), - ('versioncode', '3.0.9', 'default', NULL), - ('productversion', '309', 'default', NULL), + ('versioncode', '3.1.0', 'default', NULL), + ('productversion', '310', 'default', NULL), ('producttype', 'free', 'default', NULL), ('tve_enabled', '2', 'default', NULL), ('tve_mailreadtype', '3', 'default', NULL), @@ -610,7 +610,7 @@ if (!is_admin()) { $jsst_inquery .= ' AND adminonly != 1 '; } - $jsst_query = "SELECT field,fieldtitle,isuserfield,userfieldtype,userfieldparams,multiformid FROM " . jssupportticket::$_db->prefix . "js_ticket_fieldsordering WHERE isuserfield = 1 AND " . $jsst_published . " AND fieldfor =" . esc_sql($jsst_fieldfor) . $jsst_inquery. " AND multiformid =" . esc_sql($jsst_multiformid). " ORDER BY ordering"; + $jsst_query = "SELECT field,fieldtitle,isuserfield,userfieldtype,userfieldparams,multiformid FROM " . jssupportticket::$_db->prefix . "js_ticket_fieldsordering WHERE isuserfield = 1 AND " . $jsst_published . " AND fieldfor =" . intval($jsst_fieldfor) . $jsst_inquery. " AND multiformid =" . intval($jsst_multiformid). " ORDER BY ordering"; $jsst_data = jssupportticket::$_db->get_results($jsst_query); return $jsst_data; } @@ -628,7 +628,7 @@ $jsst_inquery .= " AND adminonly != 1"; } - $jsst_query = "SELECT `rows`,`cols`,required,field,fieldtitle,isuserfield,userfieldtype,userfieldparams,depandant_field FROM " . jssupportticket::$_db->prefix . "js_ticket_fieldsordering WHERE isuserfield = 1 AND " . $jsst_inquery . " AND fieldfor =" . esc_sql($jsst_fieldfor) ." ORDER BY ordering "; + $jsst_query = "SELECT `rows`,`cols`,required,field,fieldtitle,isuserfield,userfieldtype,userfieldparams,depandant_field FROM " . jssupportticket::$_db->prefix . "js_ticket_fieldsordering WHERE isuserfield = 1 AND " . $jsst_inquery . " AND fieldfor =" . intval($jsst_fieldfor) ." ORDER BY ordering "; $jsst_data = jssupportticket::$_db->get_results($jsst_query); return $jsst_data; } @@ -638,7 +638,7 @@ return false; } - $jsst_query = "SELECT `rows`,`cols`,required,field,fieldtitle,isuserfield,userfieldtype,userfieldparams,depandant_field FROM " . jssupportticket::$_db->prefix . "js_ticket_fieldsordering WHERE isuserfield = 1 AND published = 1 AND search_admin =1 AND fieldfor =" . esc_sql($jsst_fieldfor) ." ORDER BY ordering "; + $jsst_query = "SELECT `rows`,`cols`,required,field,fieldtitle,isuserfield,userfieldtype,userfieldparams,depandant_field FROM " . jssupportticket::$_db->prefix . "js_ticket_fieldsordering WHERE isuserfield = 1 AND published = 1 AND search_admin =1 AND fieldfor =" . intval($jsst_fieldfor) ." ORDER BY ordering "; $jsst_data = jssupportticket::$_db->get_results($jsst_query); return $jsst_data; }
Exploit Outline
The exploit involves leveraging the missing capability checks on the plugin's AJAX handlers. An attacker first identifies a target attachment ID by observing ticket data or guessing numeric IDs. Next, the attacker acquires a necessary AJAX nonce by visiting any page where the [jssupportticket] shortcode is deployed, as the plugin localizes security nonces into the global JavaScript scope (typically within the 'jssupportticket' or 'js_support_ticket_ajax' objects). Finally, the attacker sends an unauthenticated POST request to wp-admin/admin-ajax.php with the action parameter set to 'js_ticket_delete_attachment' (or related internal routing actions like 'jsst_remove_attachment') and the target attachment ID. Because the plugin fails to verify if the requester has administrative privileges or ownership of the ticket associated with the attachment, the server deletes the corresponding file and database record.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.