Proof/CVE-2026-16534
The importer never asked who was promoting whom.
WordPress blocks a lesser role from minting an administrator through the admin screens. The Import and Export Users and Customers plugin took the same actor's CSV file and wrote the rows straight into wp_insert_user — so a role that could only create users could hand itself an administrator, or rewrite an existing admin's password.
WordPress does not treat "create a user" and "promote a user to administrator" as the same permission. Core maps them to distinct capabilities — create_users, promote_users, edit_users — and adds a hard rule that a non-administrator can never edit or elevate an administrator. A bulk-import feature is only safe if it re-checks those same capabilities for the person clicking Import.
A user who holds only the user-creation capability must not be able to assign the administrator role, nor edit an account that already holds it.
The bug was not that the plugin could import users. The bug was that the import path trusted the CSV's role, user_pass and target-identity columns as data, while WordPress core treats acting on those same fields as privileged operations gated by the current user's capabilities.
The import handler confirmed the actor could reach the import screen and create users, then looped over rows and called WordPress's user APIs with the CSV's own field values. What it never did was re-map the role change or the edit of an existing account against the acting user — the check core performs on every admin-screen equivalent.
import screen:
require current_user_can('create_users') // gate present
per row:
role = row['role'] // taken verbatim
target = existing user? update : insert
wp_insert_user([... 'role' => role]) // no promote_users check
wp_update_user([... 'user_pass' => row['user_pass'],
'user_email' => row['user_email']])
// no edit_user(target) check
// missing: current_user_can('promote_users')
// missing: current_user_can('edit_user', target_admin_id)Core's own map_meta_cap() would have refused both operations for a lesser role: promoting to administrator requires promote_users, and editing an existing administrator is denied outright to anyone who is not one. By calling the low-level functions directly, the importer skipped the layer that enforces those rules.
User-creation role
A custom role with import access and create_users, nothing more.
Crafted CSV
Role column set to administrator; or an admin's login plus a new password.
Direct write
Rows passed to wp_insert_user / wp_update_user unchecked.
Admin control
New administrator, or an existing admin's credentials overwritten.
Actor holds only user creation
A custom or vendor role (shop manager, staff onboarding, LMS instructor) that can reach the plugin's import screen and create accounts, but cannot promote users or edit administrators through wp-admin.
Admin-screen control confirmed active
The same actor tries to set a new user's role to administrator in the normal Users screen and is refused. This proves core's capability boundary is present and enforced.
CSV carries the privileged fields
A row sets role=administrator for a new account, or names an existing administrator by login/ID and supplies a new user_pass / user_email.
Import writes without re-checking
The handler passes each row to wp_insert_user / wp_update_user. No promote_users check on the role, no per-target edit check on the administrator.
Administrator obtained or hijacked
The attacker logs in to the freshly minted administrator, or authenticates as the existing admin whose password they just overwrote. Full site control follows.
The finding was promoted only after the vulnerable path, the negative control and the fixed control were retained together against the same actor and site.
Admin screen
The user-creation role is refused when it tries to set administrator or edit an admin in wp-admin.
CSV import
The same role imports a CSV; an administrator is created and an existing admin's credentials are overwritten.
2.4.2 import
The privileged row is rejected because the import now enforces role-assignment and per-user edit capabilities.
A capability check that guards the screen is not the same as a capability check that guards the operation. The import screen asked "can this user create accounts?" and then performed operations — promotion, and editing an administrator — that demand far stronger permissions.
The durable engineering rule is that bulk and API paths must re-map every privileged field against the acting user with the same current_user_can() checks the interactive path uses. A CSV column is attacker-controlled input, not an authorization decision.
Agents mapped the capability gap. Evidence proved it crossed a role boundary.
The workflow did not stop at "the import writes users." Each stage had to produce evidence, or a refutation, before the candidate could move forward.
Source mapping
Trace the CSV role and target-identity columns into the user-write calls and isolate every capability check on the path.
Capability hypothesis
Challenge the assumption that a create-users gate is sufficient to authorize a promote-to-administrator write.
Runtime construction
Stand up a non-admin role and the smallest CSV that both mints an administrator and rewrites an existing one.
Skeptic gate
Admin-screen negative control, exact privilege marker, and re-run against 2.4.2 as the fixed control.
Promotion and disclosure
Review source, replay, impact, version range, wording and public-safety boundary before reporting to the vendor.
WPScan scores CVE-2026-16534 at 7.2 High on CVSS 3.1. The PR:H term reflects that the attacker must already hold a privileged role — one with import access and user creation — not that the impact is limited. Once that foothold exists, confidentiality, integrity and availability are all fully compromised at the administrator level.
CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H
The realistic actor is an inside role or a lower-tier account on a multi-author, membership, e-commerce or LMS site — exactly the deployments that hand out custom roles with user-management slices. Any such role that can reach this import screen becomes an administrator on demand.
| Plugin | Affected range | Fixed |
|---|---|---|
import-users-from-csv-with-meta | ≤ 2.4.1 (all < 2.4.2) | 2.4.2 |
Marketed as Import and Export Users and Customers. Sites that never delegate user management to a non-administrator role are not directly exploitable, but should still upgrade.
The fix is not "block the administrator string in CSV." The durable rule is that the import must authorize each privileged field against the acting user exactly as the interactive path does.
- Upgrade to Import and Export Users and Customers 2.4.2 or later.
- Enforce
current_user_can('promote_users')before honoring any role in a row, and reject or downgrade rows that request roles the actor cannot grant. - Enforce a per-target edit check (
current_user_can('edit_user', $id)) before updating an existing account, so administrators cannot be edited by lesser roles. - Until patched, restrict the import screen to full administrators and audit custom roles that carry
create_usersor list-user capabilities. - Review recently created administrators and any unexpected email/password changes on existing admin accounts.
The bug lived between two ideas of "who can do this." WordPress core said a lesser role may not promote users or touch an administrator. The plugin said, in effect, "if you reached the import screen, your rows are trusted." A CSV column carried the privileged instruction across that gap.
The CVE is the identifier. The admin-screen negative control is what proved the role boundary was real before the import erased it.
- WPScan / WPVDB record for CVE-2026-16534
- NVD record for CVE-2026-16534
- Import and Export Users and Customers plugin page
- WordPress
map_meta_cap()— promote_users / edit_users mapping - WordPress
wp_insert_user()reference
Credit: Discovered and reported by Charles Vosburgh. Research was AI-assisted through source mapping, hypothesis generation, runtime construction and skeptical validation; final review and disclosure ownership remained human.