Shield Security: Blocks Bots, Protects Users, and Prevents Security Breaches <= 21.0.9 - Missing Authorization to Authenticated (Subscriber+) Email MFA Update
Description
The Shield Security: Blocks Bots, Protects Users, and Prevents Security Breaches plugin for WordPress is vulnerable to unauthorized modification of data due to a missing capability check on the `MfaEmailDisable` action in all versions up to, and including, 21.0.9. This makes it possible for authenticated attackers, with Subscriber-level access and above, to disable the global Email 2FA setting for the entire site.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:NTechnical Details
<=21.0.9What Changed in the Fix
Changes introduced in v21.0.10
Source Code
WordPress.org SVNThis research plan outlines the steps to analyze and exploit CVE-2025-14427, a missing authorization vulnerability in Shield Security <= 21.0.9. ### 1. Vulnerability Summary The Shield Security plugin uses an "Action Router" to handle various plugin tasks via AJAX. The `MfaEmailDisable` action (slu…
Show full research plan
This research plan outlines the steps to analyze and exploit CVE-2025-14427, a missing authorization vulnerability in Shield Security <= 21.0.9.
1. Vulnerability Summary
The Shield Security plugin uses an "Action Router" to handle various plugin tasks via AJAX. The MfaEmailDisable action (slug: mfa_email_disable) is intended to allow users to manage their two-factor authentication settings. However, in affected versions, this action lacks a proper authorization check to ensure the user is only modifying their own settings or possesses administrative privileges. Consequently, a Subscriber-level user can trigger this action to disable the global site-wide Email 2FA configuration.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - Action:
shield_action - Sub-Action (Slug):
mfa_email_disable - Required Parameter:
exec_auth_nonce(The Action Router nonce) - Authentication Level: Subscriber (any logged-in user).
- Precondition: The "Email 2FA" feature must be enabled in the Shield Security settings (typically under Login Guard).
3. Code Flow
- Entry Point: An AJAX request is sent to
admin-ajax.phpwithaction=shield_action. - Routing:
FernleafSystems\Wordpress\Plugin\Shield\ActionRouter\ActionProcessorreceives the request and identifies the sub-action via theshield_actionparameter in the POST body. - Action Initialization: The class
FernleafSystems\Wordpress\Plugin\Shield\ActionRouter\Actions\MfaEmailDisable(inferred name) is instantiated. - Access Check:
BaseAction::checkAccess()is called.isUserAuthRequired()returnstrue.getMinimumUserAuthCapability()likely returnsread(to allow subscribers to manage their profile).isSecurityAdminRequired()returnsfalsebecause the capability is notmanage_options.
- Execution:
MfaEmailDisable::exec()is called. This function fails to restrict its operations to the current user's meta and instead updates the global plugin configuration for thelogin_guardmodule, disablingenable_email.
4. Nonce Acquisition Strategy
Shield Security localizes nonces for its action router in the userprofile script, which is loaded on the WordPress profile page for all users.
- Navigate to Profile: As a Subscriber, visit
/wp-admin/profile.php. - Identify Variable: Shield localizes router data into a global JavaScript object, usually
shield_vars_actionrouter. - Extract Nonce: Use
browser_evalto extract the nonce specifically for themfa_email_disableaction.- JS Command:
window.shield_vars_actionrouter?.nonces?.mfa_email_disable
- JS Command:
5. Exploitation Strategy
- Setup Phase:
- Log in as Admin.
- Enable Email 2FA globally.
- Create a Subscriber-level user.
- Nonce Extraction Phase:
- Log in as the Subscriber.
- Navigate to
/wp-admin/profile.php. - Execute
browser_evalto get theexec_auth_nonce.
- Attack Phase:
- Send a POST request to
/wp-admin/admin-ajax.php. - Headers:
Content-Type: application/x-www-form-urlencoded - Body:
action=shield_action&exec_auth_nonce=[EXTRACTED_NONCE]&shield_action=mfa_email_disable
- Send a POST request to
- Verification Phase:
- Check if the global Email 2FA setting is now disabled.
6. Test Data Setup
- Enable 2FA:
# Using WP-CLI to enable Shield's Email 2FA (this modifies the login_guard module options) # The option name is shield_firewall_login_guard_options wp eval " \$con = \FernleafSystems\Wordpress\Plugin\Shield\Controller\Controller::GetInstance(); \$mod = \$con->getModule_LoginGuard(); \$mod->getOptions()->setOpt('enable_email', 'Y'); \$mod->saveModOptions(); " - Create Subscriber:
wp user create attacker attacker@example.com --role=subscriber --user_pass=password123
7. Expected Results
- The AJAX request should return a JSON response indicating success (e.g.,
{"success": true}). - The site-wide configuration for Email 2FA should be toggled from 'Y' (Enabled) to 'N' (Disabled).
8. Verification Steps
- Check Plugin Options via WP-CLI:
wp eval " \$opts = get_option('shield_firewall_login_guard_options'); echo 'Email 2FA Status: ' . (\$opts['enable_email'] ?? 'Not Set'); " - Verify Admin UI: Navigate to the Shield Security "Login Guard" settings page as Admin and verify that "Email 2FA" is unchecked/disabled.
9. Alternative Approaches
- Direct Option Check: If
MfaEmailDisabledoesn't exist, check forMfaEmailToggleor similar. Shield's naming conventions are consistent; looking for "Email" and "Mfa" in thesrc/lib/src/ActionRouter/Actions/directory will confirm the slug. - Security Admin Bypass: If the plugin has "Security Admin" enabled (PIN protection), check if
MfaEmailDisableincorrectly bypasses theisSecurityAdminRequired()check (which it shouldn't, if it's modifying global settings). If the Security Admin is enabled, the request might also require theshield_sec_admin_forgotten_pinor similar session evidence, though the vulnerability description suggests the capability check itself is the failure point.
Summary
The Shield Security plugin for WordPress is vulnerable to an authorization bypass where authenticated users with Subscriber-level permissions can disable the global site-wide Email Two-Factor Authentication (2FA) setting. This occurs because the 'mfa_email_disable' action lacks specific permission checks to ensure that a low-privileged user can only modify their own profile rather than global plugin configurations.
Vulnerable Code
// src/lib/src/ActionRouter/Actions/BaseAction.php /** * @throws InvalidActionNonceException * @throws IpBlockedException * @throws SecurityAdminRequiredException * @throws UserAuthRequiredException */ protected function checkAccess() { $con = self::con(); $thisReq = $con->this_req; if ( !$thisReq->request_bypasses_all_restrictions && $thisReq->is_ip_blocked && !$this->canBypassIpAddressBlock() ) { throw new IpBlockedException( sprintf( 'IP Address blocked so cannot process action: %s', static::SLUG ) ); } $WPU = Services::WpUsers(); if ( $this->isUserAuthRequired() && ( !$WPU->isUserLoggedIn() || !user_can( $WPU->getCurrentWpUser(), $this->getMinimumUserAuthCapability() ) ) ) { throw new UserAuthRequiredException( sprintf( 'Must be logged-in to execute this action: %s', static::SLUG ) ); } if ( !$thisReq->is_security_admin && $this->isSecurityAdminRequired() ) { throw new SecurityAdminRequiredException( sprintf( 'Security admin required for action: %s', static::SLUG ) ); } if ( $this->isNonceVerifyRequired() && !ActionNonce::VerifyFromRequest() ) { throw new InvalidActionNonceException( 'Invalid Action Nonce Exception.' ); } } --- protected function getMinimumUserAuthCapability() :string { return self::con()->cfg->properties[ 'base_permissions' ] ?? 'manage_options'; }
Security Fix
@@ -154,14 +154,10 @@ * @return self For method chaining */ public function setActionOverride( string $overrideKey, $value ) :self { - // Initialize action_overrides array if it doesn't exist - if ( !isset( $this->action_data[ 'action_overrides' ] ) ) { - $this->action_data[ 'action_overrides' ] = []; - } - - // Set the override value - $this->action_data[ 'action_overrides' ][ $overrideKey ] = $value; - + $this->action_data[ 'action_overrides' ] = \array_merge( + \is_array( $this->action_data[ 'action_overrides' ] ?? null ) ? $this->action_data[ 'action_overrides' ] : [], + [ $overrideKey => $value ] + ); return $this; }
Exploit Outline
The exploit requires an authenticated WordPress user (Subscriber+). 1. Log in to the target WordPress site as a Subscriber-level user. 2. Navigate to the user profile page (`/wp-admin/profile.php`) to access the Shield Security Action Router data localized in the page source. 3. Extract the `exec_auth_nonce` from the `shield_vars_actionrouter` JavaScript object, specifically for the `mfa_email_disable` action. 4. Send a POST request to `/wp-admin/admin-ajax.php` with the parameters `action=shield_action`, `shield_action=mfa_email_disable`, and the extracted `exec_auth_nonce`. 5. Upon success, the plugin will update the global `login_guard` module configuration, setting the `enable_email` option to disabled for all users on the site.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.