[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fQ1Vdq3XFxsySY63SeadhtrmKIiz05zIKnyZTAVyqLtc":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-2026-0561","shield-security-unauthenticated-reflected-cross-site-scripting-via-message-parameter","Shield Security \u003C= 21.0.8 - Unauthenticated Reflected Cross-Site Scripting via 'message' Parameter","The Shield Security plugin for WordPress is vulnerable to Reflected Cross-Site Scripting via the 'message' parameter in all versions up to, and including, 21.0.8 due to insufficient input sanitization and output escaping. This makes it possible for unauthenticated attackers to inject arbitrary web scripts in pages that execute if they can successfully trick a user into performing an action such as clicking on a link.","wp-simple-firewall",null,"\u003C=21.0.8","21.0.10","medium",6.1,"CVSS:3.1\u002FAV:N\u002FAC:L\u002FPR:N\u002FUI:R\u002FS:C\u002FC:L\u002FI:L\u002FA:N","Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')","2026-02-18 16:17:44","2026-02-19 04:36:25",[19],"https:\u002F\u002Fwww.wordfence.com\u002Fthreat-intel\u002Fvulnerabilities\u002Fid\u002Fcb49eb5f-c1ff-4440-8b53-c2515e65da27?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,"This research plan targets a reflected Cross-Site Scripting (XSS) vulnerability in the Shield Security plugin. The vulnerability arises from the unescaped reflection of the `message` query parameter on pages rendered via the plugin's `ActionRouter` system.\n\n### 1. Vulnerability Summary\n- **Vulnerability**: Unauthenticated Reflected Cross-Site Scripting (XSS).\n- **Component**: `ActionRouter` system.\n- **Variable**: `message` parameter.\n- **Cause**: The plugin's action processing logic accepts a `message` parameter from the request (GET or POST) and echoes it back in the generated HTML response (such as a block page, login page, or error notice) without sufficient sanitization or output escaping.\n- **Affected Versions**: \u003C= 21.0.8.\n\n### 2. Attack Vector Analysis\n- **Endpoint**: Any URL where the Shield `ActionRouter` is invoked. This is typically the main site index with a `shield_action` parameter or `wp-login.php`.\n- **Parameter**: `message` (GET parameter).\n- **Authentication**: None (Unauthenticated).\n- **Bypass**: Shield's `BaseAction.php` contains a logic flaw where nonces are only verified for AJAX requests. A standard browser `GET` request bypasses nonce verification.\n- **Payload**: A standard `\u003Cscript>` or event handler payload, e.g., `\u003Cscript>alert(document.domain)\u003C\u002Fscript>`.\n\n### 3. Code Flow\n1. **Entry Point**: A user navigates to `\u002F?shield_action=[ACTION_SLUG]&message=[PAYLOAD]`.\n2. **Action Initialization**: `BaseAction::__construct()` merges request data into `$this->action_data`. The `message` parameter from the URL is now in `$this->action_data['message']`.\n3. **Access Check**: `BaseAction::process()` calls `checkAccess()`.\n4. **Nonce Bypass**: In `BaseAction::isNonceVerifyRequired()`, the code checks `self::con()->this_req->wp_is_ajax`. For a direct browser `GET` request, this is `false`, and `isNonceVerifyRequired()` returns `false`, bypassing `ActionNonce::VerifyFromRequest()`.\n5. **Action Execution**: If the action (e.g., `mfa_passkey_auth_start`) uses the `AuthNotRequired` trait, it proceeds without a logged-in session.\n6. **Sink**: The action completes or throws an exception. The `ActionRouter` then renders a response page. The template logic (not provided, but inferred) retrieves the `message` from the request or the `action_data` and echoes it directly into the HTML to display a notice to the user.\n\n### 4. Nonce Acquisition Strategy\nAccording to `src\u002Flib\u002Fsrc\u002FActionRouter\u002FActions\u002FBaseAction.php`, nonces are **not required** for non-AJAX requests:\n```php\nprotected function isNonceVerifyRequired() :bool {\n    return (bool)( $this->getActionOverrides()[ Constants::ACTION_OVERRIDE_IS_NONCE_VERIFY_REQUIRED ] ?? self::con()->this_req->wp_is_ajax );\n}\n```\nIf `wp_is_ajax` is false, the check is skipped. This allows unauthenticated exploitation via a direct link. \n\nIf a specific action *does* override this and require a nonce, it can be found in the browser context:\n- **Action Slug**: `mfa_passkey_auth_start` (confirmed in source).\n- **JS Variable**: `window.shield_vars_login_2fa` or `window.shield_vars_main`.\n- **Extraction**: `browser_eval(\"window.shield_vars_main.ajax.shield_action.nonce\")` (inferred path).\n\n### 5. Exploitation Strategy\nWe will use the `mfa_passkey_auth_start","The Shield Security plugin for WordPress is vulnerable to unauthenticated reflected Cross-Site Scripting (XSS) via the 'message' parameter. This occurs because the plugin's ActionRouter system echoes the 'message' query parameter directly into the HTML response of various actions without sufficient sanitization or output escaping.","\u002F\u002F src\u002Flib\u002Fsrc\u002FActionRouter\u002FActions\u002FBaseAction.php\n\npublic function __construct( array $data = [], ?ActionResponse $response = null ) {\n    $this->action_data = $data; \u002F\u002F Request data (GET\u002FPOST) is merged directly into action_data\n    $this->response = $response instanceof ActionResponse ? $response : new ActionResponse();\n}\n\n\u002F\u002F ...\n\nprotected function isNonceVerifyRequired() :bool {\n    \u002F\u002F Nonce verification is skipped for non-AJAX (direct GET) requests by default\n    return (bool)( $this->getActionOverrides()[ Constants::ACTION_OVERRIDE_IS_NONCE_VERIFY_REQUIRED ] ?? self::con()->this_req->wp_is_ajax );\n}\n\n---\n\n\u002F\u002F src\u002Flib\u002Fsrc\u002FActionRouter\u002FActions\u002FMfaPasskeyAuthenticationStart.php\n\nclass MfaPasskeyAuthenticationStart extends MfaUserConfigBase {\n\n\tuse AuthNotRequired; \u002F\u002F Allows unauthenticated users to trigger this action\n\n\tpublic const SLUG = 'mfa_passkey_auth_start';\n\n\tprotected function exec() {\n        \u002F\u002F ... reflection of messages in response data often happens via the 'message' key","--- \u002Fsrc\u002Flib\u002Fsrc\u002FActionRouter\u002FActions\u002FBaseAction.php\n+++ \u002Fsrc\u002Flib\u002Fsrc\u002FActionRouter\u002FActions\u002FBaseAction.php\n@@ -154,14 +154,10 @@\n \t * @return self For method chaining\n \t *\u002F\n \tpublic function setActionOverride( string $overrideKey, $value ) :self {\n-\t\t\u002F\u002F Initialize action_overrides array if it doesn't exist\n-\t\tif ( !isset( $this->action_data[ 'action_overrides' ] ) ) {\n-\t\t\t$this->action_data[ 'action_overrides' ] = [];\n-\t\t}\n-\n-\t\t\u002F\u002F Set the override value\n-\t\t$this->action_data[ 'action_overrides' ][ $overrideKey ] = $value;\n-\n+\t\t$this->action_data[ 'action_overrides' ] = \\array_merge(\n+\t\t\t\\is_array( $this->action_data[ 'action_overrides' ] ?? null ) ? $this->action_data[ 'action_overrides' ] : [],\n+\t\t\t[ $overrideKey => $value ]\n+\t\t);\n \t\treturn $this;\n \t}\n \n--- \u002Fsrc\u002Flib\u002Fsrc\u002FActionRouter\u002FActions\u002FMfaPasskeyAuthenticationStart.php\n+++ \u002Fsrc\u002Flib\u002Fsrc\u002FActionRouter\u002FActions\u002FMfaPasskeyAuthenticationStart.php\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 }","The exploit targets the Shield ActionRouter system. An attacker crafts a GET request to the site's index or login page using the `shield_action` parameter to trigger a specific action that does not require authentication or nonce verification (e.g., `mfa_passkey_auth_start`). By including a malicious payload in the `message` parameter (e.g., `\u002F?shield_action=mfa_passkey_auth_start&message=\u003Cscript>alert(document.domain)\u003C\u002Fscript>`), the attacker triggers the ActionRouter logic which reflects the `message` input directly into the rendered HTML response page. Since the plugin fails to sanitize or escape this value before outputting it, the script executes in the victim's browser context. Authentication is not required if a publicly accessible action is used.","gemini-3-flash-preview","2026-04-19 02:51:14","2026-04-19 02:52:13",{"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"]