The Exploit
An unauthenticated attacker can trigger reflected XSS on the WordPress login page by crafting a malicious login URL that, when visited by a victim, injects arbitrary JavaScript into the response. No session or authentication is required.
GET /wp-login.php?redirect_to=http://evil.com/"><script>alert('XSS')</script> HTTP/1.1
Host: target-wordpress-site.com
User-Agent: Mozilla/5.0
Accept: text/html,application/xhtml+xml
The victim visits this URL, and the injected <script> executes in their browser session. The attacker observes the alert box firing (or, in a real attack, the victim's credentials or session cookies being exfiltrated to an attacker-controlled server).
What the Patch Did
Before (wp-login.php):
sprintf(
/* translators: %s: Link to the login page. */
__( 'Check your email for the confirmation link, then visit the <a href="%s">login page</a>.' ),
wp_login_url()
),
After (wp-login.php):
sprintf(
/* translators: %s: Link to the login page. */
__( 'Check your email for the confirmation link, then visit the <a href="%s">login page</a>.' ),
esc_url( wp_login_url() )
),
The patch adds esc_url() wrapping around the wp_login_url() call. This is WordPress's built-in output sanitization function that strips dangerous characters (including <, >, ", ') from URLs, ensuring that any user-controllable parameter injected into the URL cannot break out of the HTML attribute context.
Before (wp-includes/user.php):
'<strong>' . $username . '</strong>'
After (wp-includes/user.php):
'<strong>' . esc_html( $username ) . '</strong>'
In wp-includes/user.php, the patch adds esc_html() to all user-controlled data ($username, $email) that was being concatenated into error messages. This function HTML-encodes special characters, preventing script injection.
Root Cause
CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting').
The dataflow begins with the redirect_to parameter in wp-login.php. WordPress uses wp_login_url() to construct the redirect URL, but this function does not sanitize the redirect_to parameter. The unsanitized value flows directly into sprintf() calls in the registration confirmation messages, where it is placed inside an HTML <a href=""> attribute. Because the string concatenation happens before output, an attacker can inject a closing quote and additional HTML/JavaScript. The same pattern repeats in wp-includes/user.php for error messages: $username and $email parameters arrive directly from $_POST or $_GET and are concatenated into error strings without escaping, crossing the trust boundary from untrusted input into HTML output.
Why It Works
The single most load-bearing line is esc_url( wp_login_url() ) in wp-login.php. If an attacker removes just that esc_url() call, the redirect_to parameter flows unsanitized into the href attribute, allowing them to break out of the attribute with "><script>.... The esc_url() function is specifically designed for this context: it strips all characters that could allow XSS in a URL attribute, while preserving legitimate URL characters. The other esc_html() additions in user.php are secondary but critical defence-in-depth: they handle the case where user-supplied strings ($username, $email) appear in non-URL contexts like <strong> tags. Without these, an attacker could inject script via the username or email fields during registration or error display. The engineer added all five escape calls because each represents a unique injection surface — wp_login_url() for URL attributes, esc_html() for text content, and esc_attr() for attribute values — ensuring complete coverage of the output encoding requirements.
Hardening Checklist
- Use
esc_url()oresc_url_raw()for any URL that is output in an HTML attribute context, especially when the URL contains query parameters likeredirect_to. - Apply
esc_html()to all user-supplied strings ($username,$email,$display_name) before concatenating them into HTML error messages or success notifications. - Never trust the return value of
wp_login_url(),get_option('admin_email'), or similar WordPress core functions that incorporate user-controlled input — always wrap them in the appropriateesc_*()function at the point of output. - Use
wp_kses()or a whitelist-based HTML sanitizer when building HTML in emails or dashboard messages that include user data — do not rely on string concatenation. - Perform a full commit-diff review of all
sprintf()and string concatenation patterns in theme and plugin code, particularly for messages that include user-submitted data in HTML attribute values.
References
- https://nvd.nist.gov/vuln/detail/CVE-2026-64638