CVE-2025-14427

Shield Security: Blocks Bots, Protects Users, and Prevents Security Breaches <= 21.0.9 - Missing Authorization to Authenticated (Subscriber+) Email MFA Update

mediumMissing Authorization
4.3
CVSS Score
4.3
CVSS Score
medium
Severity
21.0.10
Patched in
1d
Time to patch

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:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Unchanged
None
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=21.0.9
PublishedFebruary 18, 2026
Last updatedFebruary 19, 2026
Affected pluginwp-simple-firewall

What Changed in the Fix

Changes introduced in v21.0.10

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

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 (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

  1. Entry Point: An AJAX request is sent to admin-ajax.php with action=shield_action.
  2. Routing: FernleafSystems\Wordpress\Plugin\Shield\ActionRouter\ActionProcessor receives the request and identifies the sub-action via the shield_action parameter in the POST body.
  3. Action Initialization: The class FernleafSystems\Wordpress\Plugin\Shield\ActionRouter\Actions\MfaEmailDisable (inferred name) is instantiated.
  4. Access Check: BaseAction::checkAccess() is called.
    • isUserAuthRequired() returns true.
    • getMinimumUserAuthCapability() likely returns read (to allow subscribers to manage their profile).
    • isSecurityAdminRequired() returns false because the capability is not manage_options.
  5. 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 the login_guard module, disabling enable_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.

  1. Navigate to Profile: As a Subscriber, visit /wp-admin/profile.php.
  2. Identify Variable: Shield localizes router data into a global JavaScript object, usually shield_vars_actionrouter.
  3. Extract Nonce: Use browser_eval to extract the nonce specifically for the mfa_email_disable action.
    • JS Command: window.shield_vars_actionrouter?.nonces?.mfa_email_disable

5. Exploitation Strategy

  1. Setup Phase:
    • Log in as Admin.
    • Enable Email 2FA globally.
    • Create a Subscriber-level user.
  2. Nonce Extraction Phase:
    • Log in as the Subscriber.
    • Navigate to /wp-admin/profile.php.
    • Execute browser_eval to get the exec_auth_nonce.
  3. 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
      
  4. Verification Phase:
    • Check if the global Email 2FA setting is now disabled.

6. Test Data Setup

  1. 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();
    "
    
  2. 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

  1. 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');
    "
    
  2. 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 MfaEmailDisable doesn't exist, check for MfaEmailToggle or similar. Shield's naming conventions are consistent; looking for "Email" and "Mfa" in the src/lib/src/ActionRouter/Actions/ directory will confirm the slug.
  • Security Admin Bypass: If the plugin has "Security Admin" enabled (PIN protection), check if MfaEmailDisable incorrectly bypasses the isSecurityAdminRequired() check (which it shouldn't, if it's modifying global settings). If the Security Admin is enabled, the request might also require the shield_sec_admin_forgotten_pin or similar session evidence, though the vulnerability description suggests the capability check itself is the failure point.
Research Findings
Static analysis — not yet PoC-verified

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

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/wp-simple-firewall/21.0.9/src/lib/src/ActionRouter/Actions/BaseAction.php /home/deploy/wp-safety.org/data/plugin-versions/wp-simple-firewall/21.0.10/src/lib/src/ActionRouter/Actions/BaseAction.php
--- /home/deploy/wp-safety.org/data/plugin-versions/wp-simple-firewall/21.0.9/src/lib/src/ActionRouter/Actions/BaseAction.php	2026-01-12 14:09:12.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/wp-simple-firewall/21.0.10/src/lib/src/ActionRouter/Actions/BaseAction.php	2026-01-13 13:33:30.000000000 +0000
@@ -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.