Critical XSS Vulnerability in Sell BTC Plugin//Published on 2026-02-04//CVE-2025-14554

WP-FIREWALL SECURITY TEAM

Sell BTC – Cryptocurrency Selling Calculator Vulnerability

Plugin Name Sell BTC – Cryptocurrency Selling Calculator
Type of Vulnerability Cross-Site Scripting (XSS)
CVE Number CVE-2025-14554
Urgency Medium
CVE Publish Date 2026-02-04
Source URL CVE-2025-14554

Critical: Unauthenticated Stored XSS in “Sell BTC – Cryptocurrency Selling Calculator” (<= 1.5) — What WordPress Site Owners Must Do Now

Date: 4 Feb, 2026
CVE: CVE-2025-14554
Severity: Medium (CVSS 7.1)
Vulnerable versions: ≤ 1.5
Fixed in: 1.6

As a WordPress security specialist at WP-Firewall, I want to walk you through the recent vulnerability discovered in the "Sell BTC – Cryptocurrency Selling Calculator" plugin (≤ 1.5). This vulnerability allows unauthenticated attackers to store JavaScript payloads via an AJAX action called orderform_data, which can later execute in the context of users visiting pages where that data is rendered — a classic stored XSS scenario.

This post covers the technical details, risk assessment, immediate mitigations (including a ready-to-deploy WAF rule), recovery steps if you suspect compromise, and long-term hardening. I’ll also explain how WP-Firewall’s free plan can protect your site right away while you apply the plugin update.


TL;DR (If you only do one thing)

  1. Update the Sell BTC plugin to version 1.6 immediately (this is the definitive fix).
  2. If you cannot update right away, block or mitigate the vulnerable AJAX action (orderform_data) at the firewall level — a sample rule is provided below.
  3. Hunt your database and logs for injected scripts or suspicious HTML fragments and clean or restore from a clean backup.
  4. Enable runtime protection (WAF), scanning, and hardening — WP-Firewall’s free plan can provide those protections while you update.

What happened (quick technical summary)

  • The plugin exposed an AJAX endpoint handling the orderform_data action without sufficient input validation, sanitization, or authorization checks.
  • An unauthenticated attacker could send malicious payloads (e.g., <script></script>, or more practical payloads) as field values in requests to the orderform_data AJAX action.
  • The plugin persisted this payload to the database (stored XSS). When that stored value is later output onto a page without proper escaping, the browser executes the injected JavaScript in the context of the site.
  • The vulnerability was assigned CVE-2025-14554 and is fixed in version 1.6.

Why this matters — real-world impact

Stored XSS can be used to:

  • Steal authentication cookies or session tokens (leading to account takeover).
  • Perform actions on behalf of an administrative user (if the payload triggers when an admin views the page).
  • Inject cryptojacking, phishing forms, or content defacement that harms visitors and site reputation.
  • Plant persistent backdoors — e.g., uploading additional malicious JavaScript that persists and evades simple cleanup.
  • Pivot to other parts of the site (e.g., call internal AJAX endpoints that require logged-in context).

Even though the initial attack is unauthenticated, the real danger is the payload being executed in another user’s browser — possibly an administrator.


Who is at risk

  • Sites running the vulnerable plugin (versions ≤ 1.5).
  • Sites exposing AJAX endpoints publicly (standard WordPress sites do).
  • Sites where plugin output is displayed to administrators or visitors without escaping.

If you host multiple WordPress sites, treat this as a high-priority remediation.


Reproduction (high level)

An attacker sends an HTTP POST to admin-ajax.php like:

POST /wp-admin/admin-ajax.php
Content-Type: application/x-www-form-urlencoded

action=orderform_data&field_name=<script></script>&other=...

If the plugin stores field_name and later outputs it in an HTML page as-is or with insufficient escaping, the script runs in the visitor’s browser.

Note: I won’t provide a copy-paste exploit payload for ethical and safety reasons, but the above illustrates the vector.


Immediate actions (step-by-step)

  1. Update the plugin to version 1.6 right now. This releases a permanent fix in the plugin code.
    • If your site is managed by someone else, contact them and insist on the update.
  2. If you cannot update immediately:
    • Block requests to the vulnerable AJAX action at the edge (WAF) — see sample rules below.
    • Restrict access to admin-ajax.php so that only known origins can use it for that specific action.
    • Disable the plugin temporarily until you can update.
  3. Scan for injected content:
    • Search the database for suspicious tokens: <script, onerror=, javascript:, document.cookie, common obfuscated patterns.
    • Search in wp_posts, wp_postmeta, wp_options, and any plugin-specific tables that may store form submissions.
  4. Review access logs for unusual POSTs to admin-ajax.php and any requests containing suspicious characters.
  5. Rotate admin credentials if you find evidence of admin-side interaction after the vulnerability was introduced.
  6. Monitor and quarantine suspicious accounts.

WP-Firewall mitigation rule (sample)

Below is a generic WAF rule concept you can apply if your firewall supports custom rules (most modern WAFs do). This rule blocks POST requests to admin-ajax.php where action=orderform_data and the request body contains script-like payloads.

Note: Adapt the syntax to your firewall engine — examples are illustrative and intentionally conservative.

Example ModSecurity-style rule (conceptual):

SecRule REQUEST_URI "@beginsWith /wp-admin/admin-ajax.php" "phase:1,chain,deny,log,msg:'Block orderform_data XSS attempt',tag:wp-firewall,severity:2"
    SecRule &REQUEST_METHOD "@eq 1" "chain"
    SecRule ARGS_POST:action "@pm ^orderform_data$" "chain"
    SecRule ARGS|ARGS_POST|REQUEST_BODY "(

Simpler regex-based block (if you can only match body + action):

if request.path == '/wp-admin/admin-ajax.php' and request.method == 'POST':
    if 'action=orderform_data' in request.body:
        if re.search(r'(<script|javascript:|onerror=|onload=|document\.cookie|window\.location|eval\()', request.body, re.I):
            block_request()

WAF rules should:

  • Block or challenge (CAPTCHA) suspicious requests.
  • Log full request body for hunting.
  • Temporarily whitelist legitimate traffic if false positives occur.

If you use WP-Firewall, enable auto mitigation/per-rule blocking for AJAX actions and set a rule to block requests to action=orderform_data with script patterns.


Quick hardening steps you can apply in minutes

If you can’t update the plugin immediately, consider the following short-term mitigations:

  1. Block access to admin-ajax.php for unauthenticated users (careful — some plugins rely on it for front-end functionality).
    • Example nginx rule to block POSTs with action=orderform_data from anonymous origins.
  2. Add a .htaccess rule to deny admin-ajax.php requests that include the vulnerable action parameter:
    <IfModule mod_rewrite.c>
      RewriteCond %{REQUEST_URI} ^/wp-admin/admin-ajax.php$
      RewriteCond %{REQUEST_METHOD} POST
      RewriteCond %{QUERY_STRING} action=orderform_data [OR]
      RewriteCond %{REQUEST_BODY} action=orderform_data
      RewriteRule .* - [F,L]
    </IfModule>
    
  3. Apply a Content Security Policy (CSP) as a defense-in-depth:
    • Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-...';
      CSP doesn’t fix stored XSS but can reduce the impact of injected scripts.
  4. Harden HTTP headers:
    • X-Content-Type-Options: nosniff
    • X-Frame-Options: SAMEORIGIN
    • Referrer-Policy: no-referrer-when-downgrade
    • Ensure cookies are HttpOnly, Secure, and have appropriate SameSite attributes.

How to find stored payloads — hunting queries

Search common WordPress storage locations. Run these queries against your database (adjust table prefixes):

Find <script fragments:

SELECT 'wp_posts' AS table_name, ID, post_title
FROM wp_posts
WHERE post_content LIKE '%<script%';

SELECT 'wp_postmeta' AS table_name, post_id, meta_key
FROM wp_postmeta
WHERE meta_value LIKE '%<script%';

SELECT 'wp_options' AS table_name, option_name
FROM wp_options
WHERE option_value LIKE '%<script%';

Search for event handlers or JS keywords:

SELECT 'wp_posts' AS table_name, ID
FROM wp_posts
WHERE post_content LIKE '%onerror=%' OR post_content LIKE '%javascript:%' OR post_content LIKE '%document.cookie%';

Check plugin-specific tables. If the plugin uses its own table(s), inspect them for fields that might store order form data:

-- Example: find columns storing user-submitted data
SELECT table_name, column_name
FROM information_schema.columns
WHERE table_schema = DATABASE()
  AND (column_name LIKE '%order%' OR column_name LIKE '%form%' OR column_name LIKE '%data%');

Backups: If you find a stored payload and cannot safely remove it, restore the affected posts/options from a clean backup.


If you find a stored malicious payload — cleanup checklist

  1. Take the site into maintenance mode if the payload appears to execute in the front-end.
  2. Backup current site and database (for forensic analysis).
  3. Remove the malicious content manually (or replace with clean backup content).
  4. Update plugin to 1.6 and other plugins/themes/core to latest versions.
  5. Rotate all admin passwords and any API credentials that could be leaked.
  6. Invalidate sessions and force password resets for all administrative accounts.
  7. Review change logs, recent file modifications, and uploads for web shells or other planted files:
    • Use find to locate recently changed files: find . -mtime -14 -type f -print
  8. Scan the site for web shells or suspicious PHP files; remove and validate.
  9. Re-examine server logs for lateral movement or data exfiltration.
  10. Perform a full malware scan and file integrity checks (WP-Firewall includes scanning features on the free plan).

Secure coding guidance for plugin developers (how the fix should look)

If you maintain custom plugins or theme code, apply these principles:

  1. Authorization: Don’t expose sensitive AJAX actions to unauthenticated users. Add capability checks:
if ( ! is_user_logged_in() ) {
    wp_send_json_error( 'Authentication required.', 403 );
}

or, at minimum, use capability checks:

if ( ! current_user_can( 'edit_posts' ) ) {
    wp_send_json_error( 'Insufficient privileges', 403 );
}
  1. Nonces: Require and validate nonces for AJAX POSTs:
check_ajax_referer( 'my_action_nonce', 'security' );
  1. Sanitize inputs before database storage:
$clean_field = wp_kses_post( $_POST['field_name'] ); // allows safe HTML, strips scripts
$plain_text = sanitize_text_field( $_POST['username'] ); // for plain text
  1. Escape on output: Use esc_html(), esc_attr(), esc_js() based on context:
echo esc_html( $stored_text ); // HTML body text context
echo esc_attr( $stored_attr ); // attribute context
  1. Prefer JSON encoding for data passed to JS:
$payload = wp_json_encode( $data );
  1. Avoid storing untrusted HTML if you don’t need it. Use structured fields (e.g., JSON key/value) that are processed safely on render.

A minimal safe AJAX handler:

add_action('wp_ajax_my_secure_action', 'my_secure_action_handler');
add_action('wp_ajax_nopriv_my_secure_action', 'my_secure_action_handler');

function my_secure_action_handler() {
    check_ajax_referer( 'my_action_nonce', 'security' );

    $field = isset($_POST['field']) ? wp_kses_post( $_POST['field'] ) : '';

    // capability check depending on what the action does
    if ( ! current_user_can('edit_posts') ) {
        wp_send_json_error( 'No permission', 403 );
    }

    // store sanitized data
    update_option( 'my_plugin_field', $field );

    wp_send_json_success( array( 'status' => 'saved' ) );
}

Detecting exploitation via logs and monitoring

  • Look for repeated POSTs to /wp-admin/admin-ajax.php containing action=orderform_data.
  • Watch for requests including <script or suspicious keywords in request bodies.
  • Monitor for subsequent user logins or actions by administrative accounts around the same timestamps.
  • Enable WAF alerts for blocked/handled hits against the rule described earlier.

Add monitoring for file changes and admin user changes (new accounts, role escalations). WP-Firewall’s scan and alerting can detect many of these patterns and notify you immediately.


Longer-term mitigation and best practices

  • Always keep plugins and themes updated; apply critical updates promptly.
  • Run a WAF with rules for common WordPress AJAX endpoints and known vulnerable actions.
  • Use a process for staging and testing updates, but ensure critical security updates are applied quickly in production.
  • Implement a backup and incident response plan:
    • Daily backups, verified restores.
    • Incident runbook for triage and remediation.
  • Apply least privilege for admin users and remove unused admin accounts.
  • Use two-factor authentication for administrator accounts.
  • Use Content Security Policy and other HTTP security headers.
  • Periodically scan your website for vulnerabilities and malware.

If your site was compromised — incident response checklist

  1. Isolate: Put the site into maintenance mode or limit public access until cleanup.
  2. Evidence: Preserve logs and a copy of the compromised site for forensics.
  3. Clean: Restore from a known-good backup or manually remove malicious artifacts.
  4. Update: Bring WordPress core, plugins (including Sell BTC to 1.6), and themes fully up to date.
  5. Harden: Apply WP-Firewall protections and other hardening measures.
  6. Reissue secrets: Reset admin passwords & API keys; rotate database and hosting credentials if necessary.
  7. Re-audit: Conduct a full scan and verify no web shells or persistent backdoors remain.
  8. Monitor: Increase monitoring for suspicious activity in the weeks following remediation.

Example indicators to search for in files/logs

  • Files with suspicious timestamps or with code such as eval(, base64_decode(, gzinflate(, system(, exec(.
  • Requests with payloads containing action=orderform_data together with <script, onerror=, or document.cookie.
  • Unexpected new admin users or changes to options like siteurl, home, or plugin options.

Why a WAF matters (and how it helps now)

A Web Application Firewall provides an important layer of defense:

  • Blocks malicious requests before they hit PHP — preventing storage of malicious data.
  • Provides virtual patching: when a plugin vulnerability is known but a patch can’t be applied immediately, WAF rules can block exploit attempts.
  • Logs and alerts suspicious activity (useful for incident response).
  • Offers reputation-based blocking and rate limiting.

While code fixes are the only true remediation, a WAF buys you time and prevents many automated attacks.


Start protecting now — free plan designed to stop attacks while you update

Start Protecting for Free Today

If you want immediate, managed protection while you update the plugin, WP-Firewall offers a Basic (Free) plan that includes essential protections:

  • Managed firewall and WAF rules to block known exploit patterns.
  • Unlimited bandwidth through the firewall.
  • Malware scanner to detect suspicious files and payloads.
  • Mitigation geared to the OWASP Top 10 risks.

Sign up for the free plan at: https://my.wp-firewall.com/buy/wp-firewall-free-plan/ and enable the WAF to block attempts targeting orderform_data and similar AJAX vectors. If you need more automation (automatic malware removal, blacklist/whitelist control) consider upgrading to Standard or Pro plans.

Plan snapshot:

  • Basic (Free): Managed firewall, unlimited bandwidth, WAF, malware scanner, OWASP Top 10 mitigations.
  • Standard ($50/year): + Automatic malware removal, IP blacklist/whitelist (20 entries).
  • Pro ($299/year): + Monthly security reports, auto virtual patching, and premium add-ons.

The WP-Firewall free tier is an excellent short-term safety net while you perform updates and cleanups.


Final recommendations (prioritized)

  1. Update Sell BTC plugin to 1.6 right away. This is the single most important step.
  2. If you can’t update immediately:
    • Enable WP-Firewall’s WAF and apply a rule to block requests to action=orderform_data that contain script-like payloads.
    • Consider temporarily disabling the plugin until updated.
  3. Hunt and clean any stored payloads in your database and files.
  4. Rotate credentials and harden admin access.
  5. Keep monitoring and scanning in place after cleanup.

Appendix — additional helpful commands & rules

  • Find recent edits on server:
    # find files modified in last 7 days
    find /var/www/html -type f -mtime -7 -print
  • Grep your logs for suspicious AJAX posts:
    grep "admin-ajax.php" /var/log/nginx/access.log | grep "orderform_data" | tail -n 50
  • SQL snippet to locate suspicious admin-ajax hits in access logs if logged to DB:
    SELECT * FROM access_logs
    WHERE request_uri LIKE '%admin-ajax.php%' AND request_body LIKE '%orderform_data%';
  • Example of a stricter CSP header snippet (add to web server config):
    Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-<random>'; object-src 'none'; base-uri 'self';

If you need help applying any of the rules above, hunting for stored payloads, or verifying your cleanup, WP-Firewall’s support team and scanners can assist. Protecting your site proactively reduces downtime, prevents visitor harm, and keeps your brand trusted.

Stay safe,
A WordPress security specialist — WP-Firewall


wordpress security update banner

Receive WP Security Weekly for Free 👋
Signup Now
!!

Sign up to receive WordPress Security Update in your inbox, every week.

We don’t spam! Read our privacy policy for more info.