Securityaffairs

WordPress XSS2Shell Flaw Turns Simple Login Bug Into Full Server Takeover


WordPress XSS2Shell Flaw Turns Simple Login Bug Into Full Server Takeover

Pierluigi Paganini
August 07, 2026

WordPress XSS2Shell flaw enables admin takeover and remote code execution. Users should update to patched versions.

Researchers at Pwn just published a report on a vulnerability chain they’re calling XSS2Shell, and the entry point is quite simple: type a username that doesn’t exist, and WordPress echoes it back with a tiny formatting flaw baked into how two different sanitizers read the same string.

The bug lives in the login page’s error message. When someone submits a username that isn’t registered, WordPress builds an error using that submitted text, after running it through a function meant to strip out any HTML tags. That stripping function relies on PHP’s built-in strip_tags(), which only recognizes a tag if the opening bracket is immediately followed by a letter, no space in between.

That’s the whole crack in the wall. A payload like < area id=ajaxurl>, with a space right after the bracket, sails straight through strip_tags() untouched, because PHP’s parser doesn’t see it as a tag at all. But by the time that string reaches WordPress’s separate sanitizer, wp_kses_post(), a completely different parser that does tolerate that whitespace, it gets interpreted as a legitimate

element and rendered as real, live HTML.

“When JavaScript evaluates an identifier that has no binding in the current scope chain, the runtime eventually reaches the window object. The HTML specification (section 7.3.3) defines that the window object exposes “named properties”: any HTML element in the document with an id attribute becomes accessible as window..” reads the report. “The injected 

 is now the value that the runtime returns for window.ajaxurl.”

From there the researchers found something already sitting on the login page that would interact with injected elements automatically, no click required from a victim. WordPress loads a script meant for the profile page, user-profile.js, on the login page too, purely because the login page also handles password resets. That script watches for a password-reset button and auto-clicks it, and thanks to the injected DOM elements, it finds one that isn’t supposed to exist.

The click event triggers a chain that eventually resolves an undefined JavaScript variable, ajaxurl, using a browser quirk: any HTML element with a matching id attribute automatically becomes accessible as a property on the window object.

The injected

element becomes the value assigned by the browser to window.ajaxurl, allowing the attacker to control how the script handles the request.

That’s DOM clobbering, and it’s the pivot that turns a rendering quirk into an actual network request originating from WordPress’s own script.

That request lands on WordPress’s REST API with parameters that trick it into responding as JSONP, wrapping the reply in a callback function name the attacker controls. Because that callback field accepts dots as well as letters, it isn’t limited to calling a single global function, it can walk a full property chain across browser windows. The researchers reused a technique first published in 2022 to turn that into a cross-window click, one that fires inside an actual logged-in administrator’s session rather than the attacker’s own.

Getting from there to full server compromise takes a specific, orchestrated sequence: open a hidden window, navigate the admin’s browser to WordPress’s own application-password approval screen, then trigger the cross-window click on the approve button using the admin’s real session cookies and nonces. That hands the attacker a valid Application Password for the admin account, which WordPress’s REST API happily accepts over HTTP Basic auth from any origin.

“Application Passwords authenticate REST API requests via HTTP Basic auth. WordPress’s REST CORS implementation reflects the requesting origin and permits Authorization and Content-Type headers. The attacker’s page can now make authenticated cross-origin API calls:

” continues the report. “Single-site WordPress administrators have the unfiltered_html capability by default. The script tags survive into the published page exactly as submitted.”

From there it’s a short hop to publishing a page containing attacker JavaScript, since single-site administrators have unrestricted HTML permissions by default, and using that script to upload and execute a plugin containing arbitrary PHP.

The researchers’ proof of concept ended with a plugin file returning a JSON response confirming code execution as the web server user, then cleaned up every trace: the application password revoked, the published page deleted, the plugin directory removed. Nothing persisted, but the point had already been made. This chain works pre-authentication, needs zero user interaction beyond visiting a link, and reliably ends in remote code execution on a stock WordPress install.

“Our proof used a minimal PHP file that wrote a JSON marker and returned a custom header:” concludes the report.”After verification, the PoC went sent WordPress cleaned up after itself: the Application Password was revoked, the published page was deleted, and the plugin directory was removed. Nothing persisted.”

WordPress moved fast once notified. The researchers reported the chain on July 27, and WordPress shipped version 7.0.3 on August 6, backporting the fix all the way to WordPress 4.7 to cover every still-maintained branch. If you’re running WordPress and haven’t updated yet, this isn’t a “get to it next sprint” patch, it’s a “do it before you finish reading this sentence” patch, given how little an attacker actually needs to pull the whole chain off.

Follow me on Twitter: @securityaffairs and Facebook and Mastodon

Pierluigi Paganini

(SecurityAffairs – hacking, XSS2Shell)







Source link