Home/ Blog/ Security news/ Article
Blog · Security news

WordPress login-page XSS can chain to code execution, patch 7.0.3

WordPress 7.0.3 fixes CVE-2026-64638, a pre-auth login-page XSS that runs attacker code from one failed login and can chain to server code execution. Patch now.

Open glowing front door in a sealed facade, small hatch ajar above

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.

Source: pwn.ai XSS2Shell writeup and the WordPress 7.0.3 release.
StagePreconditionsWhat an attacker gets
Stage 1: pre-auth XSSNone. One failed login attempt.Attacker JavaScript running in the site's own origin, for anyone who loads the page
Stage 2: code executionAn administrator is logged in and opens an attacker-controlled pageA 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 _jsonp callback parameter alongside unusual _method values.
  • 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.

Topics

Frequently asked questions

What is CVE-2026-64638?

CVE-2026-64638 is a pre-authentication reflected cross-site scripting flaw on the WordPress login screen, fixed in WordPress 7.0.3 on August 6, 2026. A single failed login can inject attacker JavaScript into the page, and pwn.ai showed the flaw chaining to PHP code execution against a logged-in administrator.

Which WordPress versions are affected and what is the fix?

The flaw affects effectively all supported WordPress versions before 7.0.3. WordPress fixed it in 7.0.3 and backported the fix across maintained branches to the 4.7 series. Installs older than 4.7 receive no patch and should move to a supported branch.

Is CVE-2026-64638 being exploited in the wild?

No public in-the-wild exploitation was reported as of August 7, 2026. The pwn.ai researchers published the technique, named XSS2Shell, but no active mass exploitation had surfaced at the time of writing. The universal pre-auth XSS still warrants prompt patching.

Does the flaw give attackers remote code execution?

Code execution is possible but conditional. The full chain needs a WordPress administrator to be logged in and to open a page the attacker controls, so it is a targeted attack rather than a wormable one. Without that admin interaction, the flaw stops at pre-auth cross-site scripting.

How can I detect exploitation of this WordPress flaw?

Watch for a sequence rather than one event: malformed markup with a space after a less-than sign in login POST bodies, REST requests carrying a _jsonp callback, and an Application Password creation followed quickly by a plugin upload. File integrity monitoring on the webroot flags a plugin directory that gains an unexpected PHP file.

Ready to meet the Guardians?

Deploys fast - agentless for monitoring and cloud, a lightweight agent for deep endpoint security. Just Suriq, standing watch.