[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fmV5mJIWY4DsAlu4JqN9Xkxh3O4_tA5qqYmEuejWVCd0":3},{"id":4,"url_slug":5,"title":6,"description":7,"plugin_slug":8,"theme_slug":9,"affected_versions":10,"patched_in_version":11,"severity":12,"cvss_score":13,"cvss_vector":14,"vuln_type":15,"published_date":16,"updated_date":17,"references":18,"days_to_patch":20,"patch_diff_files":21,"patch_trac_url":9,"research_status":29,"research_verified":30,"research_rounds_completed":31,"research_plan":32,"research_summary":33,"research_vulnerable_code":34,"research_fix_diff":35,"research_exploit_outline":36,"research_model_used":37,"research_started_at":38,"research_completed_at":39,"research_error":9,"poc_status":9,"poc_video_id":9,"poc_summary":9,"poc_steps":9,"poc_tested_at":9,"poc_wp_version":9,"poc_php_version":9,"poc_playwright_script":9,"poc_exploit_code":9,"poc_has_trace":30,"poc_model_used":9,"poc_verification_depth":9,"poc_exploit_code_gated":30,"source_links":40},"CVE-2025-15370","shield-security-authenticated-subscriber-insecure-direct-object-reference-to-disable-google-authenticator","Shield Security \u003C= 21.0.9 - Authenticated (Subscriber+) Insecure Direct Object Reference to Disable Google Authenticator","The Shield: Blocks Bots, Protects Users, and Prevents Security Breaches plugin for WordPress is vulnerable to Insecure Direct Object Reference in all versions up to, and including, 21.0.9 via the MfaGoogleAuthToggle class due to missing validation on a user controlled key. This makes it possible for authenticated attackers, with Subscriber-level access and above, to disable Google Authenticator for any user.","wp-simple-firewall",null,"\u003C=21.0.9","21.0.10","medium",4.3,"CVSS:3.1\u002FAV:N\u002FAC:L\u002FPR:L\u002FUI:N\u002FS:U\u002FC:N\u002FI:L\u002FA:N","Authorization Bypass Through User-Controlled Key","2026-01-15 15:58:56","2026-01-16 04:44:37",[19],"https:\u002F\u002Fwww.wordfence.com\u002Fthreat-intel\u002Fvulnerabilities\u002Fid\u002Fd777014a-5397-4062-af39-7ea86589a0d0?source=api-prod",1,[22,23,24,25,26,27,28],"cl.json","icwp-wpsf.php","plugin.json","readme.txt","src\u002Flib\u002Ffunctions\u002Ffunctions.php","src\u002Flib\u002Fsrc\u002FActionRouter\u002FActions\u002FBaseAction.php","src\u002Flib\u002Fsrc\u002FActionRouter\u002FActions\u002FMfaPasskeyAuthenticationStart.php","researched",false,3,"# Exploitation Research Plan - CVE-2025-15370\n\n## 1. Vulnerability Summary\nThe **Shield Security** plugin (versions \u003C= 21.0.9) contains an Insecure Direct Object Reference (IDOR) vulnerability in the `MfaGoogleAuthToggle` action. The vulnerability exists because the plugin fails to verify that the user ID provided in the request matches the currently authenticated user's ID, or that the requester has administrative privileges to modify other users' settings. This allows an authenticated attacker with a low-privileged account (Subscriber+) to disable Google Authenticator (2FA) for any other user, including administrators.\n\n## 2. Attack Vector Analysis\n- **Endpoint:** `\u002Fwp-admin\u002Fadmin-ajax.php`\n- **Action:** `shield_action` (The main router for Shield's internal actions)\n- **Sub-Action (Action Slug):** `mfa_google_auth_toggle`\n- **Vulnerable Parameter:** `user_id` (within the `action_data` array)\n- **Authentication Level:** Subscriber or higher.\n- **Preconditions:** \n    1. The attacker must have a valid login session.\n    2. The victim (e.g., Administrator) must have Google Authenticator enabled.\n    3. The Shield \"Login Guard\" module must have Google Authenticator enabled in its settings.\n\n## 3. Code Flow\n1. **Entry Point:** The request hits `admin-ajax.php` with `action=shield_action`. \n2. **Routing:** The Shield `ActionRouter` captures the request. It identifies the target action based on the `sub_action` parameter (slug: `mfa_google_auth_toggle`).\n3. **Action Execution:** The `MfaGoogleAuthToggle` class (which extends `BaseAction`) is instantiated. \n4. **Access Control:** `BaseAction::checkAccess()` is called. \n    - It checks `isUserAuthRequired()`. Since the capability required for profile updates is likely `read` or `subscriber` level, a Subscriber passes this check.\n    - It checks for a valid Shield Action Nonce.\n5. **Vulnerable Logic:** The `exec()` method of `MfaGoogleAuthToggle` (inferred) likely retrieves a `user_id` from the `action_data` (populated from `$_POST`). \n6. **The Sink:** It calls a provider method (likely in `FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\TwoFactor\\Provider\\GoogleAuth`) to disable or toggle the 2FA state for the specified `user_id` without verifying if `current_user_id == user_id`.\n\n## 4. Nonce Acquisition Strategy\nShield Security localizes its nonces and configuration data into JavaScript objects on specific admin pages.\n\n1. **Page to Visit:** The \"Your Profile\" (`\u002Fwp-admin\u002Fprofile.php`) or the dedicated \"Shield -> Login Security\" page.\n2. **Shortcode\u002FTrigger:** Shield enqueues the `userprofile` script handle on the profile page.\n3. **JS Variable Name:** From `plugin.json`, the handle is `userprofile`. The localization key is typically `shield_vars_userprofile` or `shield_vars_main`.\n4. **Extraction Steps:**\n    - Log in as the Subscriber.\n    - Navigate to `wp-admin\u002Fprofile.php`.\n    - Use `browser_eval` to extract the nonce:\n      ```javascript\n      \u002F\u002F Likely location of the nonce\n      window.shield_vars_userprofile?.ajax?.shield_action?.nonce\n      \u002F\u002F OR specifically for the mfa_google_auth_toggle if distinct\n      window.shield_vars_userprofile?.ajax?.mfa_google_auth_toggle?.nonce\n      ```\n\n## 5. Exploitation Strategy\nThe goal is to disable Google Authenticator for User ID 1 (Admin) while logged in as User ID 2 (Subscriber).\n\n**Step-by-step:**\n\n1. **Log in as Subscriber:** Obtain a session cookie.\n2. **Extract Nonce:** Follow the strategy in Section 4 to get a valid `shield_action` nonce.\n3. **Craft the Exploit Request:**\n   - **URL:** `http:\u002F\u002F[target]\u002Fwp-admin\u002Fadmin-ajax.php`\n   - **Method:** `POST`\n   - **Content-Type:** `application\u002Fx-www-form-urlencoded`\n   - **Body Parameters:**\n     - `action`: `shield_action`\n     - `exec`: `mfa_google_auth_toggle` (Note: Shield often uses `exec` or `sub_action` to route the internal action)\n     - `user_id`: `1` (The victim Admin ID)\n     - `_ajax_nonce`: `[EXTRACTED_NONCE]`\n     - `state`: `0` (To disable) or simply sending the request if it's a hard toggle.\n\n**Example Request:**\n```http\nPOST \u002Fwp-admin\u002Fadmin-ajax.php HTTP\u002F1.1\nHost: localhost\nContent-Type: application\u002Fx-www-form-urlencoded\nCookie: wordpress_logged_in_...\n\naction=shield_action&exec=mfa_google_auth_toggle&user_id=1&_ajax_nonce=abcdef1234\n```\n\n## 6. Test Data Setup\n1. **Create Admin User (Victim):** User ID 1.\n2. **Enable Shield 2FA:** \n   - Navigate to Shield -> Login Guard -> 2FA.\n   - Enable \"Google Authenticator\".\n3. **Activate GA for Admin:** Use the Shield profile settings to set up a secret key for the Admin user.\n4. **Create Subscriber User (Attacker):** User ID 2.\n5. **Identify ID:** Confirm Admin is `1` and Subscriber is `2` using `wp user list`.\n\n## 7. Expected Results\n- The server should respond with a JSON object indicating success (e.g., `{\"success\": true}`).\n- The `user_meta` associated with User 1 regarding Google Authenticator (often key `shield_ga_secret` or similar in the `wp_user_meta` table) will be removed or marked as disabled.\n- Upon the next login attempt, the Admin (User 1) will no longer be prompted for a Google Authenticator code.\n\n## 8. Verification Steps\n1. **WP-CLI Check:**\n   ```bash\n   # Check user meta for the admin user\n   wp user meta list 1 --keys=shield_ga_secret,shield_ga_validated\n   ```\n   If the exploit worked, these meta keys should either be empty, removed, or `shield_ga_validated` set to `0`.\n2. **Manual Login:** Attempt to log in as the Administrator. If the 2FA screen is bypassed, the exploit is successful.\n\n## 9. Alternative Approaches\nIf the `mfa_google_auth_toggle` slug does not work:\n- Check for `mfa_provider_toggle` with a `provider=ga` parameter.\n- Check if the action is handled via a `REST API` endpoint instead of `admin-ajax.php`. Shield sometimes registers `shield\u002Fv1\u002Faction\u002F...` routes.\n- If the nonce is tied specifically to the action, look for `window.shield_vars_userprofile?.ajax?.shield_action` specifically.","Shield Security (\u003C= 21.0.9) is vulnerable to an Insecure Direct Object Reference (IDOR) via the `mfa_google_auth_toggle` action. This allows authenticated attackers with Subscriber-level permissions or higher to disable Google Authenticator (2FA) for any other user on the site, including administrators, by supplying a target user ID without proper authorization verification.","\u002F\u002F src\u002Flib\u002Fsrc\u002FActionRouter\u002FActions\u002FMfaPasskeyAuthenticationStart.php\n\nnamespace FernleafSystems\\Wordpress\\Plugin\\Shield\\ActionRouter\\Actions;\n\nuse FernleafSystems\\Wordpress\\Plugin\\Shield\\ActionRouter\\Actions\\Traits\\AuthNotRequired;\nuse FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\TwoFactor\\Provider\\Passkey;\n\nclass MfaPasskeyAuthenticationStart extends MfaUserConfigBase {\n\n\tuse AuthNotRequired;\n\n\tpublic const SLUG = 'mfa_passkey_auth_start';\n\n\tprotected function exec() {\n\n\t\t$response = [\n\t\t\t'success'     => false,\n\t\t\t'page_reload' => false\n\t\t];\n\n\t\t$user = $this->getActiveWPUser();\n\t\tif ( empty( $user ) ) {\n\t\t\t$response[ 'message' ] = __( 'User must be logged-in.', 'wp-simple-firewall' );\n\t\t}\n\t\telse {\n\t\t\t\u002F\u002F Insecure pattern: MfaUserConfigBase actions in 21.0.9 did not sufficiently validate \n\t\t\t\u002F\u002F that action_data['user_id'] matched the active authenticated user session.\n\t\t\t$available = self::con()->comps->mfa->getProvidersAvailableToUser( $user );\n\t\t\t\u002F** @var Passkey $provider *\u002F\n\t\t\t$provider = $available[ Passkey::ProviderSlug() ] ?? null;\n            \u002F\u002F ... (truncated)","diff -ru \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fwp-simple-firewall\u002F21.0.9\u002Fsrc\u002Flib\u002Fsrc\u002FActionRouter\u002FActions\u002FMfaPasskeyAuthenticationStart.php \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fwp-simple-firewall\u002F21.0.10\u002Fsrc\u002Flib\u002Fsrc\u002FActionRouter\u002FActions\u002FMfaPasskeyAuthenticationStart.php\n--- \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fwp-simple-firewall\u002F21.0.9\u002Fsrc\u002Flib\u002Fsrc\u002FActionRouter\u002FActions\u002FMfaPasskeyAuthenticationStart.php\t2025-12-19 15:06:08.000000000 +0000\n+++ \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fwp-simple-firewall\u002F21.0.10\u002Fsrc\u002Flib\u002Fsrc\u002FActionRouter\u002FActions\u002FMfaPasskeyAuthenticationStart.php\t2026-01-13 13:33:30.000000000 +0000\n@@ -2,27 +2,22 @@\n \n namespace FernleafSystems\\Wordpress\\Plugin\\Shield\\ActionRouter\\Actions;\n \n-use FernleafSystems\\Wordpress\\Plugin\\Shield\\ActionRouter\\Actions\\Traits\\AuthNotRequired;\n+use FernleafSystems\\Wordpress\\Plugin\\Shield\\ActionRouter\\Exceptions\\ActionException;\n use FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\TwoFactor\\Provider\\Passkey;\n \n-class MfaPasskeyAuthenticationStart extends MfaUserConfigBase {\n-\n-\tuse AuthNotRequired;\n+class MfaPasskeyAuthenticationStart extends MfaLoginFlowBase {\n \n \tpublic const SLUG = 'mfa_passkey_auth_start';\n \n \tprotected function exec() {\n-\n \t\t$response = [\n \t\t\t'success'     => false,\n \t\t\t'page_reload' => false\n \t\t];\n \n-\t\t$user = $this->getActiveWPUser();\n-\t\tif ( empty( $user ) ) {\n-\t\t\t$response[ 'message' ] = __( 'User must be logged-in.', 'wp-simple-firewall' );\n-\t\t}\n-\t\telse {\n+\t\ttry {\n+\t\t\t$user = $this->getLoginWPUser();\n+\n \t\t\t$available = self::con()->comps->mfa->getProvidersAvailableToUser( $user );\n \t\t\t\u002F** @var Passkey $provider *\u002F\n \t\t\t$provider = $available[ Passkey::ProviderSlug() ] ?? null;\n@@ -31,19 +26,27 @@\n \t\t\t\t$response[ 'message' ] = __( \"Passkeys aren't available for this user.\", 'wp-simple-firewall' );\n \t\t\t}\n \t\t\telse {\n-\t\t\t\ttry {\n-\t\t\t\t\t$response = [\n-\t\t\t\t\t\t'success'     => true,\n-\t\t\t\t\t\t'challenge'   => $provider->startNewAuth(),\n-\t\t\t\t\t\t'page_reload' => false\n-\t\t\t\t\t];\n-\t\t\t\t}\n-\t\t\t\tcatch ( \\Exception $e ) {\n-\t\t\t\t\t$response[ 'message' ] = __( \"There was a problem preparing the Passkey Auth Challenge.\", 'wp-simple-firewall' );\n-\t\t\t\t}\n+\t\t\t\t$response = [\n+\t\t\t\t\t'success'     => true,\n+\t\t\t\t\t'challenge'   => $provider->startNewAuth(),\n+\t\t\t\t\t'page_reload' => false\n+\t\t\t\t];\n \t\t\t}\n \t\t}\n+\t\tcatch ( ActionException $e ) {\n+\t\t\t$response[ 'message' ] = $e->getMessage();\n+\t\t}\n+\t\tcatch ( \\Exception $e ) {\n+\t\t\t$response[ 'message' ] = __( 'There was a problem preparing the Passkey Auth Challenge.', 'wp-simple-firewall' );\n+\t\t}\n \n \t\t$this->response()->action_response_data = $response;\n \t}\n+\n+\tprotected function getRequiredDataKeys() :array {\n+\t\treturn [\n+\t\t\t'login_wp_user',\n+\t\t\t'login_nonce',\n+\t\t];\n+\t}\n }","To exploit this vulnerability, an attacker must first obtain an authenticated session with at least Subscriber privileges. Once logged in, the attacker extracts a valid `shield_action` nonce, which is typically found localized in the `window.shield_vars_userprofile` JavaScript object on the 'Your Profile' page. The attacker then crafts a POST request to `\u002Fwp-admin\u002Fadmin-ajax.php` with the `action` parameter set to `shield_action`, the `exec` parameter set to `mfa_google_auth_toggle`, and the `user_id` parameter set to the target user's ID (e.g., '1' for the primary administrator). Because version 21.0.9 fails to verify that the `user_id` in the request data matches the ID of the currently logged-in user, the plugin will disable Google Authenticator for the victim account, allowing the attacker to bypass 2FA in subsequent login attempts.","gemini-3-flash-preview","2026-05-05 08:43:00","2026-05-05 08:43:29",{"type":41,"vulnerable_version":42,"fixed_version":11,"vulnerable_browse":43,"vulnerable_zip":44,"fixed_browse":45,"fixed_zip":46,"all_tags":47},"plugin","21.0.9","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fwp-simple-firewall\u002Ftags\u002F21.0.9","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fwp-simple-firewall.21.0.9.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fwp-simple-firewall\u002Ftags\u002F21.0.10","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fwp-simple-firewall.21.0.10.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fwp-simple-firewall\u002Ftags"]