AffiliateX 1.0.0 - 1.3.9.3 - Authenticated (Subscriber+) Missing Authorization to Stored Cross-Site Scripting via save_customization_settings
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:NTechnical Details
>=1.0.0 <=1.3.9.3Source Code
WordPress.org SVN# 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 viawp_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
- Registration: The plugin registers the AJAX action in the admin initialization phase:
add_action( 'wp_ajax_save_customization_settings', [ $class, 'save_customization_settings' ] ); - Handler Execution: The
save_customization_settingsfunction is called. It likely performs a nonce check usingcheck_ajax_referer()but fails to verify the user's capabilities. - Data Storage: The function retrieves user-supplied data from
$_POSTand saves it usingupdate_option()(e.g.,update_option( 'affiliatex_customization_settings', ... )) without proper sanitization/escaping of HTML/Script tags. - 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.
- 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. - Identification:
- Search for
wp_localize_scriptin the plugin source to find the variable name. - Inferred Variable Name:
affiliatex_admin_objoraffiliatex_vars. - Inferred Nonce Key:
nonceorsecurity.
- Search for
- 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).
- Action String: The nonce is likely created for the action
save_customization_settings.
5. Exploitation Strategy
- 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.
- Use
- Data Setup:
- Create a Subscriber user.
- Create a public post containing an AffiliateX block (e.g.,
[affiliatex_pros_cons]).
- Payload Construction:
- Construct a JSON payload or POST string that includes the XSS:
<script>alert(document.domain)</script>.
- Construct a JSON payload or POST string that includes the XSS:
- Execution:
- Authenticate as the Subscriber via the
http_requesttool to get cookies. - Use
http_requestto send a POST toadmin-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
settingsmust be verified in the source code.) - Authenticate as the Subscriber via the
- 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.phprequest should return a200 OKresponse, likely with a JSON body:{"success": true}. - The
affiliatex_customization_settings(or equivalent) option in thewp_optionstable will contain the<script>payload. - Upon visiting the frontend post, a JavaScript alert will execute.
8. Verification Steps
- 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). - Frontend Check: Use the
http_requesttool to fetch the frontend page andgrepfor 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
settingsis not a single array, try injecting into specific fields likebutton_textorlabel_colorif 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.
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
@@ -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.