CVE-2025-13859

AffiliateX 1.0.0 - 1.3.9.3 - Authenticated (Subscriber+) Missing Authorization to Stored Cross-Site Scripting via save_customization_settings

mediumMissing Authorization
6.4
CVSS Score
6.4
CVSS Score
medium
Severity
1.4.0
Patched in
1d
Time to patch

Description

The AffiliateX – Amazon Affiliate Plugin plugin for WordPress is vulnerable to unauthorized modification of data due to a missing capability check on the save_customization_settings AJAX action in versions 1.0.0 to 1.3.9.3. This makes it possible for authenticated attackers, with Subscriber-level access and above, to store arbitrary JavaScript that executes whenever an AffiliateX block renders on the site.

CVSS Vector Breakdown

CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Changed
Low
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions>=1.0.0 <=1.3.9.3
PublishedJanuary 15, 2026
Last updatedJanuary 15, 2026
Affected pluginaffiliatex

Source Code

WordPress.org SVN
Research Plan
Unverified

# Research Plan: CVE-2025-13859 AffiliateX Missing Authorization to Stored XSS ## 1. Vulnerability Summary The **AffiliateX** plugin (versions 1.0.0 - 1.3.9.3) is vulnerable to **Stored Cross-Site Scripting (XSS)** via the `save_customization_settings` AJAX action. The vulnerability exists because …

Show full research plan

Research Plan: CVE-2025-13859 AffiliateX Missing Authorization to Stored XSS

1. Vulnerability Summary

The AffiliateX plugin (versions 1.0.0 - 1.3.9.3) is vulnerable to Stored Cross-Site Scripting (XSS) via the save_customization_settings AJAX action. The vulnerability exists because the plugin registers an AJAX handler that lacks a capability check (e.g., current_user_can( 'manage_options' )), allowing any authenticated user—including those with Subscriber permissions—to update plugin settings. These settings are subsequently rendered on the frontend when AffiliateX blocks are displayed, leading to arbitrary JavaScript execution in the context of site visitors and administrators.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • Action: save_customization_settings (hooked via wp_ajax_save_customization_settings)
  • Vulnerable Parameter: Likely a POST parameter named settings, custom_settings, or individual fields within the customization payload.
  • Authentication: Required (Subscriber level or higher).
  • Preconditions:
    • The attacker must be logged in as a Subscriber.
    • A valid WordPress nonce for the action must be obtained (usually exposed in the admin dashboard for all logged-in users).
    • At least one AffiliateX block must be present or added to a post/page to trigger the XSS.

3. Code Flow

  1. Registration: The plugin registers the AJAX action in the admin initialization phase:
    add_action( 'wp_ajax_save_customization_settings', [ $class, 'save_customization_settings' ] );
  2. Handler Execution: The save_customization_settings function is called. It likely performs a nonce check using check_ajax_referer() but fails to verify the user's capabilities.
  3. Data Storage: The function retrieves user-supplied data from $_POST and saves it using update_option() (e.g., update_option( 'affiliatex_customization_settings', ... )) without proper sanitization/escaping of HTML/Script tags.
  4. Frontend Rendering: When a post containing an AffiliateX block (e.g., "Pros and Cons", "Comparison Table") is viewed, the plugin retrieves the saved option via get_option() and echoes it into the page source (often within a <script> or <style> block, or as an attribute), triggering the stored XSS.

4. Nonce Acquisition Strategy

The save_customization_settings action is used in the plugin's administration/customization interface. WordPress plugins typically localize nonces into the page source for their JavaScript components.

  1. Access Point: Subscribers can access /wp-admin/profile.php. The plugin likely enqueues its admin scripts on all admin pages or specifically on a customization page that Subscribers might reach if authorization is missing.
  2. Identification:
    • Search for wp_localize_script in the plugin source to find the variable name.
    • Inferred Variable Name: affiliatex_admin_obj or affiliatex_vars.
    • Inferred Nonce Key: nonce or security.
  3. Extraction:
    • Log in as a Subscriber.
    • Navigate to /wp-admin/profile.php.
    • Execute: browser_eval("window.affiliatex_admin_obj?.nonce") (Replace with the actual variable discovered during initial recon).
  4. Action String: The nonce is likely created for the action save_customization_settings.

5. Exploitation Strategy

  1. Reconnaissance:
    • Use grep -r "save_customization_settings" . to find the handler and identify the exact parameter name used for the settings data.
    • Identify the nonce action string and the JS localization key.
  2. Data Setup:
    • Create a Subscriber user.
    • Create a public post containing an AffiliateX block (e.g., [affiliatex_pros_cons]).
  3. Payload Construction:
    • Construct a JSON payload or POST string that includes the XSS: <script>alert(document.domain)</script>.
  4. Execution:
    • Authenticate as the Subscriber via the http_request tool to get cookies.
    • Use http_request to send a POST to admin-ajax.php.
    POST /wp-admin/admin-ajax.php HTTP/1.1
    Content-Type: application/x-www-form-urlencoded
    
    action=save_customization_settings&security=[NONCE]&settings[custom_js]=<script>alert(1)</script>
    

    (Note: The exact structure of settings must be verified in the source code.)

  5. Trigger:
    • Navigate to the frontend page containing the AffiliateX block to confirm the alert fires.

6. Test Data Setup

  • User: wp user create attacker attacker@example.com --role=subscriber --user_pass=password
  • Content:
    wp post create --post_type=post --post_status=publish --post_title="Affiliate Review" --post_content='<!-- wp:affiliatex/pros-cons -->[affiliatex_pros_cons]<!-- /wp:affiliatex/pros-cons -->'
    
  • Settings: Ensure the plugin is active and at least one default block is configured.

7. Expected Results

  • The admin-ajax.php request should return a 200 OK response, likely with a JSON body: {"success": true}.
  • The affiliatex_customization_settings (or equivalent) option in the wp_options table will contain the <script> payload.
  • Upon visiting the frontend post, a JavaScript alert will execute.

8. Verification Steps

  1. DB Check: Use WP-CLI to verify the option was changed:
    wp option get affiliatex_customization_settings (Verify the exact option name in the code).
  2. Frontend Check: Use the http_request tool to fetch the frontend page and grep for the payload in the HTML source.
    http_request GET /affiliate-review/ | grep "<script>alert(1)</script>"

9. Alternative Approaches

  • CSS Injection: If the plugin only allows "Custom CSS", use an XSS payload inside a CSS context: body { background-image: url("javascript:alert(1)"); } or using -webkit-mask.
  • Different Parameters: If settings is not a single array, try injecting into specific fields like button_text or label_color if they are echoed without escaping.
  • REST API: Check if the plugin also exposes this functionality via a REST API endpoint (e.g., /wp-json/affiliatex/v1/settings) which might have the same missing authorization.
Research Findings
Static analysis — not yet PoC-verified

Summary

The AffiliateX plugin for WordPress (versions 1.0.0 - 1.3.9.3) fails to perform authorization checks on its 'save_customization_settings' AJAX action. This allows authenticated users with low-level permissions, such as Subscribers, to modify plugin settings and inject arbitrary JavaScript that executes when site visitors view posts containing AffiliateX blocks.

Vulnerable Code

// File: includes/Ajax/Ajax.php (approximate path)

public function __construct() {
    // Registration of the vulnerable AJAX action
    add_action( 'wp_ajax_save_customization_settings', [ $this, 'save_customization_settings' ] );
}

/**
 * Vulnerable handler lacking capability checks
 */
public function save_customization_settings() {
    // Check for nonce, but NO check for user capabilities (e.g., current_user_can('manage_options'))
    check_ajax_referer( 'affiliatex_nonce', 'security' );

    if ( isset( $_POST['settings'] ) ) {
        $settings = $_POST['settings'];
        // The data is saved directly to the database without proper sanitization for JS/HTML
        update_option( 'affiliatex_customization_settings', $settings );
    }

    wp_send_json_success();
}

Security Fix

--- a/includes/Ajax/Ajax.php
+++ b/includes/Ajax/Ajax.php
@@ -10,6 +10,10 @@
 	public function save_customization_settings() {
 		check_ajax_referer( 'affiliatex_nonce', 'security' );
 
+		if ( ! current_user_can( 'manage_options' ) ) {
+			wp_send_json_error( array( 'message' => 'Unauthorized access.' ) );
+		}
+
 		if ( isset( $_POST['settings'] ) ) {
-			$settings = $_POST['settings'];
+			$settings = map_deep( $_POST['settings'], 'sanitize_text_field' );
 			update_option( 'affiliatex_customization_settings', $settings );
 		}

Exploit Outline

The exploit targets the 'save_customization_settings' AJAX endpoint, which is accessible to any authenticated user. An attacker follows these steps: 1. Authenticate as a Subscriber-level user. 2. Extract the security nonce for the 'save_customization_settings' action, typically found in the global JavaScript objects (e.g., affiliatex_admin_obj) localized by the plugin on admin pages like /wp-admin/profile.php. 3. Send a POST request to /wp-admin/admin-ajax.php with the 'action' parameter set to 'save_customization_settings', the 'security' parameter set to the extracted nonce, and a 'settings' parameter containing a malicious payload like '<script>alert(document.domain)</script>'. 4. The malicious script is saved in the 'affiliatex_customization_settings' option. 5. Any visitor, including administrators, who views a page containing an AffiliateX block (such as a 'Pros and Cons' or 'Comparison Table' block) will trigger the stored JavaScript execution.

Check if your site is affected.

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