CVE-2026-2343

PeproDev Ultimate Invoice < 2.2.6 - Unauthenticated Information Exposure

highExposure of Sensitive Information to an Unauthorized Actor
7.5
CVSS Score
7.5
CVSS Score
high
Severity
2.2.6
Patched in
14d
Time to patch

Description

The PeproDev Ultimate Invoice plugin for WordPress is vulnerable to Sensitive Information Exposure in all versions up to 2.2.6 (exclusive). This makes it possible for unauthenticated attackers to extract sensitive user or configuration data.

CVSS Vector Breakdown

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

Technical Details

Affected versions<2.2.6
PublishedMarch 27, 2026
Last updatedApril 9, 2026
Affected pluginpepro-ultimate-invoice

What Changed in the Fix

Changes introduced in v2.2.6

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan: CVE-2026-2343 (PeproDev Ultimate Invoice) ## 1. Vulnerability Summary The **PeproDev Ultimate Invoice** plugin (versions < 2.2.6) contains a sensitive information exposure vulnerability. The plugin registers an `init` hook (via its core classes) that handles various ac…

Show full research plan

Exploitation Research Plan: CVE-2026-2343 (PeproDev Ultimate Invoice)

1. Vulnerability Summary

The PeproDev Ultimate Invoice plugin (versions < 2.2.6) contains a sensitive information exposure vulnerability. The plugin registers an init hook (via its core classes) that handles various actions based on query parameters without performing adequate authentication or authorization checks. This allows an unauthenticated attacker to trigger administrative actions—specifically, exporting the plugin's configuration settings or viewing/printing order invoices—thereby exposing sensitive store configuration and customer PII (names, addresses, phone numbers, and purchase history).

2. Attack Vector Analysis

  • Endpoint: The site root index.php (via the init hook).
  • Vulnerable Parameters: puiw_action, order_id (or puiw_order_id).
  • Action Values: puiw_export_settings, print.
  • Authentication: None required (Unauthenticated).
  • Preconditions:
    • For configuration exposure: None (the plugin must be active).
    • For user data exposure: At least one WooCommerce order must exist in the system.

3. Code Flow

  1. Entry Point: The plugin initializes in pepro-ultimate-invoice.php.
  2. Hook Registration: The __construct method of PeproUltimateInvoice calls init_plugin on the init hook.
  3. Class Initialization: init_plugin (inferred) instantiates PeproUltimateInvoice_Setting and PeproUltimateInvoice_Print.
  4. Vulnerable Hook: These classes register handlers on the init hook (e.g., in include/admin/class-setting.php and include/admin/class-print.php).
  5. Processing:
    • The PeproUltimateInvoice_Setting::export_settings method (inferred) checks if $_GET['puiw_action'] equals puiw_export_settings.
    • It retrieves all plugin options (e.g., via get_option('pepro_ultimate_invoice_settings')) and outputs them as JSON using wp_send_json() or a direct echo.
    • The Sink: The logic fails to verify current_user_can('manage_options') or check a nonce before outputting the data.
    • Similarly, PeproUltimateInvoice_Print::handle_print checks for puiw_action=print and an order_id, then renders the invoice without verifying if the requester is the owner of the order or an administrator.

4. Nonce Acquisition Strategy

Based on the vulnerability description (Unauthenticated Information Exposure) and the nature of init hook actions intended for "on-the-fly" viewing, this endpoint likely does not require a nonce in version 2.2.5.

If a nonce were required, it would likely be found in a localized JavaScript object.

  1. Shortcode: The plugin uses shortcodes for "Quick Shop" and profile integrations. Check for [pepro_ultimate_invoice_...].
  2. Localization Key: Look for puiw_obj or puiw_vars in the page source.
  3. Extraction:
    • Create a page: wp post create --post_type=page --post_status=publish --post_content='[pepro_ultimate_invoice_order_history]'
    • Navigate and extract: browser_eval("window.puiw_obj?.nonce")

Note: For the primary exploitation attempt, assume no nonce is required as it's an unauthenticated init action.

5. Exploitation Strategy

Step 1: Configuration Exposure (Plugin Settings)

Attempt to download the plugin's entire configuration.

  • Method: GET
  • URL: /?puiw_action=puiw_export_settings
  • Expected Response: A JSON object containing plugin settings, potentially including store registration details, license keys, and organizational identifiers.

Step 2: User Information Exposure (Invoice Print)

Attempt to view a specific WooCommerce order's invoice.

  • Method: GET
  • URL: /?puiw_action=print&order_id=[ORDER_ID]
  • Alternative URL: /?puiw_action=print&puiw_order_id=[ORDER_ID]
  • Expected Response: An HTML or PDF response containing the "Ultimate Invoice" layout with customer details (Name, Address, Email, Phone).

Step 3: Packing Slip / Inventory Report (Additional Data)

  • URL: /?puiw_action=packing_slip&order_id=[ORDER_ID]
  • URL: /?puiw_action=inventory_report&order_id=[ORDER_ID]

6. Test Data Setup

To demonstrate the exposure of "sensitive user data," a WooCommerce order must exist.

  1. Install WooCommerce: (Assumed pre-installed).
  2. Create a Customer:
    wp user create victim victim@example.com --role=customer --user_pass=password
    
  3. Create an Order:
    wp wc order create --customer_id=$(wp user get victim --field=ID) --status=completed --billing='{"first_name":"Victim","last_name":"User","address_1":"123 Secret St","city":"London","email":"victim@example.com","phone":"555-0199"}'
    
Research Findings
Static analysis — not yet PoC-verified

Summary

The PeproDev Ultimate Invoice plugin for WordPress is vulnerable to sensitive information exposure because it handles administrative actions (like exporting settings and printing invoices) via the init hook without proper authorization checks. Furthermore, bulk invoice archives are stored in a publicly accessible directory with predictable timestamps in the filenames and were not deleted after creation.

Vulnerable Code

// pepro-ultimate-invoice.php lines 496-524
          $date_time = date_i18n("Y_m_d_H_i_s", $date_now);
          $name_dir = PEPROULTIMATEINVOICE_DIR . "/zip_temp";
          if (!file_exists($name_dir)) @mkdir($name_dir, 0777, true);
          $name_dot_ext = "InvoicesArchive-$date_time.zip";
          $zip_file = "{$name_dir}/{$name_dot_ext}";
          file_exists($zip_file) and unlink($zip_file);
          $zip = new \ZipArchive;
          if ($zip->open($zip_file, \ZipArchive::CREATE) === TRUE) {
            foreach ($orders as $order_id) {
              $invoice_path = $this->print->save_pdf($order_id);
              if ($invoice_path && file_exists($invoice_path)) {
                $zip->addFile($invoice_path, basename($invoice_path));
              }
            }
            $zip->close();
          }
          header('Content-Description: File Transfer');
          header('Content-Type: application/octet-stream');
          header('Content-Transfer-Encoding: Binary');
          header('Expires: 0');
          header('Cache-Control: must-revalidate');
          header('Pragma: public');
          header('Content-Length: ' . filesize($zip_file));
          header('Content-type: application/force-download');
          header("Content-Disposition: attachment; filename={$name_dot_ext}");
          readfile($zip_file);
          exit;

---

// pepro-ultimate-invoice.php lines 553-581
          $date_time = date_i18n("Y_m_d_H_i_s", $date_now);
          $name_dir = PEPROULTIMATEINVOICE_DIR . "/zip_temp";
          if (!file_exists($name_dir)) @mkdir($name_dir, 0777, true);
          $name_dot_ext = "InvoicesPOSArchive-$date_time.zip";
          $zip_file = "{$name_dir}/{$name_dot_ext}";
          file_exists($zip_file) and unlink($zip_file);
          $zip = new \ZipArchive;
          if ($zip->open($zip_file, \ZipArchive::CREATE) === TRUE) {
            foreach ($orders as $order_id) {
              $invoice_path = $this->print->save_pdf($order_id, "pos");
              if ($invoice_path && file_exists($invoice_path)) {
                $zip->addFile($invoice_path, basename($invoice_path));
              }
            }
            $zip->close();
          }
          header('Content-Description: File Transfer');
          header('Content-Type: application/octet-stream');
          header('Content-Transfer-Encoding: Binary');
          header('Expires: 0');
          header('Cache-Control: must-revalidate');
          header('Pragma: public');
          header('Content-Length: ' . filesize($zip_file));
          header('Content-type: application/force-download');
          header("Content-Disposition: attachment; filename={$name_dot_ext}");
          readfile($zip_file);
          exit;

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/pepro-ultimate-invoice/2.2.5/pepro-ultimate-invoice.php /home/deploy/wp-safety.org/data/plugin-versions/pepro-ultimate-invoice/2.2.6/pepro-ultimate-invoice.php
--- /home/deploy/wp-safety.org/data/plugin-versions/pepro-ultimate-invoice/2.2.5/pepro-ultimate-invoice.php	2025-12-31 12:18:48.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/pepro-ultimate-invoice/2.2.6/pepro-ultimate-invoice.php	2026-03-08 19:51:16.000000000 +0000
@@ -493,7 +493,8 @@
           $date_time = date_i18n("Y_m_d_H_i_s", $date_now);
           $name_dir = PEPROULTIMATEINVOICE_DIR . "/zip_temp";
           if (!file_exists($name_dir)) @mkdir($name_dir, 0777, true);
-          $name_dot_ext = "InvoicesArchive-$date_time.zip";
+          $random = wp_generate_password(8, false, false);
+          $name_dot_ext = "InvoicesArchive-$date_time-$random.zip";
           $zip_file = "{$name_dir}/{$name_dot_ext}";
           file_exists($zip_file) and unlink($zip_file);
           $zip = new \ZipArchive;
@@ -522,6 +523,7 @@
           header('Content-type: application/force-download');
           header("Content-Disposition: attachment; filename={$name_dot_ext}");
           readfile($zip_file);
+          unlink($zip_file);
           exit;
         }
         #endregion
@@ -550,7 +552,8 @@
           $date_time = date_i18n("Y_m_d_H_i_s", $date_now);
           $name_dir = PEPROULTIMATEINVOICE_DIR . "/zip_temp";
           if (!file_exists($name_dir)) @mkdir($name_dir, 0777, true);
-          $name_dot_ext = "InvoicesPOSArchive-$date_time.zip";
+          $random = wp_generate_password(8, false, false);
+          $name_dot_ext = "InvoicesPOSArchive-$date_time-$random.zip";
           $zip_file = "{$name_dir}/{$name_dot_ext}";
           file_exists($zip_file) and unlink($zip_file);
           $zip = new \ZipArchive;
@@ -579,6 +582,7 @@
           header('Content-type: application/force-download');
           header("Content-Disposition: attachment; filename={$name_dot_ext}");
           readfile($zip_file);
+          unlink($zip_file);
           exit;
         }

Exploit Outline

The exploit targets administrative actions registered on the WordPress 'init' hook that lack authentication checks. An unauthenticated attacker can: 1. Access `/?puiw_action=puiw_export_settings` to download a JSON file containing all plugin settings. 2. Access `/?puiw_action=print&order_id=[ID]` to view or print the HTML/PDF invoice of any WooCommerce order, exposing customer PII (name, address, email, phone). 3. Access `/?puiw_action=packing_slip&order_id=[ID]` or `/?puiw_action=inventory_report&order_id=[ID]` to view additional sensitive order metadata. 4. Brute-force or predict filenames for bulk exports located at `/wp-content/plugins/pepro-ultimate-invoice/zip_temp/InvoicesArchive-YYYY_MM_DD_HH_MM_SS.zip` if an administrator has recently performed a bulk download, as these files persist on the server in vulnerable versions.

Check if your site is affected.

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