If your site runs WordPress 6.9.0 through 6.9.4 or 7.0.0 through 7.0.1, it is affected by the full remote code execution chain being called wp2shell. If it runs 6.8.0 through 6.8.5, it is affected by the SQL injection half of that chain. Either way, update WordPress core today. The fixed releases are 6.8.6, 6.9.5 and 7.0.2, and updating core from your dashboard is the actual fix. Everything else here is detail around that one action.
We recently ran a scan-and-patch program across our shared hosting servers, covering thousands of WordPress installations. One finding from it matters more to most site owners than the CVE does.
What wp2shell actually is
wp2shell is not one bug. It is a chain of two flaws in WordPress core, each a problem alone and worse together.
CVE-2026-63030 is a route-handling confusion issue in the WordPress REST API batch endpoint, scored CVSS 9.8. It affects 6.9.0 through 6.9.4 and 7.0.0 through 7.0.1, and was introduced in 6.9. It was discovered by Adam Kues of Searchlight Cyber.
CVE-2026-60137 is a SQL injection flaw in the author__not_in parameter of WP_Query, scored CVSS 5.9 by the WordPress CNA and 9.1 by CISA, which disagree mainly on how hard it is to reach and whether it lets an attacker write as well as read. On 6.8.0 through 6.8.5 it is a standalone SQL injection; on 6.9.x and 7.0.x it is the second half of the chain. It is credited to the researchers TF1T, dtro and haongo.
Chained on 6.9.x or 7.0.x, the two let an attacker who has never logged in execute code on the server hosting your site, with no plugins required and no preconditions beyond a default configuration. On 6.8.x the SQL injection is only reachable if a plugin or theme passes untrusted input into that parameter, which is a real risk on a typical site but not an automatic one.
The chain was disclosed on 17 July 2026 and public proof-of-concept exploits appeared within hours. Both CVEs were added to CISA’s Known Exploited Vulnerabilities catalog on 21 July 2026, which is formal confirmation they are being used against real sites; CISA set a patch-by date of 24 July. We have seen probe traffic carrying literal wp2shell and wp2shell-check/1.0 user-agent strings. What matters to you is your version number.
| Your WordPress version | Affected? | What to do |
|---|---|---|
| 6.8.6, 6.9.5, 7.0.2 or later | No | Nothing. Keep auto-updates on. |
| 7.0.0 to 7.0.1 | Yes, full RCE chain | Update to 7.0.2 now. |
| 6.9.0 to 6.9.4 | Yes, full RCE chain | Update to 6.9.5 now. |
| 6.8.0 to 6.8.5 | Yes, SQL injection, where a plugin or theme feeds the parameter | Update to 6.8.6 now. |
| Older than 6.8.0 | Not in this range, but read on | See the end-of-life section. This is worse, not better. |
Credit to the WordPress core security team, who shipped 6.8.6, 6.9.5 and 7.0.2 quickly. On a default install, those releases install themselves.
How to check your version right now
1. From the WordPress dashboard (the ninety-second check)
Log in and look at Dashboard, then At a Glance. It states the running version directly, as in “WordPress 7.0.1 running Twenty Twenty-Six theme”, and the version also appears in the bottom-right of the admin footer. Then open Dashboard, then Updates. If a core update is offered, take it. On a standard install that is one button. If your host provides WordPress Toolkit or an installer such as Softaculous, that will also list every install on the account with its version.
2. From File Manager, if you want certainty
In cPanel, open File Manager, go to the folder holding the site (usually public_html), then into wp-includes, and view version.php. Near the top is a line reading $wp_version = ... followed by the version string. That is the version on disk, which is the one that counts. A dashboard can be cached or filtered by a plugin; the file cannot.
3. The check almost nobody does
Look for other copies of WordPress on your account: folders named old, dev, staging, backup, public_html-old, or a dated copy from a redesign. If a folder holds wp-load.php and a wp-admin directory it is a WordPress install, and if anything can reach it over the web it is as exposed as your main site. That was the biggest surprise in our audit.
What to do, in priority order
- Update core on your live site. Dashboard, Updates, Update Now. If your host takes daily backups you already have a restore point, so do not let backup-shopping delay this by days.
- Know what the update does not do. It replaces core files. It does not remove a backdoor planted in
wp-content, inmu-plugins, in your uploads folder or in the database, and a passing checksum check does not mean the site is clean. If your site ran an affected version and was reachable, work through the inspection list below as well as updating. - Update every other WordPress copy on the account, or delete the ones you no longer need. An abandoned copy is not lower risk than a live one. It is higher, because nobody is watching it.
- Turn automatic core updates back on under Dashboard, Updates, and update plugins and themes while you are there. This will not be the last bug, and a site behind on core is usually behind on everything.
What we did across our shared servers
We scanned our shared hosting servers for WordPress installs inside the affected ranges and worked through what we found. The numbers below describe that program, not every server we run.
| Measure | Result |
|---|---|
| Installations not in the affected version ranges | About 95% |
| Installations that needed patching | About 5% |
Counts as of the final re-audit on 7 August 2026. The large majority of installations we scanned — about 95 percent — were not affected by wp2shell. The remainder is what the rest of this section is about.
Finding them
We wrote a purpose-built audit script, proved it on one server, then carried it to the rest, producing a dated report per server. The key decision was to scan the whole filesystem, not document roots only. That is slower, hours rather than minutes on a busy server, and worth it, because a docroot-only scan would have missed these entirely:
- Copies sitting in
.trashdirectories. - Dated backup copies of
public_htmlmade during a redesign and never removed. - Control panel restore archives extracted inside a live account and left there.
- Auto-restore copies created by security plugins.
Every install was classified mapped, meaning a live virtual host serves it, or unmapped, meaning inert on disk. That decided which patch path it got: a live site and an inert copy are not the same job.
Patching them
There is no one safe way to patch hundreds of installs owned by hundreds of customers, so we used three paths.
| Path | Used when | What it does |
|---|---|---|
| Normal | Install is live and its database is reachable | wp core update then wp core update-db |
| Files-only, forced | The database cannot safely be touched: suspended account, missing or dead database, maintenance mode, or a stale copy still pointing at the database of a live site already patched | wp core download --force --skip-content --version=<same-branch release> with no database step at all. The explicit version matters: without it the download resolves to the latest release and jumps release branch, which is the schema change this path exists to avoid. |
| Hand-built | The automated paths did not apply | Replace wp-admin, wp-includes and top-level PHP files only, leaving wp-config.php and wp-content untouched, inside the same release branch so the schema version never changes |
Before every change we backed up that specific install: a tarball of the core files always, and a mysqldump of its database wherever the install had a reachable one. A meaningful minority did not, because there was no wp-config.php or the config pointed at a database deleted years ago; those were backed up files-only, which is also why they got no database step. Not a backup of the server, and not from last night: that install, immediately before that change.
Every WordPress archive was checksum-verified against the hashes WordPress publishes before use: MD5 via WP-CLI on the tooling paths, SHA1 against the published release hashes where we fetched and swapped core by hand. If you are overwriting core files on a live business website, you should be able to prove the files you write are the ones WordPress published.
Verifying them
Patching is the easy half; proving you broke nothing is the half customers care about. Four independent checks:
- Checksum verification.
wp core verify-checksumswherever the environment allowed it to run, confirming the files on disk match the official release. - External HTTP baseline, before and after. Not just the status code but the response byte size, because a site that returns 200 both times can still be quietly broken. Where a page is static enough to give a byte-identical response, that is about as good as external verification gets; where nonces or rotating content make that impossible, we compared size within an expected band and checked the page rendered.
- Full re-audit of the server after the patch run, looking for anything the first pass missed.
- An explicit check for new PHP fatal errors or parse errors.
Where the files-only path was used, operators additionally confirmed the database schema version was unchanged afterwards. That is the proof that patching a stale copy could not disturb a live database it happened to share with a site serving customers.
What the job taught us
- A suspended account breaks the normal patch path, because suspension disables the account’s database user. The updater cannot connect, so it refuses.
- Installs pointing at databases deleted years ago cannot bootstrap. The site is dead; the files are still reachable.
- WP-CLI walks up the directory tree looking for
wp-config.php, so a copy nested inside another install, with no config of its own, resolves to the parent. Pass an explicit path on every invocation, then verify the parent was untouched. - The version race is real. Installs auto-update between the scan and the patch run, and forcing a download to a version read from a stale scan would be a silent downgrade. Re-read the on-disk version immediately before patching.
- Abandoned duplicates are everywhere on any shared platform, many not web-reachable, and some still pointing at the database of a site that is. An inert copy still deserves a deliberate decision.
A clean result is not the same as a safe site
Here is the part worth more than the CVE itself. A WordPress install older than 6.8 is not in the affected ranges at all — and that is not good news. Older than 6.8 means already end-of-life and out of security support before this vulnerability was ever published.
On standard shared hosting, core updates belong to the site owner: on an unmanaged plan nobody can apply them for you. That makes this the one item on the list you have to own yourself.
So the counter-intuitive part: an install too old to fall inside the vulnerable range is too old to be getting security fixes at all, and is exposed to a backlog of older, thoroughly weaponized vulnerabilities nobody writes advisories about any more. Passing this inspection because there is nothing left in the building to burn is not a pass.
If your version is below 6.8, this advisory is not your most urgent problem; getting back onto a supported release is. On an old site that is a real project, with plugin and theme compatibility to work through, worth scheduling deliberately.
Where a WAF fits, honestly
Cloudflare deployed managed rules on 17 July to all plans including free, but only for sites whose traffic is actually proxied through Cloudflare, not for domains merely using its DNS. Its own guidance is worth quoting rather than talking around: “WAF protections reduce exposure while customers update, but they are not a substitute for patching.” Our gloss: a rule blocks a request pattern, it does not change the code on your disk.
That is how we treat our own layers. Our Web Application Firewall applies managed rules including virtual patching for applications like WordPress, which buys a defended window between disclosure and the moment your site is updated. We are not claiming here that it caught wp2shell for you specifically; ask us if you want to know what applied to your plan. Use the window; do not mistake it for the destination.
Check the site, even if nothing looks wrong
If your site ran an affected version and was reachable between 17 July and the day you updated, treat inspection as the default, not something you do only on a hunch. A pre-authentication remote code execution bug with public exploit code does not announce itself.
- Users, All Users, filtered by Administrator. Any account you do not recognize is the finding.
- The
wp-content/mu-pluginsfolder. Anything in there runs on every page load and does not appear in your plugins list. If you did not put a file there, treat it as suspect. - Any
.phpfile underwp-content/uploads. There should be none. Uploads hold images and documents. - Your
.htaccessfiles. Look forauto_prepend_fileor long base64 strings. Neither belongs in a normal WordPress.htaccess. - Cron Jobs in cPanel. A scheduled command you did not create is a persistence mechanism.
- Recently modified files, and Settings, General. Sort File Manager by Last Modified: PHP files that changed when you changed nothing are worth asking about, as is any unfamiliar plugin. Then confirm the site URL, admin email and registration setting are what you intended.
- Rotate credentials for administrators, control panel, FTP and database, and regenerate the keys and salts in
wp-config.php. Changing a password alone leaves stolen login cookies valid; replacing the salts invalidates every session. On an affected, reachable site, do this rather than deciding whether to.
If something looks wrong, do not start deleting files, and do not update on top of it and assume that fixed it. Preserve the current state, because it is evidence, and get in touch. On a site you believe was touched, cleaning comes before updating.
Where we can help
- Managed WordPress covers core security updates and safe plugin and theme updates with a pre-update backup, plus rollback and staging-based testing on higher tiers, malware detection and daily backups. If a site sat on an old version because nobody owned the update, that is the gap it closes.
- CloudSafe Backup for a restore point that exists before you need it, alongside Malware Scanning, Disaster Recovery and Managed Security if they fit. Ask us if you want to talk through a recovery plan.
None of that is the point of this article, though. The point is the version number in wp-includes/version.php. If it is 6.9.x or 7.0.x and not 6.9.5 or 7.0.2, update today. If it is 6.8.x and not 6.8.6, update today. And if it is older than 6.8, wp2shell was never your real problem: that site has been out of security support for longer than this vulnerability has existed, and getting it onto a supported release is the work that protects it.
Be First to Comment