CVE Watch

Wholesale Lead Capture CVE-2026-27540: Patched in February, Attacked All Summer

· 6 min read
A storefront web page with an upload box as a red code file flies into it, beside a checklist that ticks image files and marks a code file with a red X, with a shopping cart and a warning sign

If your WooCommerce store uses Wholesale Lead Capture, check that it’s on 2.0.3.2 or later, and then check whether someone uploaded a web shell before you got there. CVE-2026-27540 let anyone on the internet upload a PHP file through the plugin’s wholesale registration upload, because the plugin let the visitor decide which file types were allowed. The fix shipped on February 20. The attacks started in June, kept coming through the summer, and Wordfence has now blocked more than 100,000 of them. Every one of those attempts was aimed at a store that had gone at least three months without updating.

At a glance

CVECVE-2026-27540
PluginWholesale Lead Capture for WooCommerce, a premium plugin by Rymera (Wholesale Suite). About 6,000 active installs
TypeUnauthenticated arbitrary file upload, leading to remote code execution
SeverityCVSS 9.8 per Wordfence, 9.0 per Patchstack. Critical either way
Affected2.0.3.1 and earlier
Fixed2.0.3.2, released February 20, 2026. Updating does not remove a file that was already uploaded
StatusExploited. More than 100,000 attempts blocked by Wordfence, with peaks June 4–17, July 1 and August 30. Patchstack lists it as a known exploited vulnerability

What’s wrong

Wholesale Lead Capture adds a registration form where would-be wholesale buyers apply for an account. Stores often ask for a document with the application, such as a resale certificate or tax-exemption form, so the form can include a file upload.

That upload goes to an AJAX action called wwlc_file_upload_handler, which anyone can call without logging in. The handler checks the file’s extension against a list of allowed types, which is the right idea. The problem is where that list comes from: a request parameter called file_settings, sent by the browser. In other words, the visitor sends both the file and the rules for checking it.

So the attack, found by researcher Teemu Saarentaus, is one request:

  1. Call the upload handler at /wp-admin/admin-ajax.php with the action wwlc_file_upload_handler. No account is needed.
  2. Send a forged file_settings that adds php to the allowed types.
  3. Attach a PHP file. The plugin checks it against the attacker’s own list, finds it allowed, and saves it.
  4. Run it. The attacker requests the file, and it runs as code on your server.

Version 2.0.3.2 stops trusting the browser’s list.

What the attacks look like

Wordfence describes the payload seen in the attacks as a file named shell.php. It reports details about the server back to the attacker and offers a browser-based upload form, so the attacker can add more files at will. On a store, that’s a starting point, not the goal. Analysts warn of what tends to follow on WooCommerce sites: new admin accounts, a copy of the customer database, and card-skimming code injected into checkout.

  • POST requests to /wp-admin/admin-ajax.php with action=wwlc_file_upload_handler. The action is usually in the request body, so many access logs only show the AJAX URL. Bursts of POSTs to it from addresses that never loaded your registration page are the pattern to look for.
  • A PHP file in wp-content/uploads/, including a shell.php, or any PHP file with a recent date. WordPress never puts code there.
  • GET requests to a .php file under /wp-content/uploads/ in your access logs. That’s the attacker using the shell.
  • Wholesale applications with no real applicant behind them, or with odd attachments.

The addresses Wordfence reported, as listed by The Hacker News:

92.241.13.213
92.241.13.140
31.59.129.150
23.137.105.214
23.180.120.140
104.194.9.138
187.75.114.36
114.10.43.203
37.114.144.209
2a0f:85c1:840:5389::1

Use them to search your logs. Blocking them doesn’t protect an unpatched store, because the attack fits in one request from anywhere.

What to do

1. Check your version

In Plugins, look at Wholesale Lead Capture’s version. With WP-CLI:

wp plugin list --fields=name,status,version | grep -i wholesale

Below 2.0.3.2 is vulnerable. This is a premium plugin, so its updates arrive through your Wholesale Suite license. If the license has lapsed, the dashboard may simply never have offered the update. Update to the latest version, or deactivate the plugin until you can.

2. Check whether you were hit

If the store ran 2.0.3.1 or earlier at any point since June, look, even if it’s up to date now:

find wp-content/uploads -type f \( -name "*.php*" -o -name "*.phtml" -o -name "*.phar" \)
find wp-content -type f -name "*.php" -newermt "2026-06-01" -not -path "*/plugins/*" -not -path "*/themes/*"
  • Files: any PHP in uploads/, and any changed file in plugins, themes or core that an update doesn’t explain.
  • Users: administrators and shop managers you didn’t create.
  • Checkout: scripts on the checkout page that you don’t recognize, in theme files, in the database (widgets, options, custom code plugins) and in the page source itself.
  • Integrations: WooCommerce REST API keys under WooCommerce → Settings → Advanced → REST API and application passwords you can’t account for. The remote-access audit lists the other places to look.
  • Scheduled tasks and .htaccess changes, both favorite places to hide persistence.

If you find a shell, treat the whole store as compromised. Preserve the logs, restore from a backup taken before the upload, change every password, log out all sessions, rotate the salts in wp-config.php, and rotate your payment gateway keys. If checkout code was changed, card data may have been taken. Talk to your payment provider about what that means for you.

3. Stop PHP running in uploads

This one setting would have turned every one of those 100,000 attempts into a harmless file. On Apache or LiteSpeed, create wp-content/uploads/.htaccess with:

# Never execute scripts from the uploads folder.
<FilesMatch "\.(php|phtml|phar|php\d)$">
  Require all denied
</FilesMatch>

On Nginx:

location ~* ^/wp-content/uploads/.*\.(php|phtml|phar|php\d)$ {
  deny all;
}

Test it by putting a small PHP file in uploads/ and requesting it. You want a 403. Then delete the test file.

Where sources disagree

  • Severity. Wordfence scores it 9.8. Patchstack and some others give 9.0.
  • Exploitation. The CISA enrichment on the CVE record still says no exploitation, which appears to predate the attacks. Wordfence’s data and Patchstack’s listing say otherwise.
  • Success rate. Wordfence counts blocked attempts. Nobody has published how many stores were actually compromised.

The bigger lesson

Two lessons, one for store owners and one for developers.

For store owners: this fix was out for more than three months before the attacks began. Unlike the bugs that get exploited on patch day, this one gave everyone plenty of time, and attackers still found enough unpatched stores to keep going all summer. Those stores weren’t running out-of-date code because the update was hard. In most cases nobody was checking, or the license that delivers updates had quietly run out. A plugin that sits on the public registration path of a store that takes payments needs someone whose job it is to keep it current.

For developers: the rules for what a request is allowed to do must never come from the request. An allowlist the client sends is not an allowlist. Keep upload rules on the server, check the real file type with wp_check_filetype_and_ext(), and store uploads where PHP can’t run them. The plugin had a check. It just asked the attacker what to check for.

Sources