PeproDev Ultimate Invoice < 2.2.6 - Unauthenticated Information Exposure
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:NTechnical Details
<2.2.6What Changed in the Fix
Changes introduced in v2.2.6
Source Code
WordPress.org SVN# 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 theinithook). - Vulnerable Parameters:
puiw_action,order_id(orpuiw_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
- Entry Point: The plugin initializes in
pepro-ultimate-invoice.php. - Hook Registration: The
__constructmethod ofPeproUltimateInvoicecallsinit_pluginon theinithook. - Class Initialization:
init_plugin(inferred) instantiatesPeproUltimateInvoice_SettingandPeproUltimateInvoice_Print. - Vulnerable Hook: These classes register handlers on the
inithook (e.g., ininclude/admin/class-setting.phpandinclude/admin/class-print.php). - Processing:
- The
PeproUltimateInvoice_Setting::export_settingsmethod (inferred) checks if$_GET['puiw_action']equalspuiw_export_settings. - It retrieves all plugin options (e.g., via
get_option('pepro_ultimate_invoice_settings')) and outputs them as JSON usingwp_send_json()or a directecho. - The Sink: The logic fails to verify
current_user_can('manage_options')or check a nonce before outputting the data. - Similarly,
PeproUltimateInvoice_Print::handle_printchecks forpuiw_action=printand anorder_id, then renders the invoice without verifying if the requester is the owner of the order or an administrator.
- The
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.
- Shortcode: The plugin uses shortcodes for "Quick Shop" and profile integrations. Check for
[pepro_ultimate_invoice_...]. - Localization Key: Look for
puiw_objorpuiw_varsin the page source. - 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")
- Create a page:
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.
- Install WooCommerce: (Assumed pre-installed).
- Create a Customer:
wp user create victim victim@example.com --role=customer --user_pass=password - 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"}'
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
@@ -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.