CVE-2025-15260

MyRewards – Loyalty Points and Rewards for WooCommerce <= 5.6.1 - Missing Authorization to Authenticated (Subscriber+) Arbitrary Loyalty Rule Modification

mediumMissing Authorization
6.5
CVSS Score
6.5
CVSS Score
medium
Severity
5.7.0
Patched in
13d
Time to patch

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:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Unchanged
None
Confidentiality
High
Integrity
None
Availability

Technical Details

Affected versions<=5.6.1
PublishedFebruary 3, 2026
Last updatedFebruary 16, 2026
Affected pluginwoorewards

What Changed in the Fix

Changes introduced in v5.7.0

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# 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 the ajax() function in the rule management class).
  • Vulnerable Parameter: method (defines the sub-action), rule (array containing rule data), and id (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

  1. Entry Point: A Subscriber sends a POST request to admin-ajax.php with action=woorewards_ajax.
  2. Hook: The plugin registers this action via add_action( 'wp_ajax_woorewards_ajax', ... ).
  3. Dispatching: The ajax() function is called. It checks the method parameter (e.g., save_rule).
  4. Nonce Check: The function likely calls check_ajax_referer('woorewards_ajax', 'nonce') or wp_verify_nonce(). Since the Subscriber can obtain this nonce from the frontend/dashboard, this check passes.
  5. Vulnerability: The function omits a check for current_user_can('manage_options').
  6. Processing: Input from the rule or multiplier parameter is passed directly to the rule update logic (e.g., update_option or 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.

  1. Identify Page: Visit any page where the loyalty program is active (e.g., My Account page or a page containing the [myrewards_points] shortcode).
  2. Shortcode Creation:
    wp post create --post_type=page --post_status=publish --post_title="Loyalty" --post_content='[myrewards_account]'
    
  3. Browser Navigation: Navigate to the newly created page as a Subscriber.
  4. Extraction:
    Use browser_eval to 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:
    action=woorewards_ajax&method=save_rule&rule[id]=RULE_ID&rule[multiplier]=9999&nonce=EXTRACTED_NONCE
    
    (Note: The exact parameter structure might be id=RULE_ID&multiplier=9999 or inside a rule[] array depending on the specific ajax() 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

  1. Create a Subscriber User:
    wp user create attacker attacker@example.com --role=subscriber --user_pass=password
    
  2. 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
    
  3. Create a Nonce-Leaking Page:
    wp post create --post_type=page --post_status=publish --post_content='[myrewards_account]'
    

7. Expected Results

  • HTTP Response: 200 OK or 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_rule fails, try method=delete_rule&id=RULE_ID.
  • EditList Action: If the plugin uses the lws-adminpanel library's generic handler, the action might be lws_editlist_action with subaction=apply.
    • Body: action=lws_editlist_action&subaction=apply&id=1&multiplier=9999&nonce=...
  • Parameter Variation: Try passing the rule data as a JSON string in a data parameter if the rule array doesn't work.
Research Findings
Static analysis — not yet PoC-verified

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

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/woorewards/5.6.0/assets/lws-adminpanel/include/admin.php /home/deploy/wp-safety.org/data/plugin-versions/woorewards/5.7.0/assets/lws-adminpanel/include/admin.php
--- /home/deploy/wp-safety.org/data/plugin-versions/woorewards/5.6.0/assets/lws-adminpanel/include/admin.php	2025-08-26 07:32:14.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/woorewards/5.7.0/assets/lws-adminpanel/include/admin.php	2026-02-11 09:28:10.000000000 +0000
@@ -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.