WordPress pre-auth login XSS injection attempt (XSS2Shell) - STRICT
SIGMA detection rule by Adama ASSIONGBON (SOC & CTI Consultant), https://linkedin.com/in/adama-assiongbon-9029893a · Unknown
Detects the CVE-2026-64638 (XSS2Shell) exploitation primitive: a POST to wp-login.php whose "log" (username) field contains an unescaped "<" character anywhere in its value, literal or percent-encoded, not only as the first character. wp_strip_all_tags() lets malformed tags like "< area" (with a leading space) survive, and wp_kses_post() then re-parses that surviving fragment as valid HTML, reflecting it unescaped on the failed-login error page. The rule intentionally does not require the "<" to be the very first character of the field (an attacker can prepend an innocuous-looking prefix before the injected tag) nor a specific tag name (area, div, button in the public PoC): any other wp_kses_post()-allowed tag would build an equally valid DOM-clobbering payload, and no legitimate WordPress username or email address can contain "<" under any circumstance. A second regex branch covers the same payload delivered as multipart/form-data instead of the default application/x-www-form-urlencoded body: PHP populates $_POST['log'] identically from either encoding, so a log pipeline that only matches the urlencoded "log=" pattern misses an attacker who deliberately encodes the request as multipart. That branch tolerates single-quoted or unquoted name= attributes on the Content-Disposition line, not only the double-quoted form. A third branch flags an abnormally long "log" value (200+ characters) on its own: a log pipeline with a truncated body-capture window can drop the malformed tag entirely if the attacker pads the field before it, but no legitimate WordPress username or email is ever that long. SOC L1 should verify the source, affected host, user, process or request fields, timing, and related activity.
Détecte la primitive d'exploitation de CVE-2026-64638 (XSS2Shell) : une requête POST vers wp-login.php dont le champ "log" (nom d'utilisateur) contient un caractère "<" non échappé n'importe où dans sa valeur, en clair ou percent-encodé, pas seulement comme premier caractère. wp_strip_all_tags() laisse survivre des balises malformées comme "< area" (avec un espace en tête), puis wp_kses_post() réinterprète ce fragment survivant comme du HTML valide, reflété sans échappement sur la page d'erreur d'authentification. La règle n'exige volontairement ni que "<" soit le tout premier caractère du champ (un attaquant peut préfixer la balise injectée par un texte anodin), ni un nom de balise précis (area, div, button dans le PoC public) : toute autre balise autorisée par wp_kses_post() construirait un payload de DOM clobbering tout aussi valide, et aucun nom d'utilisateur ou email WordPress légitime ne peut contenir "<" en toute circonstance. Une deuxième branche de regex couvre le même payload envoyé en multipart/form-data plutôt qu'en application/x-www-form-urlencoded par défaut : PHP peuple $_POST['log'] de façon identique quel que soit l'encodage, donc un pipeline de logs qui ne cherche que le motif urlencodé "log=" manque un attaquant qui encode volontairement la requête en multipart. Cette branche tolère aussi un attribut name= entre guillemets simples ou sans guillemets, pas seulement la forme entre guillemets doubles. Une troisième branche signale une valeur "log" anormalement longue (200 caractères ou plus) a elle seule : un pipeline de logs qui tronque la capture du corps de requete peut faire disparaitre la balise malformee si l'attaquant remplit le champ avant elle, alors qu'aucun nom d'utilisateur ou email WordPress legitime n'atteint jamais cette longueur. Le SOC L1 doit vérifier la source, l’hôte affecté, l’utilisateur, les champs de processus ou de requête, la chronologie et les activités associées.
title: WordPress pre-auth login XSS injection attempt (XSS2Shell) - STRICT
id: 9f2ae8e2-13cc-408c-ae91-8c26fd5f13f5
status: experimental
description: |
Detects the CVE-2026-64638 (XSS2Shell) exploitation primitive: a POST to wp-login.php whose "log" (username) field contains an unescaped "<" character anywhere in its value, literal or percent-encoded, not only as the first character. wp_strip_all_tags() lets malformed tags like "< area" (with a leading space) survive, and wp_kses_post() then re-parses that surviving fragment as valid HTML, reflecting it unescaped on the failed-login error page. The rule intentionally does not require the "<" to be the very first character of the field (an attacker can prepend an innocuous-looking prefix before the injected tag) nor a specific tag name (area, div, button in the public PoC): any other wp_kses_post()-allowed tag would build an equally valid DOM-clobbering payload, and no legitimate WordPress username or email address can contain "<" under any circumstance. A second regex branch covers the same payload delivered as multipart/form-data instead of the default application/x-www-form-urlencoded body: PHP populates $_POST['log'] identically from either encoding, so a log pipeline that only matches the urlencoded "log=" pattern misses an attacker who deliberately encodes the request as multipart. That branch tolerates single-quoted or unquoted name= attributes on the Content-Disposition line, not only the double-quoted form. A third branch flags an abnormally long "log" value (200+ characters) on its own: a log pipeline with a truncated body-capture window can drop the malformed tag entirely if the attacker pads the field before it, but no legitimate WordPress username or email is ever that long. SOC L1 should verify the source, affected host, user, process or request fields, timing, and related activity.
Détecte la primitive d'exploitation de CVE-2026-64638 (XSS2Shell) : une requête POST vers wp-login.php dont le champ "log" (nom d'utilisateur) contient un caractère "<" non échappé n'importe où dans sa valeur, en clair ou percent-encodé, pas seulement comme premier caractère. wp_strip_all_tags() laisse survivre des balises malformées comme "< area" (avec un espace en tête), puis wp_kses_post() réinterprète ce fragment survivant comme du HTML valide, reflété sans échappement sur la page d'erreur d'authentification. La règle n'exige volontairement ni que "<" soit le tout premier caractère du champ (un attaquant peut préfixer la balise injectée par un texte anodin), ni un nom de balise précis (area, div, button dans le PoC public) : toute autre balise autorisée par wp_kses_post() construirait un payload de DOM clobbering tout aussi valide, et aucun nom d'utilisateur ou email WordPress légitime ne peut contenir "<" en toute circonstance. Une deuxième branche de regex couvre le même payload envoyé en multipart/form-data plutôt qu'en application/x-www-form-urlencoded par défaut : PHP peuple $_POST['log'] de façon identique quel que soit l'encodage, donc un pipeline de logs qui ne cherche que le motif urlencodé "log=" manque un attaquant qui encode volontairement la requête en multipart. Cette branche tolère aussi un attribut name= entre guillemets simples ou sans guillemets, pas seulement la forme entre guillemets doubles. Une troisième branche signale une valeur "log" anormalement longue (200 caractères ou plus) a elle seule : un pipeline de logs qui tronque la capture du corps de requete peut faire disparaitre la balise malformee si l'attaquant remplit le champ avant elle, alors qu'aucun nom d'utilisateur ou email WordPress legitime n'atteint jamais cette longueur. Le SOC L1 doit vérifier la source, l’hôte affecté, l’utilisateur, les champs de processus ou de requête, la chronologie et les activités associées.
references:
- https://www.cve.org/CVERecord?id=CVE-2026-64638
- https://nvd.nist.gov/vuln/detail/CVE-2026-64638
- https://wordpress.org/news/2026/08/wordpress-7-0-3-release/
- https://hadrian.io/blog/wordpress-xss2shell-unauthenticated-login-screen-xss-to-php-code-execution-cve-2026-64638
- https://pwn.ai/blog/xss2shell
- https://github.com/imbas007/CVE-2026-64638-POC
- https://github.com/Boreas37/CVE-2026-64638-PoC-XSS2Shell-
author: Adama ASSIONGBON (SOC & CTI Consultant), https://linkedin.com/in/adama-assiongbon-9029893a
date: 2026-08-08
related:
- id: 6651aeef-a7f9-4aed-95bb-370fbe7da24d
type: derived
- id: 1cb448aa-2092-4d01-9509-5f723226e3a9
type: derived
- id: 4fc16c50-0ed6-42df-be58-7dd73bedcece
type: derived
tags:
- attack.initial-access
- attack.execution
- attack.t1190
- attack.t1059.007
- cve.2026-64638
logsource:
category: webserver
definition: >
Web server, WAF or reverse-proxy access logs with request body inspection on wp-login.php.
Expected fields: Url, HttpMethod, RequestBody. Field names vary by log source; adapt
RequestBody to the local pipeline (e.g. http.request.body.content, post_data).
detection:
selection_endpoint:
Url|contains: '/wp-login.php'
selection_method:
HttpMethod: POST
selection_xss_payload:
RequestBody|re:
- '(?i)(^|&)log=[^&]*?(<|%3c)'
- '(?i)name\s*=\s*["'']?log["'']?[^\n]*\n\s*\n[^\n<]*<'
# Log-truncation evasion: a WAF/log pipeline that caps captured RequestBody length can drop
# the malformed tag entirely if the attacker pads the "log" field before it. No legitimate
# username or email is ever this long (WordPress core caps sanitize_user() at 60 chars), so
# an oversized log value is itself anomalous even when the "<" byte falls outside the
# captured window.
selection_oversized_log_field:
RequestBody|re: '(?i)(^|&)log=[^&]{200,}'
condition: selection_endpoint and selection_method and (selection_xss_payload or selection_oversized_log_field)
fields:
- SourceIp
- DestinationIp
- DestinationPort
- HttpMethod
- Url
- RequestBody
- UserAgent
- StatusCode
falsepositives:
- None identified. A valid WordPress username or email address can never contain "<"; any occurrence in the log parameter, literal or percent-encoded, has no legitimate origin.
- Log pipelines that do not capture RequestBody for wp-login.php will silently miss this rule; verify body capture is enabled before relying on it as the sole detection for this CVE.
level: high