Shield Security <= 21.0.8 - Cross-Site Request Forgery to SQL Injection
Description
The Shield Security plugin for WordPress is vulnerable to Cross-Site Request Forgery in all versions up to, and including, 21.0.8. This is due to the plugin allowing nonce verification to be bypassed via user-supplied parameter in the 'isNonceVerifyRequired' function. This makes it possible for unauthenticated attackers to execute SQL injection attacks, extracting sensitive information from the database, via a forged request granted they can trick a site administrator into performing an action such as clicking on a link.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:NTechnical Details
<=21.0.8What Changed in the Fix
Changes introduced in v21.0.10
Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2026-0722 (Shield Security Nonce Bypass to SQLi) ## 1. Vulnerability Summary The Shield Security plugin (up to 21.0.8) contains a vulnerability where the core `ActionRouter` mechanism allows for a total bypass of CSRF (nonce) protection. This occurs in `src/lib/src…
Show full research plan
Exploitation Research Plan: CVE-2026-0722 (Shield Security Nonce Bypass to SQLi)
1. Vulnerability Summary
The Shield Security plugin (up to 21.0.8) contains a vulnerability where the core ActionRouter mechanism allows for a total bypass of CSRF (nonce) protection. This occurs in src/lib/src/ActionRouter/Actions/BaseAction.php, specifically within the isNonceVerifyRequired() method.
The method checks a user-controlled parameter action_overrides to decide whether to skip nonce verification. By setting a specific key (is_nonce_verify_required) to false in the request, an attacker can disable the nonce check for any action. When this bypass is applied to actions that perform unsafe database queries, it results in a CSRF-to-SQL Injection vulnerability, allowing attackers to manipulate or extract data from the WordPress database.
2. Attack Vector Analysis
- Endpoint:
wp-admin/admin-ajax.php - AJAX Action:
shield_action - Action Slug:
audit_trail_table_data(or any action that handles database-backed tables). - Vulnerable Parameter:
action_data[action_overrides][is_nonce_verify_required] - SQLi Parameter:
action_data[table_data][order][0][dir](commonly vulnerable in Shield's DataTables implementations). - Authentication: Required (Subscriber or higher, typically Administrator for sensitive data extraction). The "unauthenticated" aspect in the description refers to the ability to bypass the nonce check without possessing a valid nonce, but the target action still enforces capability checks (e.g.,
manage_options).
3. Code Flow
- Entry: A POST request is sent to
admin-ajax.php?action=shield_action. - Routing: The
ActionRouteridentifies the target action class via theaction_slugparameter. - Initialization: The action class is instantiated, and
BaseAction::__construct()populates$this->action_datafrom the request body. - Access Check:
BaseAction::process()callscheckAccess(). - Bypass Logic:
checkAccess()callsisNonceVerifyRequired().isNonceVerifyRequired()(Line 99 ofBaseAction.php) returns the value ofaction_overrides[is_nonce_verify_required].- If the attacker provides
action_data[action_overrides][is_nonce_verify_required]=0, the method returnsfalse.
- Verification Skip: The condition
if ( $this->isNonceVerifyRequired() && !ActionNonce::VerifyFromRequest() )(Line 94) evaluates tofalse, skippingActionNonce::VerifyFromRequest(). - Sink: The action's
exec()method is called. ForAuditTrailTableAction, it processes sorting parameters and passes them to a database query builder, which may lack proper sanitization for theorderdirection (ASC/DESC).
4. Nonce Acquisition Strategy (Bypass)
The vulnerability is a nonce bypass. No valid nonce is required.
- Override Key: The
Constants::ACTION_OVERRIDE_IS_NONCE_VERIFY_REQUIREDidentifier corresponds to the stringis_nonce_verify_required. - Mechanism: The attacker simply includes
action_data[action_overrides][is_nonce_verify_required]=0in the POST body to satisfy the conditional check inBaseAction::isNonceVerifyRequired().
5. Exploitation Strategy
We will perform a time-based blind SQL injection via a CSRF-style request.
Step 1: Prove Nonce Bypass (CSRF)
Send a request to a sensitive action without a nonce but with the override. If the response is not a 403 Invalid Nonce, the bypass works.
Step 2: SQL Injection (Time-based)
We target the audit_trail_table_data action, which is used to render the activity logs.
Payload:
- Action:
shield_action - Action Slug:
audit_trail_table_data - Override:
action_data[action_overrides][is_nonce_verify_required]=0 - SQLi: We inject into the
dirparameter of the sorting array.
HTTP Request (Action Router):
POST /wp-admin/admin-ajax.php HTTP/1.1
Content-Type: application/x-www-form-urlencoded
action=shield_action&action_slug=audit_trail_table_data&action_data[action_overrides][is_nonce_verify_required]=0&action_data[table_data][order][0][column]=1&action_data[table_data][order][0][dir]=ASC AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)
6. Test Data Setup
- Plugin Installation: Install Shield Security <= 21.0.8.
- Audit Trail: Ensure the "Logging" (Audit Trail) module is enabled in Shield settings (it is usually enabled by default).
- Administrator Session: The exploit agent must have a valid Administrator session cookie to perform the CSRF against the
manage_optionsrestricted action.
7. Expected Results
- Success (Vulnerable): The server takes ~5 seconds to respond.
- Failure (Patched): The server responds immediately with a
403 ForbiddenorInvalid Action Nonceerror because theis_nonce_verify_requiredoverride is no longer respected from user input in 21.0.10.
8. Verification Steps
- Response Time: Confirm the delay in
http_requestmatches theSLEEP()duration. - Audit Log Check: Use WP-CLI to confirm the action was attempted:
wp shield audit_trail --limit=5 - Database State: Confirm the SQLi can extract data (e.g., database version):
# Payload for version extraction (Error-based if display_errors is on) # ...dir=ASC AND (SELECT 1 FROM (SELECT COUNT(*),CONCAT(0x7e,VERSION(),0x7e,FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.PLUGINS GROUP BY x)a)
9. Alternative Approaches
If audit_trail_table_data is unavailable, target:
- Action Slug:
render_ip_analysis - Action Slug:
render_scans_results - Payload: If
SLEEPis blocked by the Shield Firewall, use a boolean-based injection by manipulating theordercolumn index and observing the sort order of the returned JSON data.
Note on CSRF Execution: Since this is a CSRF-to-SQLi, the http_request must include the Admin's cookies. In a real-world scenario, the attacker would host an auto-submitting HTML form on their site and trick the admin into visiting it. For the PoC, direct authenticated requests using the http_request tool suffice to demonstrate the vulnerability of the endpoint.
Summary
The Shield Security plugin for WordPress is vulnerable to a nonce bypass in its ActionRouter mechanism, which allows attackers to disable CSRF protection by including a specific override parameter in the request. This bypass can be leveraged by unauthenticated attackers to execute SQL injection attacks via forged requests (CSRF) targeting administrative actions that handle database queries, such as audit trail rendering.
Vulnerable Code
// src/lib/src/ActionRouter/Actions/BaseAction.php /** * @throws InvalidActionNonceException * @throws IpBlockedException * @throws SecurityAdminRequiredException * @throws UserAuthRequiredException */ protected function checkAccess() { // ... (truncated) if ( $this->isNonceVerifyRequired() && !ActionNonce::VerifyFromRequest() ) { throw new InvalidActionNonceException( 'Invalid Action Nonce Exception.' ); } } --- // src/lib/src/ActionRouter/Actions/BaseAction.php protected function isNonceVerifyRequired() :bool { return (bool)( $this->getActionOverrides()[ Constants::ACTION_OVERRIDE_IS_NONCE_VERIFY_REQUIRED ] ?? self::con()->this_req->wp_is_ajax ); } protected function getActionOverrides() :array { return $this->action_data[ 'action_overrides' ] ?? []; }
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 targets the Shield Security ActionRouter by bypassing CSRF protection and injecting malicious SQL into table-sorting parameters. 1. **Endpoint**: The attack hits `wp-admin/admin-ajax.php?action=shield_action`. 2. **Nonce Bypass**: The attacker includes the parameter `action_data[action_overrides][is_nonce_verify_required]=0` in a POST request. The `BaseAction::isNonceVerifyRequired()` method reads this directly from user input and returns `false`, causing the router to skip `ActionNonce::VerifyFromRequest()`. 3. **SQL Injection Sink**: The attacker targets an action that processes database-backed tables, such as `audit_trail_table_data`. 4. **Payload**: The SQL injection is placed in the sorting direction parameter: `action_data[table_data][order][0][dir]`. An attacker can use a time-based blind payload (e.g., `ASC AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)`). 5. **Authentication**: While the bypass allows unauthenticated nonce validation, the target action still requires an active session with appropriate privileges (e.g., `manage_options`). Therefore, the attack is typically executed as a CSRF, where a logged-in administrator is tricked into submitting the malicious request via a cross-site link or form.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.