CVE-2026-48887

JS Help Desk – AI-Powered Support & Ticketing System <= 3.0.9 - Missing Authorization

mediumMissing Authorization
5.3
CVSS Score
5.3
CVSS Score
medium
Severity
3.1.0
Patched in
7d
Time to patch

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:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
Unchanged
None
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=3.0.9
PublishedJune 2, 2026
Last updatedJune 8, 2026
Affected pluginjs-support-ticket

What Changed in the Fix

Changes introduced in v3.1.0

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# 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 from includes/includer.php module 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

  1. Entry Point: An AJAX request is sent to admin-ajax.php with action=js_ticket_delete_attachment.
  2. Routing: The plugin's main controller (referenced in includes/includer.php as modules/js-support-ticket-controller.php) catches the action.
  3. Model Loading: The controller uses JSSTincluder::getJSModel('attachment') to load the attachment model.
  4. Processing: The model's deletion function is called.
  5. Missing Check: The function proceeds to execute a SQL query (targeting the js_ticket_attachments table defined in includes/activation.php) and unlinks the file (using paths derived in includes/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.

  1. Identify Script Handle: According to includes/classes/customfields.php, the plugin enqueues scripts with the handle js-support-ticket-main-js.
  2. 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]'
  3. Navigate and Extract:
    • Navigate to the newly created page.
    • The plugin localizes data into a JavaScript object, often named js_support_ticket_ajax or jssupportticket.
    • Based on customfields.php which uses wp_create_nonce("is-field-required-..."), other nonces are likely in the same global scope.
  4. 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:

  1. Identify Attachment ID: Use wp_cli to find an existing ID in the wp_js_ticket_attachments table.
  2. 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]
      
  3. Analyze Response: A successful deletion usually returns a JSON success status or a "1".

6. Test Data Setup

  1. Activate Plugin: Ensure js-support-ticket is active.
  2. Create Ticket Page:
    • wp post create --post_type=page --post_title="Support" --post_status=publish --post_content='[jssupportticket]'
  3. 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_attachments format based on jssupportticket::$_db->prefix . "js_ticket_attachments".

7. Expected Results

  • The HTTP response from admin-ajax.php should indicate a successful operation.
  • The file record with ID 1337 should be removed from the wp_js_ticket_attachments table.

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:

  1. Action: jsst_remove_attachment
  2. Action: js_ticket_remove_attachment
  3. Custom Fields Data: getDataForVisibleField (from customfields.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).
  4. Direct Controller Routing: If the main controller handles requests via a request parameter:
    • action=jsticket_ajax&request=deleteAttachment&id=1337
Research Findings
Static analysis — not yet PoC-verified

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

--- /home/deploy/wp-safety.org/data/plugin-versions/js-support-ticket/3.0.9/includes/activation.php	2026-05-15 04:15:24.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/js-support-ticket/3.1.0/includes/activation.php	2026-05-20 04:03:44.000000000 +0000
@@ -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),
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/js-support-ticket/3.0.9/includes/classes/customfields.php /home/deploy/wp-safety.org/data/plugin-versions/js-support-ticket/3.1.0/includes/classes/customfields.php
--- /home/deploy/wp-safety.org/data/plugin-versions/js-support-ticket/3.0.9/includes/classes/customfields.php	2026-05-15 04:15:24.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/js-support-ticket/3.1.0/includes/classes/customfields.php	2026-05-20 04:03:44.000000000 +0000
@@ -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.