diff --git a/src/network-services-pentesting/pentesting-web/wordpress.md b/src/network-services-pentesting/pentesting-web/wordpress.md index 04717c759..416a8b034 100644 --- a/src/network-services-pentesting/pentesting-web/wordpress.md +++ b/src/network-services-pentesting/pentesting-web/wordpress.md @@ -490,6 +490,76 @@ add_action( 'wp_ajax_litho_remove_font_family_action_data', 'secure_remove_font_ --- +### Privilege escalation via stale role restoration and missing authorization (ASE "View Admin as Role") + +Many plugins implement a "view as role" or temporary role-switching feature by saving the original role(s) in user meta so they can be restored later. If the restoration path relies only on request parameters (e.g., `$_REQUEST['reset-for']`) and a plugin-maintained list without checking capabilities and a valid nonce, this becomes a vertical privilege escalation. + +A real-world example was found in the Admin and Site Enhancements (ASE) plugin (≤ 7.6.2.1). The reset branch restored roles based on `reset-for=` if the username appeared in an internal array `$options['viewing_admin_as_role_are']`, but performed neither a `current_user_can()` check nor a nonce verification before removing current roles and re-adding the saved roles from user meta `_asenha_view_admin_as_original_roles`: + +```php +// Simplified vulnerable pattern +if ( isset( $_REQUEST['reset-for'] ) ) { + $reset_for_username = sanitize_text_field( $_REQUEST['reset-for'] ); + $usernames = get_option( ASENHA_SLUG_U, [] )['viewing_admin_as_role_are'] ?? []; + + if ( in_array( $reset_for_username, $usernames, true ) ) { + $u = get_user_by( 'login', $reset_for_username ); + foreach ( $u->roles as $role ) { $u->remove_role( $role ); } + $orig = (array) get_user_meta( $u->ID, '_asenha_view_admin_as_original_roles', true ); + foreach ( $orig as $r ) { $u->add_role( $r ); } + } +} +``` + +Why it’s exploitable + +- Trusts `$_REQUEST['reset-for']` and a plugin option without server-side authorization. +- If a user previously had higher privileges saved in `_asenha_view_admin_as_original_roles` and was downgraded, they can restore them by hitting the reset path. +- In some deployments, any authenticated user could trigger a reset for another username still present in `viewing_admin_as_role_are` (broken authorization). + +Attack prerequisites + +- Vulnerable plugin version with the feature enabled. +- Target account has a stale high-privilege role stored in user meta from earlier use. +- Any authenticated session; missing nonce/capability on the reset flow. + +Exploitation (example) + +```bash +# While logged in as the downgraded user (or any auth user able to trigger the code path), +# hit any route that executes the role-switcher logic and include the reset parameter. +# The plugin uses $_REQUEST, so GET or POST works. The exact route depends on the plugin hooks. +curl -s -k -b 'wordpress_logged_in=...' \ + 'https://victim.example/wp-admin/?reset-for=' +``` + +On vulnerable builds this removes current roles and re-adds the saved original roles (e.g., `administrator`), effectively escalating privileges. + +Detection checklist + +- Look for role-switching features that persist “original roles” in user meta (e.g., `_asenha_view_admin_as_original_roles`). +- Identify reset/restore paths that: + - Read usernames from `$_REQUEST` / `$_GET` / `$_POST`. + - Modify roles via `add_role()` / `remove_role()` without `current_user_can()` and `wp_verify_nonce()` / `check_admin_referer()`. + - Authorize based on a plugin option array (e.g., `viewing_admin_as_role_are`) instead of the actor’s capabilities. + +Hardening + +- Enforce capability checks on every state-changing branch (e.g., `current_user_can('manage_options')` or stricter). +- Require nonces for all role/permission mutations and verify them: `check_admin_referer()` / `wp_verify_nonce()`. +- Never trust request-supplied usernames; resolve the target user server-side based on the authenticated actor and explicit policy. +- Invalidate “original roles” state on profile/role updates to avoid stale high-privilege restoration: + +```php +add_action( 'profile_update', function( $user_id ) { + delete_user_meta( $user_id, '_asenha_view_admin_as_original_roles' ); +}, 10, 1 ); +``` + +- Consider storing minimal state and using time-limited, capability-guarded tokens for temporary role switches. + +--- + ## WordPress Protection ### Regular Updates @@ -584,5 +654,7 @@ The server responds with the contents of `wp-config.php`, leaking DB credentials - [Unauthenticated Arbitrary File Deletion Vulnerability in Litho Theme](https://patchstack.com/articles/unauthenticated-arbitrary-file-delete-vulnerability-in-litho-the/) - [Multiple Critical Vulnerabilities Patched in WP Job Portal Plugin](https://patchstack.com/articles/multiple-critical-vulnerabilities-patched-in-wp-job-portal-plugin/) +- [Rare Case of Privilege Escalation in ASE Plugin Affecting 100k+ Sites](https://patchstack.com/articles/rare-case-of-privilege-escalation-in-ase-plugin-affecting-100k-sites/) +- [ASE 7.6.3 changeset – delete original roles on profile update](https://plugins.trac.wordpress.org/changeset/3211945/admin-site-enhancements/tags/7.6.3/classes/class-view-admin-as-role.php?old=3208295&old_path=admin-site-enhancements%2Ftags%2F7.6.2%2Fclasses%2Fclass-view-admin-as-role.php) {{#include ../../banners/hacktricks-training.md}}