Stock Locations for WooCommerce <= 3.1.8 - Missing Authorization
Description
The Stock Locations for WooCommerce plugin for WordPress is vulnerable to unauthorized access due to a missing capability check on a function in versions up to, and including, 3.1.8. This makes it possible for authenticated attackers, with subscriber-level access and above, to perform an unauthorized action.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:NTechnical Details
<=3.1.8What Changed in the Fix
Changes introduced in v3.1.9
Source Code
WordPress.org SVN# Exploitation Research Plan - CVE-2026-57419 ## 1. Vulnerability Summary The **Stock Locations for WooCommerce** plugin (versions <= 3.1.8) is vulnerable to **Missing Authorization** in several AJAX handlers. Specifically, functions like `slw_location_status`, `slw_location_assignment`, and `slw_m…
Show full research plan
Exploitation Research Plan - CVE-2026-57419
1. Vulnerability Summary
The Stock Locations for WooCommerce plugin (versions <= 3.1.8) is vulnerable to Missing Authorization in several AJAX handlers. Specifically, functions like slw_location_status, slw_location_assignment, and slw_map_status (located in inc/functions.php) verify a WordPress nonce but fail to perform a capability check (e.g., current_user_can('manage_options')).
Since these handlers are registered via the wp_ajax_ hook, any authenticated user with Subscriber-level access or above can trigger them if they obtain a valid nonce. This allows an attacker to modify term metadata for "Stock Locations," potentially disrupting inventory management or location-based availability.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - AJAX Actions:
slw_location_status,slw_location_assignment,slw_map_status. - Authentication: Required (Subscriber level or higher).
- Vulnerable Parameters:
location_id: The ID of the location term (taxonomy:location).status/assignment: 'yes' or 'no' string to toggle settings.slw_nonce_field: The CSRF protection token (nonce).
- Preconditions:
- The plugin must be active.
- At least one "Location" term must exist in the
locationtaxonomy. - The attacker must be logged in as a Subscriber.
3. Code Flow
- Entry Point: The user sends a POST request to
admin-ajax.phpwithaction=slw_location_status. - Hook Registration: In
inc/functions.php, the action is registered:add_action('wp_ajax_slw_location_status', 'slw_location_status'); - Execution: The
slw_location_status()function is called:- It checks if
$_POST['status']is set. - It verifies the nonce:
wp_verify_nonce( $_POST['slw_nonce_field'], 'slw_nonce' ). - Crucially, it skips any
current_user_can()check.
- It checks if
- Sink: The function calls
update_term_meta($location_id, 'slw_location_status', $status), modifying the database based on user input.
4. Nonce Acquisition Strategy
The nonce action string is slw_nonce. This nonce is used across several plugin features. For a Subscriber to exploit this, they must find where the plugin localizes this nonce to the frontend or an accessible admin area.
- Identify Triggers: The plugin enqueues frontend scripts when the
[slw_product_locations]shortcode is present on a page or when "Location Selection" is enabled in settings. - Setup Trigger Page: Use WP-CLI to create a public page containing the shortcode:
wp post create --post_type=page --post_status=publish --post_content='[slw_product_locations]' --post_title='Locations' - Extraction:
- Navigate to the page as a Subscriber.
- Use
browser_evalto search for the nonce. The plugin likely localizes data into a global JS object. - Look for variables like
slw_varsor search the HTML forslw_nonce_field. - Script search:
browser_eval("window.slw_vars?.nonce || document.querySelector('input[name=\"slw_nonce_field\"]')?.value").
5. Exploitation Strategy
Step 1: Target Identification
Find a valid location_id using WP-CLI or by inspecting the frontend.wp term list location --fields=term_id,name
Step 2: Nonce Extraction
Login as a Subscriber and visit a page where the plugin is active (created in Step 6). Use browser_eval to grab the nonce value.
Step 3: Unauthorized Modification
Send a POST request to change the status of a location.
Request Details:
- URL:
http://localhost:8080/wp-admin/admin-ajax.php - Method:
POST - Headers:
Content-Type: application/x-www-form-urlencoded - Body:
action=slw_location_status&status=no&location_id=[TERM_ID]&slw_nonce_field=[NONCE_VALUE]
6. Test Data Setup
- Create Location:
wp term create location "Warehouse Alpha" --description="Primary storage" - Identify ID: Note the
term_idreturned. - Create User:
wp user create attacker attacker@example.com --role=subscriber --user_pass=password - Create Trigger Page:
wp post create --post_type=page --post_status=publish --post_content='[slw_product_locations]'
7. Expected Results
- The AJAX response should be
1(success). - The location's status in the database will be updated despite the user only being a Subscriber.
8. Verification Steps
Verify the modification using WP-CLI:wp term meta get [TERM_ID] slw_location_status
The value should be 0 (false/no) if the exploit was successful in setting it to 'no'.
9. Alternative Approaches
If slw_location_status fails, attempt the same process with:
- Action:
slw_location_assignment- Sink: Updates
slw_location_assignmentmeta.
- Sink: Updates
- Action:
slw_map_status- Sink: Updates
slw_map_statusmeta.
- Sink: Updates
Both follow the same logic in inc/functions.php and use the same slw_nonce.
Summary
The Stock Locations for WooCommerce plugin fails to perform capability checks in several AJAX handlers, including those for location status, assignment, and map visibility. This allows authenticated attackers with subscriber-level access or higher to modify stock location metadata by exploiting functions that only verify a CSRF nonce but lack authorization controls.
Vulnerable Code
// inc/functions.php @ 3.1.8 add_action('wp_ajax_slw_location_assignment', 'slw_location_assignment'); if (!function_exists('slw_location_assignment')) { function slw_location_assignment() { if (!empty($_POST) && isset($_POST['assignment'])) { if (!isset($_POST['slw_nonce_field']) || !wp_verify_nonce($_POST['slw_nonce_field'], 'slw_nonce')) { echo '0'; } else { $assignment = ($_POST['assignment'] == 'yes'); $location_id = sanitize_slw_data($_POST['location_id']); update_term_meta($location_id, 'slw_location_assignment', $assignment); echo '1'; } } wp_die(); } } --- // inc/functions.php @ 3.1.8 add_action('wp_ajax_slw_location_status', 'slw_location_status'); if(!function_exists('slw_location_status')){ function slw_location_status(){ if(!empty($_POST) && isset($_POST['status'])){ if (! isset( $_POST['slw_nonce_field'] ) || ! wp_verify_nonce( $_POST['slw_nonce_field'], 'slw_nonce' ) ) { echo '0'; } else { $status = ($_POST['status']=='yes'); $location_id = sanitize_slw_data($_POST['location_id']); update_term_meta($location_id, 'slw_location_status', $status); echo '1'; } } wp_die(); } }
Security Fix
@@ -1,579 +1,519 @@ <?php if ( ! defined( 'ABSPATH' ) ){ exit; }else{ clearstatcache(); } -if(!function_exists('pre')){ - function pre($data){ - if(isset($_GET['debug'])){ - pree($data); - } - } -} +function slw_verify_admin_request($key='slw_nonce', $field='slw_nonce_field') { + + check_ajax_referer( $key, $field ); + + if ( ! current_user_can( 'manage_options' ) ) { + wp_send_json_error( + array( + 'message' => __( 'Unauthorized', 'stock-locations-for-woocommerce' ), + ), + 403 + ); + } +} ... +add_action('wp_ajax_slw_location_assignment', 'slw_location_assignment'); + +if (!function_exists('slw_location_assignment')) { + function slw_location_assignment() { + slw_verify_admin_request(); + + if (!empty($_POST) && isset($_POST['assignment'])) { + $assignment = ($_POST['assignment'] == 'yes'); + $location_id = sanitize_slw_data($_POST['location_id']); + update_term_meta($location_id, 'slw_location_assignment', $assignment); + echo '1'; + } + + wp_die(); + } +} +add_action('wp_ajax_slw_location_status', 'slw_location_status'); + +if(!function_exists('slw_location_status')){ + function slw_location_status(){ + slw_verify_admin_request(); + + if(!empty($_POST) && isset($_POST['status'])){ + $status = ($_POST['status']=='yes'); + $location_id = sanitize_slw_data($_POST['location_id']); + update_term_meta($location_id, 'slw_location_status', $status); + echo '1'; + } + + wp_die(); + } +}
Exploit Outline
1. Login as an authenticated user with Subscriber-level privileges. 2. Locate a valid Stock Location term ID (taxonomy: 'location'), which can often be found by inspecting the HTML source of a product page or a page containing the [slw_product_locations] shortcode. 3. Extract the 'slw_nonce' from the frontend. This is commonly localized in the 'slw_vars' global JS object or within a hidden input field named 'slw_nonce_field'. 4. Send a POST request to /wp-admin/admin-ajax.php with the following payload shape: - action: slw_location_status (or slw_location_assignment/slw_map_status) - slw_nonce_field: [extracted_nonce] - location_id: [target_term_id] - status: 'no' (to disable a location) 5. Observe that the server returns '1' and updates the term metadata in the database despite the low privilege level of the attacker.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.