CVE-2025-12075

Order Splitter for WooCommerce <= 5.3.5 - Missing Authorization to Authenticated (Subscriber+) Order Information Exposure

mediumMissing Authorization
4.3
CVSS Score
4.3
CVSS Score
medium
Severity
5.3.6
Patched in
1d
Time to patch

Description

The Order Splitter for WooCommerce plugin for WordPress is vulnerable to unauthorized access of data due to a missing capability check on the 'wos_troubleshooting' AJAX endpoint in all versions up to, and including, 5.3.5. This makes it possible for authenticated attackers, with Subscriber-level access and above, to view information pertaining to other user's orders.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=5.3.5
PublishedFebruary 17, 2026
Last updatedFebruary 18, 2026
Affected pluginwoo-order-splitter

What Changed in the Fix

Changes introduced in v5.3.6

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan: CVE-2025-12075 ## 1. Vulnerability Summary The **Order Splitter for WooCommerce** plugin (versions <= 5.3.5) contains a missing authorization vulnerability within its troubleshooting functionality. Specifically, the AJAX action `wos_troubleshooting` registered in `inc/…

Show full research plan

Exploitation Research Plan: CVE-2025-12075

1. Vulnerability Summary

The Order Splitter for WooCommerce plugin (versions <= 5.3.5) contains a missing authorization vulnerability within its troubleshooting functionality. Specifically, the AJAX action wos_troubleshooting registered in inc/functions-troubleshooting.php fails to perform any capability checks (e.g., current_user_can( 'manage_options' )) or verify that the requested order belongs to the authenticated user. This allows any logged-in user, including those with Subscriber-level permissions, to access sensitive order data, metadata, and plugin configuration by simply providing a target order_id.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • Action: wos_troubleshooting
  • HTTP Method: POST
  • Vulnerable Parameter: order_id
  • Authentication: Required (Subscriber-level or higher).
  • Preconditions: At least one WooCommerce order must exist in the system for information to be exposed. The plugin must be active.

3. Code Flow

  1. Entry Point: The plugin registers the AJAX handler in inc/functions-troubleshooting.php using:
    add_action( 'wp_ajax_wos_troubleshooting', 'wos_troubleshooting' );
  2. Input Processing: In the wos_troubleshooting() function, the code executes extract($_POST);. This insecurely populates the local scope with variables from the POST request, including $order_id.
  3. Information Retrieval:
    • The code calls $order = wc_get_order($order_id); without checking if the current user has the edit_shop_order capability or is the owner of the order.
    • It retrieves order metadata via wc_os_get_order_meta($order_id).
    • It iterates through order items using $order->get_items(), retrieving product_id, vendor_id, vendor_name, and item_meta.
    • It performs complex logic via wc_os_order_splitter->split_order_logic($order_id, false, true).
  4. Data Sink: The accumulated information is stored in the $debug_output array, which is then imploded into $ret['html'].
  5. Response: The function terminates with wp_send_json($ret);, returning the complete HTML-formatted debug information (including PII from Order Meta) to the requester.

4. Nonce Acquisition Strategy

Based on the provided source code for wos_troubleshooting() in inc/functions-troubleshooting.php, there is no nonce verification (i.e., no check_ajax_referer or wp_verify_nonce) present in the function.

If a generic nonce were required by the WordPress AJAX framework or added in a global plugin initialization, a Subscriber could obtain it by:

  1. Creating a post/page with a plugin shortcode (if one exists).
  2. Navigating to the page and using browser_eval to find localized variables.

However, for version 5.3.5, the wos_troubleshooting function appears to be completely unprotected.

5. Exploitation Strategy

The goal is to leak the metadata and items of an order that does not belong to the attacker.

Step 1: Authentication

Authenticate as a Subscriber user using the standard WordPress login flow or via wp-cli.

Step 2: Information Leakage Request

Send a POST request to the AJAX endpoint targeting an existing order_id.

Request Details:

  • URL: http://[TARGET]/wp-admin/admin-ajax.php
  • Method: POST
  • Headers:
    • Content-Type: application/x-www-form-urlencoded
    • Cookie: [Subscriber Cookies]
  • Body:
    action=wos_troubleshooting&order_id=[TARGET_ORDER_ID]

Step 3: Parsing the Response

The response will be a JSON object. The html key will contain a large string of HTML.

  • Look for "Order Meta": This section contains serialized order metadata, often including billing names, emails, and phone numbers.
  • Look for "Expected Output": Shows the results of the splitting logic.
  • Look for "Item Meta": Details specific product choices/notes.

6. Test Data Setup

  1. Victim Setup:
    • Logged in as Administrator, create a test Customer user (victim_user).
    • Create a WooCommerce order with sensitive details (e.g., Billing Address: "123 Secret St", Phone: "555-0199").
    • Note the ID of this order (e.g., 123).
  2. Attacker Setup:
    • Create a Subscriber user (attacker_user).
    • Log in as the Subscriber.

7. Expected Results

A successful exploit will return a JSON response (HTTP 200) containing:

{
  "color": {"r": "...", "g": "...", "b": "..."},
  "color_hex": "#...",
  "order_id": "123",
  "split_method": "...",
  "html": "... <h4>Order Meta:</h4><pre>Array\n(\n    [_billing_first_name] => Victim\n    [_billing_email] => victim@example.com\n    [_billing_address_1] => 123 Secret St\n ..."
}

The presence of billing information or item meta belonging to the victim confirms the vulnerability.

8. Verification Steps

After performing the HTTP request, verify the leaked data against the database using wp-cli:

# Verify the leaked billing email matches the order ID targeted
wp post meta list [TARGET_ORDER_ID] --keys=_billing_email --format=json

9. Alternative Approaches

If order_id is not known, an attacker can brute-force IDs starting from 1 (or the current timestamp/sequence) as WooCommerce orders typically use sequential integer IDs in the wp_posts table. Since the response Order Meta section is verbose, even a blind probe is highly effective for data harvesting.

Research Findings
Static analysis — not yet PoC-verified

Summary

The Order Splitter for WooCommerce plugin for WordPress is vulnerable to unauthorized access of sensitive order data due to a missing capability check and nonce verification on the 'wos_troubleshooting' AJAX endpoint. This allows authenticated attackers with Subscriber-level access or higher to view other users' order details, including billing names, emails, and shipping addresses, by supplying a target order ID.

Vulnerable Code

// inc/functions-troubleshooting.php lines 3-12

	function wos_troubleshooting() {
		extract($_POST);
		$ret = array();
	
		$order = wc_get_order($order_id);
		if (!$order) {
			wp_send_json_error('Order not found');
		}

---

// inc/functions-troubleshooting.php line 126

	add_action( 'wp_ajax_wos_troubleshooting', 'wos_troubleshooting' );

Security Fix

--- /home/deploy/wp-safety.org/data/plugin-versions/woo-order-splitter/5.3.5/inc/functions-troubleshooting.php	2025-08-04 16:44:22.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/woo-order-splitter/5.3.6/inc/functions-troubleshooting.php	2025-10-31 19:59:36.000000000 +0000
@@ -1,126 +1,140 @@
 <?php
 
 	function wos_troubleshooting() {
-		extract($_POST);
-		$ret = array();
-	
-		$order = wc_get_order($order_id);
-		if (!$order) {
-			wp_send_json_error('Order not found');
-		}
-	
-		global $wc_os_settings, $wos_actions_arr;
-	
-		$hex = '#' . wc_os_random_color();
-		list($r, $g, $b) = sscanf($hex, "#%02x%02x%02x");
-		$ret['color'] = ['r' => $r, 'g' => $g, 'b' => $b];
-		$ret['color_hex'] = $hex;
-		$ret['order_id'] = $order_id;
-		$ret['split_method'] = $wc_os_settings['wc_os_ie'];
-	
-		$meta = wc_os_get_order_meta($order_id);
-		$wc_os_meta_keys = get_option('wc_os_meta_keys', array());
-		$items = $order->get_items();
-		$shipping_items = $order->get_items('shipping');
 		
-		$split_method_key = $ret['split_method'];
-		$split_method_label = $wos_actions_arr[ $split_method_key ]['action'] ?? ucfirst(str_replace('_', ' ', $split_method_key));
+		$ret = array('html'=>'');
 		
-
-	
-		$debug_output = [];
-				
-		$debug_output[] = '<div class="wos-troubleshoot-method"><h3>Split Method: ' . esc_html($split_method_key) . ' — ' . esc_html($split_method_label) . '</h3></div>';
-		$debug_output[] = '<div class="wos-meta-keys"><h4>Configured Meta Keys for Grouping:</h4><pre>' . print_r($wc_os_meta_keys, true) . '</pre></div>';
-
-		$wc_os_order_splitter = new wc_os_order_splitter;
-				
-		$debug_output[] = '<div class="wos-split-order-logic"><h4>Expected Output:</h4><pre>'.print_r($wc_os_order_splitter->split_order_logic($order_id, false, true), true).'</pre></div>';
-			
-		// Generic items listing
-		foreach ($items as $item_id => $item) {
-			$product_id = $item->get_product_id();
-			$product = wc_get_product($product_id);
-			$vendor_id = get_post_field('post_author', $product_id);
-			$vendor_name = get_the_author_meta('display_name', $vendor_id);
-				
-			$debug_output[] = '<div style="border:1px solid #ccc; padding:10px; margin-bottom:10px;">';
-			
-			$debug_output[] = '<div style="margin-bottom:5px;"><strong>Item ID:</strong> ' . $item_id . '</div>';
+		if ( ! isset($_POST['wc_os_nonce']) || ! wp_verify_nonce($_POST['wc_os_nonce'], 'wc_os_nonce_action') ) {
 			
-			$debug_output[] = '<div style="margin-bottom:5px;"><strong>Product ID:</strong> ' . $product_id . ' - ' . esc_html($product->get_name()) . '</div>';
+			wp_send_json_error(__('Sorry, your nonce did not verify.', 'woo-order-splitter'));
 			
-			$debug_output[] = '<div style="margin-bottom:5px;"><strong>Vendor ID:</strong> ' . $vendor_id . ' (' . esc_html($vendor_name) . ')</div>';
+		} elseif ( ! current_user_can('manage_woocommerce') && ! current_user_can('edit_shop_orders') ) {
 			
-			$debug_output[] = '<div style="margin-bottom:5px;"><strong>Item Meta:</strong><pre style="margin:0;">' . print_r($item->get_meta_data(), true) . '</pre></div>';
+			wp_send_json_error(__('You do not have permission to access this resource.', 'woo-order-splitter'));
 			
+		} else {
+						
+			extract($_POST);
+			$order_id = isset($_POST['order_id']) ? intval($_POST['order_id']) : 0;
+
+		
+			$order = wc_get_order($order_id);
+			if (!$order) {
+				wp_send_json_error('Order not found');
+			}
+		
+			global $wc_os_settings, $wos_actions_arr;
+		
+			$hex = '#' . wc_os_random_color();
+			list($r, $g, $b) = sscanf($hex, "#%02x%02x%02x");
+			$ret['color'] = ['r' => $r, 'g' => $g, 'b' => $b];
+			$ret['color_hex'] = $hex;
+			$ret['order_id'] = $order_id;
+			$ret['split_method'] = $wc_os_settings['wc_os_ie'];
+		
+			$meta = wc_os_get_order_meta($order_id);
+			$wc_os_meta_keys = get_option('wc_os_meta_keys', array());
+			$items = $order->get_items();
+			$shipping_items = $order->get_items('shipping');
 			
-			$debug_output[] = '</div>';
+			$split_method_key = $ret['split_method'];
+			$split_method_label = $wos_actions_arr[ $split_method_key ]['action'] ?? ucfirst(str_replace('_', ' ', $split_method_key));
 			
-
-
 	
-			// Add conditional rendering for known split methods
-			switch ($ret['split_method']) {
-				case 'group_by_order_item_meta':
-				case 'group_by_gf_meta':
-					$meta_keys = get_option('wc_os_gf_meta_keys', []);
-					$debug_output[] = '<span style="color:red">[!] Missing Group-by Meta Keys?</span>';
-					$debug_output[] = '<pre>' . print_r($meta_keys, true) . '</pre>';
-					break;
-				case 'group_by_attributes_value':
-					$debug_output[] = '<span style="color:red">[!] Attributes Value Grouping Check Needed</span>';
-					break;
-				case 'group_by_woo_vendors':
-					$vendor_term = wp_get_post_terms($product_id, 'wcpv_product_vendors');
-					$group_letter = '';
-					if (!empty($vendor_term)) {
-						$term_id = $vendor_term[0]->term_id;
-						foreach ((array) $wc_os_settings['wc_os_woo_vendors'] as $letter => $vendor_ids) {
-							if (in_array($term_id, (array) $vendor_ids)) {
-								$group_letter = strtoupper($letter);
-								break;
+			$debug_output = [];
+					
+			$debug_output[] = '<div class="wos-troubleshoot-method"><h3>Split Method: ' . esc_html($split_method_key) . ' — ' . esc_html($split_method_label) . '</h3></div>';
+			$debug_output[] = '<div class="wos-meta-keys"><h4>Configured Meta Keys for Grouping:</h4><pre>' . print_r($wc_os_meta_keys, true) . '</pre></div>';
+	
+			$wc_os_order_splitter = new wc_os_order_splitter;
+					
+			$debug_output[] = '<div class="wos-split-order-logic"><h4>Expected Output:</h4><pre>'.print_r($wc_os_order_splitter->split_order_logic($order_id, false, true), true).'</pre></div>';
+				
+			// Generic items listing
+			foreach ($items as $item_id => $item) {
+				$product_id = $item->get_product_id();
+				$product = wc_get_product($product_id);
+				$vendor_id = get_post_field('post_author', $product_id);
+				$vendor_name = get_the_author_meta('display_name', $vendor_id);
+					
+				$debug_output[] = '<div style="border:1px solid #ccc; padding:10px; margin-bottom:10px;">';
+				
+				$debug_output[] = '<div style="margin-bottom:5px;"><strong>Item ID:</strong> ' . $item_id . '</div>';
+				
+				$debug_output[] = '<div style="margin-bottom:5px;"><strong>Product ID:</strong> ' . $product_id . ' - ' . esc_html($product->get_name()) . '</div>';
+				
+				$debug_output[] = '<div style="margin-bottom:5px;"><strong>Vendor ID:</strong> ' . $vendor_id . ' (' . esc_html($vendor_name) . ')</div>';
+				
+				$debug_output[] = '<div style="margin-bottom:5px;"><strong>Item Meta:</strong><pre style="margin:0;">' . print_r($item->get_meta_data(), true) . '</pre></div>';
+				
+				
+				$debug_output[] = '</div>';
+				
+	
+	
+		
+				// Add conditional rendering for known split methods
+				switch ($ret['split_method']) {
+					case 'group_by_order_item_meta':
+					case 'group_by_gf_meta':
+						$meta_keys = get_option('wc_os_gf_meta_keys', []);
+						$debug_output[] = '<span style="color:red">[!] Missing Group-by Meta Keys?</span>';
+						$debug_output[] = '<pre>' . print_r($meta_keys, true) . '</pre>';
+						break;
+					case 'group_by_attributes_value':
+						$debug_output[] = '<span style="color:red">[!] Attributes Value Grouping Check Needed</span>';
+						break;
+					case 'group_by_woo_vendors':
+						$vendor_term = wp_get_post_terms($product_id, 'wcpv_product_vendors');
+						$group_letter = '';
+						if (!empty($vendor_term)) {
+							$term_id = $vendor_term[0]->term_id;
+							foreach ((array) $wc_os_settings['wc_os_woo_vendors'] as $letter => $vendor_ids) {
+								if (in_array($term_id, (array) $vendor_ids)) {
+									$group_letter = strtoupper($letter);
+									break;
+								}
 							}
 						}
-					}
-					$debug_output[] = '<div class="wos-vendor-group"><strong>Vendor Group:</strong> <span class="vendor-group-letter">' . esc_html($group_letter) . '</span></div>';
-					$debug_output[] = '<div class="wos-vendor-terms"><strong>Vendor Terms:</strong><pre>' . print_r($vendor_term, true) . '</pre></div>';
-					break;
-
-				case 'group_by_vendors':
-					$vendor_term = wp_get_post_terms($product_id, 'wcpv_product_vendors');
-					$debug_output[] = '<strong>Vendor Terms:</strong><pre>' . print_r($vendor_term, true) . '</pre>';
-					break;
-				case 'quantity_split':
-					$debug_output[] = '<span style="color:red">[!] Quantity Ratio or Threshold Info Missing?</span>';
-					break;
-				case 'group_by_partial_payment':
-					$debug_output[] = '<span style="color:red">[!] Booking/Partial Payment Meta Missing?</span>';
-					break;
-				case 'subscription_split':
-					$debug_output[] = '<span style="color:red">[!] Delivery Date Check?</span>';
-					break;
-				case 'group_by_acf_group_fields':
-					$acf_value = get_post_meta($product_id, 'acf_group_field', true);
-					$debug_output[] = '<strong>ACF Value:</strong><pre>' . print_r($acf_value, true) . '</pre>';
-					break;
-				default:
-					$debug_output[] = '<span style="color:red">[!] Custom check not defined for method: ' . esc_html($ret['split_method']) . '</span>';
-					break;
+						$debug_output[] = '<div class="wos-vendor-group"><strong>Vendor Group:</strong> <span class="vendor-group-letter">' . esc_html($group_letter) . '</span></div>';
+						$debug_output[] = '<div class="wos-vendor-terms"><strong>Vendor Terms:</strong><pre>' . print_r($vendor_term, true) . '</pre></div>';
+						break;
+	
+					case 'group_by_vendors':
+						$vendor_term = wp_get_post_terms($product_id, 'wcpv_product_vendors');
+						$debug_output[] = '<strong>Vendor Terms:</strong><pre>' . print_r($vendor_term, true) . '</pre>';
+						break;
+					case 'quantity_split':
+						$debug_output[] = '<span style="color:red">[!] Quantity Ratio or Threshold Info Missing?</span>';
+						break;
+					case 'group_by_partial_payment':
+						$debug_output[] = '<span style="color:red">[!] Booking/Partial Payment Meta Missing?</span>';
+						break;
+					case 'subscription_split':
+						$debug_output[] = '<span style="color:red">[!] Delivery Date Check?</span>';
+						break;
+					case 'group_by_acf_group_fields':
+						$acf_value = get_post_meta($product_id, 'acf_group_field', true);
+						$debug_output[] = '<strong>ACF Value:</strong><pre>' . print_r($acf_value, true) . '</pre>';
+						break;
+					default:
+						$debug_output[] = '<span style="color:red">[!] Custom check not defined for method: ' . esc_html($ret['split_method']) . '</span>';
+						break;
+				}
 			}
-		}
-	
-		$debug_output[] = '<h4>Order Meta:</h4>';
-		$debug_output[] = '<pre>' . print_r($meta, true) . '</pre>';
-	
-		$debug_output[] = '<h4>Shipping Items:</h4>';
-		$debug_output[] = '<pre>' . print_r($shipping_items, true) . '</pre>';
 		
-		$debug_output[] = '<h4>$wc_os_settings:</h4>';
-		$debug_output[] = '<pre>' . print_r($wc_os_settings, true) . '</pre>';
-	
-		$ret['html'] = implode("\n", $debug_output);
-	
+			$debug_output[] = '<h4>Order Meta:</h4>';
+			$debug_output[] = '<pre>' . print_r($meta, true) . '</pre>';
+		
+			$debug_output[] = '<h4>Shipping Items:</h4>';
+			$debug_output[] = '<pre>' . print_r($shipping_items, true) . '</pre>';
+			
+			$debug_output[] = '<h4>$wc_os_settings:</h4>';
+			$debug_output[] = '<pre>' . print_r($wc_os_settings, true) . '</pre>';
+		
+			$ret['html'] = implode("\n", $debug_output);
+		}
 		wp_send_json($ret);
 	}

Exploit Outline

To exploit this vulnerability, an attacker first authenticates to the WordPress site as a Subscriber or higher. They then send a POST request to the `/wp-admin/admin-ajax.php` endpoint with the `action` parameter set to `wos_troubleshooting` and the `order_id` parameter set to the ID of the target order. Since the plugin fails to verify user capabilities or validate that the requester owns the order, it returns a JSON object where the `html` key contains a comprehensive dump of the order's metadata (including PII like billing names and emails), product information, and vendor details.

Check if your site is affected.

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