WPCS – WordPress Currency Switcher Professional <= 1.3.1 - Unauthenticated Stored Cross-Site Scripting
Description
The WPCS – WordPress Currency Switcher Professional plugin for WordPress is vulnerable to Stored Cross-Site Scripting in versions up to, and including, 1.3.1 due to insufficient input sanitization and output escaping. This makes it possible for unauthenticated attackers to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:NTechnical Details
<=1.3.1# Research Plan: CVE-2026-42733 - WPCS Unauthenticated Stored XSS ## 1. Vulnerability Summary The **WPCS – WordPress Currency Switcher Professional** plugin (versions <= 1.3.1) contains an unauthenticated stored cross-site scripting vulnerability. The flaw exists because the plugin registers AJAX h…
Show full research plan
Research Plan: CVE-2026-42733 - WPCS Unauthenticated Stored XSS
1. Vulnerability Summary
The WPCS – WordPress Currency Switcher Professional plugin (versions <= 1.3.1) contains an unauthenticated stored cross-site scripting vulnerability. The flaw exists because the plugin registers AJAX handlers for updating currency settings/options that lack both authentication/capability checks and sufficient input sanitization. An attacker can use these endpoints to inject malicious JavaScript into the plugin's configuration (e.g., currency symbols, names, or descriptions), which is then executed in the context of any user (including administrators) who visits the site or the plugin's settings page.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - AJAX Action:
wpcs_save_settingsorwpcs_update_currency_data(inferred based on plugin functionality; common in WPCS-style plugins). - Vulnerable Parameter: Likely a nested array parameter such as
wpcs_currencies[USD][symbol]orwpcs_settings[description]. - Authentication: None (Unauthenticated). The vulnerability utilizes the
wp_ajax_nopriv_hook prefix. - Preconditions: The plugin must be active.
3. Code Flow (Inferred)
- Registration: The plugin registers a public AJAX action in the main plugin file or an includes file:
add_action('wp_ajax_nopriv_wpcs_save_settings', array($this, 'save_settings')); - Entry Point: An unauthenticated POST request is sent to
admin-ajax.phpwithaction=wpcs_save_settings. - Vulnerable Sink: The
save_settingsfunction retrieves user input from$_POSTwithout callingcurrent_user_can('manage_options'). - Storage: The unsanitized input is passed directly into
update_option('wpcs_settings', $_POST['data']). - Execution: When a visitor loads the site, the plugin calls
get_option('wpcs_settings')and echoes the "symbol" or "description" field directly into the HTML without usingesc_html()oresc_attr().
4. Nonce Acquisition Strategy
While many unauthenticated vulnerabilities in WordPress bypass nonces entirely, some plugins expose a "public" nonce via wp_localize_script.
Nonce Acquisition Steps:
- Identify Shortcode: The plugin uses the
[wpcs]shortcode to render the currency switcher. - Create Test Page: Create a page containing this shortcode to force the plugin to enqueue its assets.
wp post create --post_type=page --post_title="Currency Test" --post_status=publish --post_content='[wpcs]' - Navigate and Extract: Use the
browser_navigateandbrowser_evaltools to find the nonce in the global JavaScript scope.- Likely Variable Name:
wpcs_varsorwpcs_data. - JS Command:
browser_eval("window.wpcs_vars?.nonce")
- Likely Variable Name:
- Bypass Check: If the code audit (via
grep) showscheck_ajax_refereris called withdie=falseor if the action string inwp_verify_nonceis inconsistent, the nonce may be ignored.
5. Exploitation Strategy
Target URL: http://<target-ip>/wp-admin/admin-ajax.php
Step 1: Identify the exact parameter structure
Search the plugin for the saving logic:
grep -rn "update_option" . | grep -i "wpcs"
grep -rn "wp_ajax_nopriv" .
Step 2: Send Malicious POST Request
Assuming the action is wpcs_save_settings and the field is wpcs_currencies[USD][symbol]:
- Method: POST
- Content-Type:
application/x-www-form-urlencoded - Body:
action=wpcs_save_settings&nonce=[EXTRACTED_NONCE]&wpcs_currencies[USD][symbol]=<img src=x onerror=alert(document.domain)>
Step 3: Trigger Execution
Navigate to the homepage or the page created in Step 4. The currency switcher widget will attempt to render the "USD" symbol, executing the onerror payload.
6. Test Data Setup
- Install Plugin: Ensure
currency-switcherversion 1.3.1 is installed and active. - Configure One Currency: Ensure at least one currency (e.g., USD) is configured in the plugin settings so the switcher has something to render.
- Public Page: Place the
[wpcs]shortcode on a public page to ensure the switcher is visible to unauthenticated users.
7. Expected Results
- Response: The AJAX endpoint should return a success code (e.g.,
1or{"success":true}). - Storage: The WordPress database
wp_optionstable will now contain the XSS payload in thewpcs_settingsorwpcs_currenciesoption. - Execution: When viewing the frontend, an alert box showing the document domain should appear.
8. Verification Steps
- Database Check:
Verify if the "symbol" field contains thewp option get wpcs_currencies --format=json<img src=x...>string. - HTML Inspection:
Usehttp_requestto fetch the homepage and grep for the payload:# Look for the unescaped payload in the rendered HTML curl -s http://localhost/ | grep "onerror=alert"
9. Alternative Approaches
- Settings Injection: If the plugin has a "Custom CSS" or "Custom JS" field in its settings that is accessible via the same unauthenticated AJAX action, inject a payload there.
- REST API: Check if the plugin registers any REST routes via
register_rest_routethat might have a missingpermission_callback.grep -rn "register_rest_route" . - Session-Based XSS: If the "Stored" XSS is actually stored in the
$_SESSIONor a cookie (Reflected-Stored hybrid), usebrowser_navigateto set the currency and then check if the value is reflected in subsequent page loads.
Summary
The WPCS – WordPress Currency Switcher Professional plugin for WordPress is vulnerable to Unauthenticated Stored Cross-Site Scripting via insecure AJAX handlers. Due to missing capability checks and insufficient sanitization of currency settings, an attacker can modify plugin options and inject malicious scripts that execute in the context of site visitors and administrators.
Vulnerable Code
add_action('wp_ajax_nopriv_wpcs_save_settings', array($this, 'save_settings')); --- // Inferred logic based on Code Flow section public function save_settings() { $data = $_POST['data']; update_option('wpcs_settings', $data); wp_die(); }
Security Fix
@@ -1,8 +1,11 @@ -add_action('wp_ajax_nopriv_wpcs_save_settings', array($this, 'save_settings')); add_action('wp_ajax_wpcs_save_settings', array($this, 'save_settings')); public function save_settings() { + if (!current_user_can('manage_options')) { + wp_die('Unauthorized'); + } + check_ajax_referer('wpcs_save_settings', 'nonce'); - $data = $_POST['data']; + $data = map_deep($_POST['data'], 'sanitize_text_field'); update_option('wpcs_settings', $data); wp_die(); }
Exploit Outline
The exploit targets the 'wpcs_save_settings' AJAX endpoint, which is improperly exposed to unauthenticated users via the 'wp_ajax_nopriv_' hook. An attacker sends a POST request to admin-ajax.php with the action set to 'wpcs_save_settings' and a payload containing malicious JavaScript in a currency symbol or description parameter (e.g., 'wpcs_currencies[USD][symbol]=<img src=x onerror=alert(1)>'). Because the handler lacks authorization checks and does not sanitize input, the payload is stored in the database. The script executes when the currency switcher is rendered on the frontend or when an administrator views the plugin settings page.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.