SupportCandy – Helpdesk & Customer Support Ticket System <= 3.4.6 - Authenticated (Subscriber+) Insecure Direct Object Reference
Description
The SupportCandy – Helpdesk & Customer Support Ticket System plugin for WordPress is vulnerable to Insecure Direct Object Reference in all versions up to, and including, 3.4.6 due to missing validation on a user controlled key. This makes it possible for authenticated attackers, with Subscriber-level access and above, to perform an unauthorized action.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:NTechnical Details
What Changed in the Fix
Changes introduced in v3.4.7
Source Code
WordPress.org SVNThis research plan focuses on an **Authenticated Insecure Direct Object Reference (IDOR)** vulnerability in SupportCandy (<= 3.4.6). The vulnerability allows a user with **Subscriber-level access** (typically a "Customer" in SupportCandy) to perform unauthorized actions, specifically modifying or cl…
Show full research plan
This research plan focuses on an Authenticated Insecure Direct Object Reference (IDOR) vulnerability in SupportCandy (<= 3.4.6). The vulnerability allows a user with Subscriber-level access (typically a "Customer" in SupportCandy) to perform unauthorized actions, specifically modifying or closing tickets belonging to other users, due to a failure in the backend to verify ticket ownership.
1. Vulnerability Summary
- Vulnerability: Insecure Direct Object Reference (IDOR).
- Affected Component: AJAX actions that process ticket-related tasks, specifically
wpsc_it_close_ticket. - Cause: The plugin validates the existence of a WordPress nonce but fails to verify if the
ticket_idprovided in the request belongs to the authenticated user'scustomer_id. - Impact: Integrity modification (
I:L). An attacker can close tickets belonging to any other user if they know theticket_id.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - Action:
wpsc_it_close_ticket - Vulnerable Parameter:
ticket_id - Authentication: Authenticated (Subscriber+)
- Preconditions: The attacker must be logged in as a Subscriber (Customer) and have the ID of a victim's ticket.
3. Code Flow
- Entry Point: The client sends a POST request to
admin-ajax.phpwithaction=wpsc_it_close_ticket. - JS Implementation (
framework/scripts.js):function wpsc_it_close_ticket(ticket_id, nonce) { // ... confirms and checks for draft reply ... var data = { action: "wpsc_it_close_ticket", ticket_id, _ajax_nonce: nonce }; jQuery.post(supportcandy.ajax_url, data, function (response) { ... }); } - Backend Processing: The backend handler for
wpsc_it_close_ticketlikely callscheck_ajax_referer('wpsc_it_close_ticket', '_ajax_nonce'). - Vulnerable Sink: After nonce verification, the code proceeds to update the status of
ticket_idin thepsmsc_ticketstable without checking if the current user is the "customer" associated with that ticket.
4. Nonce Acquisition Strategy
SupportCandy uses localized objects for its nonces. Since the attacker is an authenticated Subscriber, they can obtain a valid nonce for the wpsc_it_close_ticket action by interacting with their own ticket.
- Create a Personal Ticket: The attacker creates a legitimate ticket to gain access to the ticket-viewing interface.
- Access Ticket UI: Navigate to the page containing the
[supportcandy]shortcode. - Extract Nonce:
- The nonce for specific ticket actions is often localized in the
supportcandyobject or embedded in the "Close" button's attributes. - Strategy: Use
browser_evalto extract the general nonce from thesupportcandyobject or locate the "Close" button for the attacker's own ticket and parse itsonclickhandler. - Variable:
window.supportcandy?.nonce(General nonce) or specific action nonces found in the DOM.
- The nonce for specific ticket actions is often localized in the
5. Exploitation Strategy
The exploit will demonstrate closing a victim's ticket from a Subscriber account.
Step-by-Step:
- Preparation: Identify a Victim ticket ID (e.g.,
1). - Session Initiation: Log in as the Attacker (Subscriber).
- Nonce Retrieval:
- Navigate to the SupportCandy portal.
- Create a dummy ticket (e.g., ID
2). - View the dummy ticket.
- Extract the nonce for the
wpsc_it_close_ticketaction.
- Forged Request: Send a POST request via the
http_requesttool targeting the victim's ticket ID.
HTTP Request Payload:
- URL:
http://localhost:8080/wp-admin/admin-ajax.php - Method:
POST - Headers:
Content-Type: application/x-www-form-urlencoded - Body:
action=wpsc_it_close_ticket&ticket_id=[VICTIM_TICKET_ID]&_ajax_nonce=[ATTACKER_NONCE]
6. Test Data Setup
- Victim User: Create a user
victim_user(Subscriber). - Attacker User: Create a user
attacker_user(Subscriber). - Victim Ticket: Log in as
victim_userand create a ticket with subject "Confidential Issue". Note the ID. - Portal Page: Create a WordPress page with the shortcode
[supportcandy]so the UI is accessible.
7. Expected Results
- Response: The server returns a success response (typically empty or a small HTML fragment/JSON).
- Database Change: The status of the victim's ticket in the
psmsc_ticketstable changes to the ID corresponding to "Closed" (often3or4depending on configuration).
8. Verification Steps
- Check Ticket Status via WP-CLI:
# Query the psmsc_tickets table for the victim's ticket status wp db query "SELECT status FROM wp_psmsc_tickets WHERE id = [VICTIM_TICKET_ID]" - Confirm Status: Compare the status ID before and after the attack. A change indicates a successful IDOR.
9. Alternative Approaches
- Action
wpsc_it_get_duplicate_ticket: If closing is not possible, check if a Subscriber can duplicate any ticket ID, which might reveal contents in the duplication form (I:L, potentiallyC:L). - Action
wpsc_get_individual_ticket: Check if this action (whichasset/js/admin.jsshows has no nonce
Summary
The SupportCandy plugin for WordPress is vulnerable to an Insecure Direct Object Reference (IDOR) in versions up to 3.4.6. This allows authenticated attackers with Subscriber-level access (customers) to perform unauthorized actions, such as closing tickets belonging to other users, because the plugin fails to verify ticket ownership before processing AJAX requests.
Vulnerable Code
// framework/scripts.js @ 3.4.6 /** * Get close current ticket modal UI */ function wpsc_it_close_ticket(ticket_id, nonce) { if (wpsc_is_description_text()) { if (!confirm(supportcandy.translations.warning_message)) return; } var flag = confirm(supportcandy.translations.confirm); if (flag) { if (wpsc_is_description_text()) { wpsc_clear_saved_draft_reply(ticket_id); } } else { return; } // Vulnerable action: Sends ticket_id and nonce without sufficient backend ownership validation var data = { action: "wpsc_it_close_ticket", ticket_id, _ajax_nonce: nonce }; jQuery.post(supportcandy.ajax_url, data, function (response) { wpsc_run_ajax_background_process(); wpsc_after_close_ticket(ticket_id); }); }
Security Fix
@@ -3600,6 +3600,59 @@ } /** + * Get edit Agent Collision widget + */ +function wpsc_get_tw_agent_collision() { + + wpsc_show_modal(); + var data = { action: 'wpsc_get_tw_agent_collision' }; + jQuery.post( + supportcandy.ajax_url, + data, + function (response) { + + // Set to modal. + jQuery( '.wpsc-modal-header' ).text( response.title ); + jQuery( '.wpsc-modal-body' ).html( response.body ); + jQuery( '.wpsc-modal-footer' ).html( response.footer ); + // Display modal. + wpsc_show_modal_inner_container(); + } + ); +} + +/** + * Set edit Agent Collision widget + */ +function wpsc_set_tw_agent_collision(el) { + var form = jQuery( '.wpsc-frm-edit-ac' )[0]; + var dataform = new FormData( form ); + + if (dataform.get( 'label' ).trim() == '') { + alert( supportcandy.translations.req_fields_missing ); + return; + } + + jQuery( '.wpsc-modal-footer button' ).attr( 'disabled', true ); + jQuery( el ).text( supportcandy.translations.please_wait ); + + jQuery.ajax( + { + url: supportcandy.ajax_url, + type: 'POST', + data: dataform, + processData: false, + contentType: false + } + ).done( + function (res) { + wpsc_close_modal(); + wpsc_get_ticket_widget(); + } + ); +} + +/** * Ticket widget status */ function wpsc_get_tw_ticket_status() { @@ -1,5 +1,12 @@ == Changelog == += 3.4.7 (May 26, 2026) = +* New: Option to disable email notifications for specific tickets +* New: Improved form validation to highlight incomplete fields on submission +* New: Agent collision widget added +* Fix: Security vulnerability resolved +* Fix: Email notifications not sent to agents assigned via workflows + = 3.4.6 (March 23, 2026) = * New: Added Merge Ticket feature (Pro) in Productivity Suite. * New: Added AI Assistant feature (Pro) in Productivity Suite.
Exploit Outline
1. Identification: The attacker identifies a target ticket ID belonging to another user. 2. Nonce Acquisition: The attacker logs into their own Subscriber account and creates or views their own ticket to extract a valid AJAX nonce for the `wpsc_it_close_ticket` action from the localized `supportcandy` JavaScript object or the ticket UI. 3. Payload Construction: The attacker prepares an unauthenticated AJAX request targeting `/wp-admin/admin-ajax.php`. 4. Execution: The attacker sends a POST request with the following body: `action=wpsc_it_close_ticket&ticket_id=[VICTIM_ID]&_ajax_nonce=[ATTACKER_NONCE]`. 5. Outcome: Because the backend handler (e.g., in `includes/admin/tickets/class-wpsc-individual-ticket.php`) validates the nonce but fails to verify that the requesting user's `customer_id` matches the `customer` field of the ticket, the victim's ticket is closed.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.