Subscriber, as shipped
wp_user_roles · default
- Capabilities
- read · level_0
- Users screen shows
- Subscriber
- Can install plugins
- No
Guide · Roles and capabilities
Your administrator list looks right, yet a subscriber or customer account reached the Plugins screen, settings changed that nobody admits to, or a security plugin warns that a role has unexpected capabilities. WordPress decides what a user can do from two places in the database: the role definitions stored in the user_roles option, and each user’s own capabilities entry in usermeta. Change either and an account that the Users screen still calls “Subscriber” can install plugins, edit files and create administrators. This guide shows how to read both without changing anything, how to tell a plugin’s legitimate custom role from a tampered one, and how to put roles back without breaking WooCommerce or membership plugins.
Published Last reviewed
How WordPress decides
The user_roles option holds a serialised array of every role on the site — administrator, editor, author, contributor and subscriber, plus any role a plugin added — and the capabilities each one grants. Change the subscriber entry to include manage_options or install_plugins and every subscriber on the site gains that right at once, while the Users screen goes on listing them as subscribers.
Each user also has a capabilities entry in usermeta: normally a one-entry serialised array naming their role. WordPress lets that entry carry individual capabilities alongside the role, so one account can be given manage_options or edit_users on its own. Again the Users screen shows only the role.
Either change needs a write to the database — through a vulnerable plugin, a stolen administrator login, or code already running on the site. It is a quiet form of persistence: the attacker keeps an ordinary-looking account and no new administrator ever appears.
Figure 1 · Admin rights without an admin
Two ways to hand out administrator rights that the Users screen still labels “Subscriber”.
wp_user_roles · default
wp_user_roles · tampered
wp_capabilities usermeta
Read the roles
WP-CLI loads WordPress to run these; --skip-plugins and --skip-themes keep plugin and theme code from running, although must-use plugins still load. If you suspect code in mu-plugins, read the database directly with the SQL below instead.
Every role the site defines, by display name and key:
wp role list --skip-plugins --skip-themes
Expect the five WordPress roles plus those your plugins document: WooCommerce adds shop_manager and customer, for example. A role nobody can attribute to an installed plugin is the first finding.
Start with the roles ordinary users hold; the customer role exists only where WooCommerce is installed:
wp cap list subscriber --skip-plugins --skip-themes
wp cap list customer --skip-plugins --skip-themes
A default subscriber has read and level_0, nothing else. Capabilities that matter most if they turn up on a low role: manage_options, install_plugins, activate_plugins, edit_plugins, edit_themes, edit_users, create_users, promote_users, delete_users, unfiltered_html and update_core.
The whole role definition as JSON, saved outside the web root for the record:
mkdir -p ~/incident
wp option get wp_user_roles --format=json --skip-plugins --skip-themes > ~/incident/user_roles.json
This is the evidence of what was changed. Take it before any reset.
The same option, without loading WordPress at all:
SELECT option_value
FROM wp_options
WHERE option_name = 'wp_user_roles';
The value is serialised. Read it as text; do not edit it in place, because changing any string’s length without updating its recorded length breaks the whole option.
Read the users
A role can be clean while a single account carries extra rights. These checks compare what each user is labelled with against what they actually hold.
Compare this list with the Users screen; a difference means the screen is being filtered:
SELECT u.ID, u.user_login, u.user_email, u.user_registered
FROM wp_users u
JOIN wp_usermeta m ON m.user_id = u.ID
WHERE m.meta_key = 'wp_capabilities'
AND m.meta_value LIKE '%administrator%';
Accounts hidden from the Users screen are covered in the rogue-administrator guide.
A user with a single role stores exactly one entry. This lists everyone with more — a second role, or individual capabilities added on top:
SELECT u.ID, u.user_login, m.meta_value
FROM wp_users u
JOIN wp_usermeta m ON m.user_id = u.ID
WHERE m.meta_key = 'wp_capabilities'
AND m.meta_value NOT REGEXP '^a:1:[{]s:[0-9]+:"[a-z0-9_-]+";b:1;[}]$';
Membership and LMS plugins assign extra roles legitimately, so read each result rather than treating the list as a verdict.
Everything a given user can do, from their role and their own entry combined:
wp user list-caps USER --skip-plugins --skip-themes
wp user meta get USER wp_capabilities --skip-plugins --skip-themes
On multisite, also list network super administrators with wp super-admin list: they hold every capability on every site, whatever their per-site role says.
Do not miss this
WordPress asks the user_has_cap and map_meta_cap filters before every permission check, and any plugin, theme or must-use plugin can hook them. A backdoor can grant itself manage_options for one cookie or one user ID at runtime, so the database looks untouched while the account behaves like an administrator.
If an account demonstrably reaches screens its stored capabilities do not allow, search the code rather than the database. A match outside a plugin you installed and can name — in mu-plugins, a drop-in, uploads or the active theme — is the next lead:
grep -rnE "user_has_cap|map_meta_cap" wp-content/
Many legitimate plugins use these filters, so read the matches rather than counting them.
Put it back
Preserve first — the JSON copy above and a database export. Then correct the roles, the accounts, and the access the escalation was used for.
A full copy outside the web root, so nothing you change is unrecoverable:
wp db export ~/incident/before-roles-reset.sql
Keep it with the user_roles.json copy.
WP-CLI restores the five default roles to their shipped capabilities; one role at a time, or all of them:
wp role reset subscriber
wp role reset --all
This touches only administrator, editor, author, contributor and subscriber. Roles added by plugins — WooCommerce’s shop_manager and customer, membership roles — are left as they are, so check those against the plugin’s documentation or a clean install of the same version.
Move anyone holding the role to an appropriate one first, then delete it:
wp user set-role USER subscriber
wp role delete ROLE_KEY
set-role replaces a user’s roles but keeps individually granted capabilities, which the next step deals with.
Remove each one the user should not have:
wp user remove-cap USER manage_options
Re-run the usermeta query afterwards until every remaining multi-entry account has an explanation.
Review what those accounts did: new plugins, edited files, new users, exported data. Rotate administrator and application passwords and the WordPress salts, and find the write path that changed the roles before declaring it closed.
Common questions
Yes. Plugins add roles and capabilities all the time — WooCommerce, membership, LMS and role-editor plugins among them. The question is whether every change has an owner. A capability on the subscriber role that no installed plugin documents does not.
No. It resets only the five default WordPress roles to their shipped capabilities and does not touch roles that plugins added. Check those separately against the plugin’s own documentation.
Not necessarily. Elevated rights can sit in a role definition, in one user’s capabilities, or in a code filter, and none of those changes the role name the screen shows. Run the role and usermeta checks above.
Through any write to the database: an exploited plugin, SQL injection, a stolen login with enough rights, or code already running on the server. The change itself tells you there was a write path; finding it is part of the response.
Not by hand. The value is serialised, and changing a string without correcting its recorded length makes WordPress discard the whole option — taking every role with it. Use WP-CLI’s role and capability commands instead.