Almost every WordPress security story a small business owner has ever been told follows the same script: a plugin nobody has updated since 2021 turns out to have a hole in it, and the fix is to update the plugin or delete it. That script has been broadly accurate for a decade, and it is the reason “keep your plugins patched” is the entire security advice most site owners have ever received.
July 2026 broke the script. The vulnerability was in WordPress core. It worked on a default install with no plugins, no theme, and no logged-in user.
What wp2shell actually is
Two bugs, and the interesting part is the join.
CVE-2026-63030 is a route confusion flaw in the REST API batch endpoint at /wp-json/batch/v1. WordPress validated the queued sub-requests in one loop and executed them in a second loop, and the two loops could be knocked out of step. Once desynchronised, every subsequent sub-request dispatches under the wrong handler, which means a request gets executed with validation that belonged to a different request. It was reported by Adam Kues of Assetnote, part of Searchlight Cyber.
CVE-2026-60137 is a SQL injection: WordPress did not properly sanitise the author__not_in parameter of WP_Query. On its own this one is close to harmless, because core never passes untrusted input to that parameter. It only becomes exploitable if a plugin or theme hands user input to it. Reported by TF1T, dtro and haongo.
Chained, the batch desynchronisation is what supplies the untrusted input that the SQL injection needs, without any plugin being involved at all. The result is pre-authentication UNION-based SQL injection, which the public exploits turn into an administrator account and then a plugin upload, which is remote code execution. Searchlight Cyber demonstrated it against a stock WordPress installation. Somebody named the chain wp2shell and the name stuck.
This is worth pausing on, because it is the recurring lesson in application security and it almost never lands until it happens to you. Two findings that a triage process would reasonably have rated “medium” and “not exploitable in core” combined into full site takeover. Neither bug was a big deal. The pair was.
The scoring reflects that confusion. WPScan rated CVE-2026-63030 at 9.8 critical and CVE-2026-60137 at 5.9 medium. CISA’s own analysts scored the same two at 7.5 high and 9.1 critical respectively. The two organisations disagree about which half of the chain is the dangerous one, which tells you something useful: a CVSS number describes a bug in isolation, and the thing that compromised sites was not a bug in isolation.
The timeline, and how little slack there was in it
- Friday, July 17, 2026. WordPress 7.0.2 ships, alongside backports as 6.9.5 (both bugs) and 6.8.6 (the SQL injection only). Versions before 6.8 are unaffected. The release post says plainly: “It is recommended that you update your sites immediately.” The WordPress.org team enabled forced updates through the auto-update system for sites running affected versions, which is a lever they pull very rarely.
- The same weekend. Exploitation begins. Hexastrike reported detections in its honeypots and said that by Sunday it had already assisted with incident response on several real attacks. watchTowr and Patchstack independently confirmed in-the-wild attempts. Wordfence saw endpoint probing and SQL injection attempts the same evening the patch landed.
- Tuesday, July 21. CISA adds both CVEs to the Known Exploited Vulnerabilities catalog. Under the new BOD 26-04 risk-based patching rules, CVE-2026-63030 got a three-day federal remediation deadline of July 24. CVE-2026-60137 got until August 4.
- Wednesday, July 22. Searchlight Cyber publishes the full technical writeup. Working proof-of-concept exploits appear on GitHub.
Note the ordering. Exploitation preceded the public technical writeup. The gap between “a patch exists” and “attackers know what it fixes” is now measured in hours, because diffing a security release is a routine, largely automated exercise. Waiting for a maintenance window is no longer a neutral choice.
The bit that most coverage skipped
Here is what a compromised site looked like, drawn from the SANS Internet Storm Center diary and Wiz’s incident telemetry:
- A PHP webshell dropped into
wp-content/cache/, with a hardcoded authentication key and a deliberate touch: it returns a 404 even though the file exists, so a casual check for it looks like a clean miss. - A backdoor plugin installed through
/wp-admin/update.php?action=upload-plugin, using the administrator session the SQL injection produced. Wiz found three webshell variants, from a one-line dropper up to a 150KB toolkit with file management, database access, port scanning and privilege escalation built in. - New administrator accounts in
wp_users. - Reads of
/wp-json/wp/v2/users?context=editto harvest account details, and local file inclusion attempts aimed atwp-config.phpand the authentication salts.
Every one of those survives an update to 7.0.2. The patch closes the door. It does not remove anyone already inside. A rogue admin account, a webshell in the cache directory, and a stolen set of auth keys all keep working perfectly on a fully patched, fully current WordPress site. Malwarebytes documented what the sites got used for afterwards: credential phishing, malware delivery and fraudulent redirects served to ordinary visitors.
If your site was running an affected version and was reachable from the internet at any point between July 17 and whenever it actually updated, patching is step one of two, and step two is the one nobody did.
Where things stand today
We pulled these numbers from the WordPress.org statistics API on August 27, 2026, refreshing the figures this post originally carried from August 22. It reports the share of installs on each release branch:
| Branch | Share of installs | August 22 |
|---|---|---|
| 7.1 | 47.7% | 43.4% |
| 7.0 | 19.3% | 23.1% |
| 6.9 | 8.6% | 8.8% |
| 6.8 | 6.3% | 6.4% |
| 6.7 and older | 18.2% | 18.3% |
Five days of movement, and it is worth reading carefully because the shape of it is the point. The 7.1 branch gained 4.3 points, almost exactly what 7.0 lost. Sites that are on auto-update kept doing what auto-update does. Meanwhile 6.9 moved by 0.2 points, 6.8 by 0.1, and 6.7 and older by 0.1. The population that had not patched a month after a force-pushed emergency release is the same population that will not patch this month either. That is not a backlog clearing slowly. It is a fixed set of sites nobody is maintaining.
Two honest caveats before anyone quotes this. The API reports branches, not point releases, so the 19.3% on 7.0 includes both the patched 7.0.2 and the vulnerable 7.0.1, and there is no way to split them from this data. And the figures only cover installs that phone home to WordPress.org, which systematically undercounts abandoned and firewalled sites, the exact population least likely to have updated.
What the table does show is the shape of the problem. 27.9% of installs are on the 6.9 or 7.0 branches, the two that contained the full wp2shell chain, and another 6.3% are on 6.8, which was exposed to the SQL injection half. An unknowable but non-zero slice of those has never taken the patch.
Update, September 1, 2026. Third pull from the same API, five days after the second. The pattern above did not just hold, it sharpened:
| Branch | Sept 1 | Aug 27 | Aug 22 | Change since Aug 22 |
|---|---|---|---|---|
| 7.1 | 50.2% | 47.7% | 43.4% | +6.8 |
| 7.0 | 17.1% | 19.3% | 23.1% | -6.0 |
| 6.9 | 8.4% | 8.6% | 8.8% | -0.4 |
| 6.8 | 6.2% | 6.3% | 6.4% | -0.2 |
| 6.7 and older | 18.1% | 18.2% | 18.3% | -0.2 |
Over ten days, the 7.1 branch took 6.8 points and 7.0 gave up 6.0. Every other row moved by less than half a point. The auto-update population is churning between current versions at speed while the non-auto-update population sits completely still, and the gap between the two is not closing at all. Ten days is a short window, but it is now three consecutive readings pointing the same way.
The headline figure moves with it: 25.5% of installs are on 6.9 or 7.0, down from 27.9%, and essentially all of that reduction came out of 7.0, the branch most likely to be auto-updating anyway. The 6.9 branch, which carried the full chain and is not getting force-pushed anything, has shed 0.4 points in ten days. At that rate it clears in about eighteen years.
The PHP numbers drifted the same negligible amount: 38.2% of WordPress sites are on an end-of-life PHP version as of September 1, against 39.2% on August 27, with PHP 7.4 at 17.2% and PHP 8.1 at 11.6%.
Update, September 5, 2026. Fourth pull, four days after the third. Nothing about the reading has changed except the confidence we have in it:
| Branch | Sept 5 | Sept 1 | Aug 27 | Aug 22 | Change since Aug 22 |
|---|---|---|---|---|---|
| 7.1 | 51.8% | 50.2% | 47.7% | 43.4% | +8.4 |
| 7.0 | 15.9% | 17.1% | 19.3% | 23.1% | -7.2 |
| 6.9 | 8.2% | 8.4% | 8.6% | 8.8% | -0.6 |
| 6.8 | 6.1% | 6.2% | 6.3% | 6.4% | -0.3 |
| 6.7 and older | 18.0% | 18.1% | 18.2% | 18.3% | -0.3 |
Fourteen days, four readings, one pattern. 7.1 has taken 8.4 points and 7.0 has given up 7.2, which is very close to a one-for-one transfer inside the auto-updating population. Everything below has moved a combined 1.2 points in two weeks. 24.1% of installs are still on 6.9 or 7.0, down from 25.5% on September 1 and 27.9% on August 22, and as before essentially all of that came out of 7.0 rather than out of the branch that will never be force-pushed anything.
The PHP figure did not improve at all. 38.7% of WordPress sites are on an end-of-life PHP version as of September 5, with PHP 7.4 at 17.1% and PHP 8.1 at 11.5%. That is up from 38.2% on September 1 and inside the noise of this sample, which is the point: over two weeks in which the current-version population churned by more than eight points, the end-of-life population did not move in either direction.
One more data point for the same thesis arrived this week from a different plugin. We pulled the version API for All-in-One WP Migration and Backup, which is carrying an 8.8 of its own, and found 39.19% patched sixteen days after the fix shipped, with the remaining 54.61% spread across releases going back years. Same curve, same tail, different codebase.
And 18.2% are on 6.7 or older, which is the genuinely alarming row. Those sites were untouched by wp2shell, because the batch endpoint bug did not exist before 6.8, and that is the only good news about them: they are running code with years of accumulated known vulnerabilities and no auto-update path that will ever reach them. A site that is three major versions behind did not get there by accident. It got there because auto-updates are off and nobody logs in.
The same API tells you what those installs are running underneath. 39.2% of WordPress sites are on a PHP version that is past end of life, with 17.4% still on PHP 7.4, which stopped receiving security fixes at the end of November 2022, and another 11.6% on PHP 8.1, which reached end of life on December 31, 2025. That is a separate problem from wp2shell, and it is a bigger one.
For context on scale: W3Techs’ survey today puts WordPress at 40.7% of all websites and 58.9% of sites whose CMS is identifiable. A percentage point of unpatched WordPress is an enormous number of actual businesses.
Meanwhile WordPress 7.1 “Mary Lou” shipped on August 19, and was already on 43.4% of installs three days later. The auto-update machinery genuinely works, and it moved most of the web onto a safe version inside a weekend. It just cannot reach the sites that have opted out of it, and those are disproportionately the ones nobody is watching.
Update, September 18, 2026: what the foothold is worth, on the kind of hosting most of these sites are on. This post has spent four readings on how many sites are still exposed. Here is the other half of the question, which is what an attacker does with one once they have it.
An unauthenticated wp2shell exploit gets code execution as the website’s own Linux user. On shared hosting that is a deliberately boxed-in position: cPanel runs PHP as the account user through suEXEC or a per-user PHP-FPM pool, so a compromised site cannot read the account next door. The isolation is real and it is the main reason a breached WordPress site is usually a contained disaster rather than a server-wide one.
CVE-2026-87886, which CISA added to its exploited list on September 16, is a flaw in exactly that boundary. It is a privilege escalation in the Acronis Backup plugin that hosting companies install into cPanel, Plesk and DirectAdmin, and its precondition is an authenticated low-privileged local account. A compromised WordPress site is one. Acronis has confirmed exploitation in limited, targeted attacks and CISA’s own assessment rates the technical impact total.
Nothing in that changes the advice below, and we have no evidence linking the two. It changes what “just one site got hit” is worth assuming. The chain is ordinary and it is the normal shape of a bad week: an unpatched site gives someone the local account, and a flaw in the hosting stack turns the local account into the server. The second half is not yours to patch, which is the subject of the Acronis backup plugin post, and it is one more reason the question “is it clean” matters as much as “is it patched.”
What to actually do, in order
1. Find out what you are running. Log in and look at Dashboard, then Updates. Or open https://yoursite.com/wp-includes/version.php in a browser: if that file is readable you have a separate configuration problem, but it will tell you. If you are on 7.0.2, 6.9.5, 6.8.6, 7.1, or anything newer, you are patched. If you are on 6.9.0 through 6.9.4 or 7.0.0 through 7.0.1, you are still exposed right now and everything below is urgent rather than precautionary.
2. Confirm auto-updates are actually on. WordPress.org force-pushed this one, but the push only reaches installs where the auto-update system is enabled and functional. Plenty of hosts disable it, plenty of “hardened” setups turn it off, and file permissions or a DISALLOW_FILE_MODS constant in wp-config.php will silently prevent it. Under Dashboard, then Updates, WordPress states whether automatic updates for maintenance and security releases are enabled. If it does not say they are, that is your finding.
3. Check for persistence, if you were exposed during the window. This is the step that matters and the one that costs an hour.
- List every administrator account under Users and confirm you recognise all of them. Attackers picked innocuous names.
- Look at everything in
wp-content/cache/with a.phpextension. A cache directory should hold generated HTML, not PHP. - Compare your installed plugin list against the plugins you meant to install, including inactive ones.
- Grep your access logs for
POSTrequests to/wp-json/batch/v1or/?rest_route=/batch/v1. Wiz flags HTTP 207 Multi-Status responses to that endpoint as the reliable exploitation indicator, and notes user agent strings containingwp2shellorrezwp2shellfrom purpose-built tooling. - Check for files with modification timestamps clustered around your exposure window that you did not put there.
4. If you find anything, assume everything is compromised. Rotate the authentication keys and salts in wp-config.php, which logs every session out including the attacker’s. Rotate the database password. Force a password reset on every account. Then restore from a backup taken before the exposure window and reapply your changes on top of it, rather than trying to remove a webshell you have found and hoping it was the only one. It usually is not.
5. If your host manages this for you, get it in writing. Ask two specific questions rather than one general one: on what date did this site move to a patched version, and did you scan it for indicators of compromise afterwards. A managed host that force-patched you on July 18 did you a real favour. A host that patched you on July 18 and never looked for webshells has only done half the job, and it is worth knowing which one you have.
The strategic read, minus the sales pitch
We have written before that WordPress is a reasonable answer when the site is mostly content and the budget is tight, and we still think that. This does not change the recommendation. It changes one of the assumptions underneath it.
The old assumption was that WordPress core is the safe part and the plugins are the risk, so a site with few plugins is a low-maintenance site. wp2shell says the core has a large attack surface too, chiefly a REST API that is enabled by default and reachable without authentication, and that the surface can be exploited without any plugin’s help.
To be clear about what this does not mean: the plugin half of the risk did not go anywhere. Five days after this post first went up, a translation plugin on more than 400,000 sites turned out to be handing out administrator password reset links to unauthenticated visitors, at the same 9.8 severity. Core is now a risk as well as, not instead of.
There is now a number for how large that combined surface is relative to everything else. VulnCheck’s State of Exploitation report for the first half of 2026, published July 28, found that content management systems accounted for one-third of all vulnerabilities with confirmed exploitation in the wild during the period, which it notes is a more significant share than it has seen historically. WordPress plugins were the largest single volume in that group, followed by Drupal, Ghost and Kentico Xperience. Put next to the frontier-AI security headlines of the last week, that is the whole picture in one statistic: the thing most likely to get a small business site taken over is not a novel exploit chain, it is a CMS component with a fix already available. We laid that comparison out in the post on AI-discovered vulnerabilities and what is actually being exploited. The practical consequence is that the maintenance cost of a WordPress site is not proportional to how many plugins it has. There is a fixed floor, and the floor is “someone has to be able to apply a core security release within roughly 24 hours, on a Friday in July.”
For a lot of small businesses that is a perfectly manageable commitment, and the auto-update system does it for free. For the ones running an install that somebody set up in 2019 and nobody has logged into since, it is a commitment they made without knowing it. If that describes a site you own, the honest fix is either to get it onto a host that does this for you or to move it to something with no admin panel and no database to inject into. We have opinions about the second option, but the first one is cheaper and works.
If you are not sure which situation you are in, send us the URL and we will tell you what version it is running and whether it looks like it has been touched. That takes about ten minutes and does not cost anything.
Update, September 22, 2026: core did it again, and this one is a formatting function
WordPress shipped 7.1.1 on September 17 with eleven security fixes, and backported them to twenty-three older branches down to 4.7.36. The headline flaw, CVE-2026-93485, is not in the same class as wp2shell: it is stored cross-site scripting rather than pre-auth remote code execution, Patchstack scored it 7.1 rather than critical, and there is no report of exploitation.
It is worth reading alongside this post for one reason. The payload arrives through the public comment form, passes WordPress’s comment sanitiser honestly because nothing in it is dangerous when it is saved, and becomes a live script only when wpautop() reformats it for display. The bug has been present since at least 4.7, which means it has been sitting in core for roughly a decade while everyone, ourselves included, has been auditing plugins.
The point we made in the section above, that installing an update tells you nothing about whether you were already reached, applies here in a softer form: the version number is the first check, not the last one. The full write-up, including the source-level mechanism and the automatic-update trap in DISALLOW_FILE_MODS, is here.
Update, September 24, 2026: a fifth reading, and core did it a third time
WordPress shipped 7.1.2 on September 22, five days after 7.1.1, as a single-fix emergency release. CVE-2026-87902 is an unauthenticated path traversal in page-template resolution that becomes remote code execution when the active theme has a top-level directory beginning with page- and the server has PEAR installed with register_argc_argv enabled. That is closer to wp2shell than to the comment flaw above: no login, and the endpoint is the front page of the site. It is being exploited, with the first attempt logged at 11:49 UTC on the day of release. The full write-up is here, including the source-level mechanism, which themes meet the precondition, and the timeline problem we found in WordPress’s own Subversion log.
Fifth pull from the statistics API, nineteen days after the fourth:
| Branch | Sept 24 | Sept 5 | Sept 1 | Aug 27 | Aug 22 |
|---|---|---|---|---|---|
| 7.1 | 57.8% | 51.8% | 50.2% | 47.7% | 43.4% |
| 7.0 | 11.3% | 15.9% | 17.1% | 19.3% | 23.1% |
| 6.9 | 7.6% | 8.2% | 8.4% | 8.6% | 8.8% |
| 6.8 | 5.8% | 6.1% | 6.2% | 6.3% | 6.4% |
| 6.7 and older | 17.6% | 18.0% | 18.1% | 18.2% | 18.3% |
The pattern holds for a fifth consecutive reading and has now been holding for a month. 7.1 took another 6.0 points and 7.0 gave up 4.6. Everything below moved a combined 1.3 points in nineteen days. 18.9% of installs are on 6.9 or 7.0, down from 24.1% on September 5 and 27.9% on August 22, and as every time before, almost all of the improvement came out of 7.0 rather than out of the branches nobody is maintaining.
The end-of-life PHP figure also refused to move again. 37.8% of WordPress sites are on an end-of-life PHP version as of today, with PHP 7.4 at 16.8% and PHP 8.1 at 11.2%. That is down 0.9 points in nineteen days, against 6.0 points of churn in the WordPress branch above it. Five readings over a month, and the two populations have never once moved at the same speed.
There is a direct link between that PHP number and the new flaw, which is why we are putting them next to each other. register_argc_argv, the setting that turns the new file inclusion into code execution, was deprecated in PHP 8.5.0. PHP 8.5 accounts for 3.2% of WordPress sites. The deprecation is real and it is not reaching anybody yet.
Sources
- WordPress 7.0.2 security release, WordPress.org, July 17, 2026
- CVE-2026-63030 and CVE-2026-60137, NVD
- CISA Known Exploited Vulnerabilities catalog, entries added July 21, 2026
- WordPress Exploitation Underway (CVE-2026-63030), SANS Internet Storm Center
- Exploitation in the Wild of wp2shell, Wiz
- WP2Shell WordPress Vulnerabilities Exploited in the Wild, SecurityWeek
- WordPress.org statistics API,
api.wordpress.org/stats/wordpress/1.0/andapi.wordpress.org/stats/php/1.0/, retrieved August 22, refreshed August 27, September 1 and September 5, 2026 - PHP supported versions, php.net, for end-of-life dates
- W3Techs CMS usage survey, August 22, 2026
- VulnCheck State of Exploitation 1H-2026, VulnCheck, July 28, 2026, for the finding that content management systems accounted for one-third of all known exploited vulnerabilities in the first half of 2026, with WordPress plugins the largest volume