WordPress shipped 7.0.3 on August 6, and the line going around, a pre-auth login flaw that leads to code execution, is accurate and misleading at the same time. Two very different problems hide behind that one sentence. One affects every site on an unpatched build and needs no account and no clicks. The other, the part people are quoting as remote code execution, only fires when a logged-in administrator opens a page the attacker controls. Patch for both. But if you triage by real exposure, you are patching for the first problem, not the second.
The flaw is CVE-2026-64638, reported by the pwn.ai research team on July 27 and fixed nine days later. It carries a CVSS score of 8.9. WordPress calls it a reflected cross-site scripting bug on the login screen, and the researchers named the full chain XSS2Shell.
A login field that survives two sanitizers
The bug lives in how wp-login.php echoes a failed-login username back into the error message. That value passes through WordPress sanitizers before it reaches the page, so on paper it should be safe. It is not, because two of those sanitizers disagree about what a tag is.
PHP's strip_tags(), which backs wp_strip_all_tags(), only treats < as the start of a tag when a letter follows it immediately. Put a space in, and it walks past. According to the pwn.ai writeup, strip_tags('< area id=test>') returns the string untouched while strip_tags('<area id=test>') strips it. WordPress then runs the survivor through its KSES sanitizer, and KSES is more forgiving: to KSES, < area is a valid <area> element. Because <area> sits on the allowlist, the attacker's injected nodes live on in the page as real DOM.
The fix ships in WordPress 7.0.3, one of a dozen issues that release addressed, and was backported across every branch WordPress still maintains, back to the 4.7 series. That covers effectively all supported installs from before those releases. Sites older than 4.7 get no patch and should be treated as permanently exposed. As of August 7, no public exploitation in the wild had been reported.
| Stage | Preconditions | What an attacker gets |
|---|---|---|
| Stage 1: pre-auth XSS | None. One failed login attempt. | Attacker JavaScript running in the site's own origin, for anyone who loads the page |
| Stage 2: code execution | An administrator is logged in and opens an attacker-controlled page | A plugin ZIP upload and attacker PHP executing on the server |
The code-execution part is real, not a mass event
The path from injected script to a shell is clever, and it is worth understanding why it is not wormable. The login screen pulls in WordPress's own user-profile.js script, whose ready handler auto-clicks a button inside a specific selector, and the injected elements are shaped to match that selector. That click fires a request to ajaxurl, which is not defined on the login page, so the browser resolves it from the DOM, where an injected <area id="ajaxurl"> now supplies the value. The request lands on a REST endpoint with a JSONP callback, and through Same Origin Method Execution the callback clicks the Application Password approval control inside the admin's authenticated session. That hands the attacker a valid Application Password, which uploads a plugin ZIP whose PHP files are reachable over the web without activation.
Every step of that assumes the victim is already signed in as an administrator and chooses to open the attacker's page. Strip either condition and the chain stops at Stage 1. So the honest read is a targeted attack against a known admin, not a spray-and-pray internet event. It is the same shape as the AI Engine plugin bug where a link an admin clicked created a rogue admin, and it inherits the same blind spot as the reported Awesome Motive CDN compromise: a payload that only acts inside a logged-in admin session is invisible to a scanner crawling the site anonymously.
This is a sanitizer-layering bug, not a login quirk
The interesting failure here is architectural, and it is not confined to the login screen. Two sanitizers that parse the same input by different rules can be worse than one, because the second one re-animates what the first declared dead. Any code path that trusts wp_strip_all_tags() output as XSS-safe and then drops it into a KSES-allowlisted context carries the same gap. If you maintain a plugin or theme, the takeaway is not "WordPress patched the login page." It is: go find every place you clean a string with one function and trust it in a context governed by another. Parser disagreements are a recurring class, the same root idea behind the CommonMark attribute XSS and the OpenWrt hostname XSS, where one rigged input walked straight through the layer meant to stop it.
How you would know: watch the sequence, not a signature
Because the chain runs on WordPress's own JavaScript and REST endpoints, there is no malware file to match. Detection has to be behavioral, and the useful signals are a sequence rather than any single event:
- Malformed markup with a space after
<in login POST bodies, the literal artifact of the bypass. - REST requests carrying a
_jsonpcallback parameter alongside unusual_methodvalues. - An Application Password created and then, seconds later, a plugin upload.
- A plugin directory that suddenly holds a PHP file it never had before.
The last one is where host-level monitoring earns its place. A managed file integrity monitoring setup watching the webroot flags a plugin folder that just gained an executable it was never shipped with, whether or not the upload came through this exact chain. Pair that with log collection on the auth and REST activity above, and the tell is the pattern, not a payload you have to recognize in advance.
Update to 7.0.3, then close the Application Password path
Update WordPress core to 7.0.3, or to the backported security release for your branch, and turn automatic core updates back on if they are off. That single step closes Stage 1 for everyone. Sites running anything below 4.7 have no patch coming and need to move to a supported branch.
Then look at Application Passwords, the feature this chain pivots through. It is enabled by default, it bypasses interactive login because it is built for API and REST access, and most sites that have it on are not using it. Where nothing depends on it, disable it (the wp_is_application_passwords_available filter returning false), and alert on any creation. Across a fleet, vulnerability detection is what surfaces the sites still sitting below 7.0.3 before someone else finds them. The patch closes the door; knowing which of your sites had it open is the part a scan after the fact cannot give you back.