
XSS reflejado no autenticado en WPForms <= 2.0.2.1 — omisión del escape de Smart Tag de CVE-2026-88996 en la posición de nombre de atributo
Afectado: WPForms – AI Form Builder for WordPress (gratuito/lite), versión 2.0.2.1 — la última actual en el momento de la divulgación (también la versión que contiene la corrección de CVE-2026-88996). La edición Pro comparte el renderizador de Smart Tags.
Clase: cross-site scripting reflejado no autenticado (CWE-79) con ejecución de JavaScript verificada, mediante una corrección incompleta de CVE-2026-88996 / GHSA-8j5g-9vmv-9mmw.
Descubierto: 2026-09-27 ·
Reportado a Patchstack: 2026-09-27 ·
Rechazado: 2026-09-28 ("el problema solo aparece cuando el administrador del sitio ha escrito un Smart Tag desnudo donde corresponde un nombre de atributo HTML... no es una configuración por defecto ni un patrón que nadie escribiría realistamente" — no se cuestionó la validez técnica) ·
Público: 2026-09-28.
La corrección de 2.0.2.1 para el XSS reflejado de la familia CVE-2026-93485 escapa los valores de Smart Tag que se
sustituyen en el marcado (SmartTags::escape_for_attribute() codifica comillas y
espacios en blanco). Eso es efectivo siempre que el valor sustituido quede dentro de un valor
de atributo entrecomillado. Cuando el autor de un formulario coloca un Smart Tag desnudo en una posición
de nombre de atributo en el mensaje de Confirmación — por ejemplo:
<input {field_id="3"}>
un valor de campo enviado que no contiene comillas ni espacios en blanco, como
onmouseover=alert(1), se inserta literalmente y se convierte en un nuevo atributo
manejador de eventos activo. El escapado nunca codifica =, y los nombres de atributo no requieren ni comillas ni
espacios en blanco. La confirmación devuelta al remitente anónimo contiene entonces:
<input onmouseover=alert(1)>
Las envíos anónimos a admin-ajax.php (action=wpforms_submit,
wp_ajax_nopriv_*) no requieren nonce por diseño para usuarios no autenticados, por lo que se trata de un XSS reflejado totalmente
no autenticado. JavaScript arbitrario se ejecuta dentro de la restricción de un solo token
mediante onmouseover=eval(String.fromCharCode(...)).
En el mismo formulario, la forma del payload del CVE principal —ruptura de comillas desde dentro de un valor entrecomillado,
p. ej. x" onfocus=alert(1) autofocus y="— se escapa correctamente en todas
las posiciones (se observa codificación " /  ). El escape de 2.0.2.1 funciona en todas partes
excepto en la ubicación del nombre del atributo. Se trata de una corrección incompleta del mismo sumidero de sustitución,
no de una brecha de funcionalidad separada.
# 1. WordPress + WPForms lite 2.0.2.1, create a form with a single-line text field (id 3)
# 2. Set the form's Confirmation message to:
# A: <input value="{field_id="3"}"> B: <input {field_id="3"}>
# 3. Run the PoC (stdlib-only Python 3):
python3 poc.py --url http://target.example/form-page/
No input content was provided after "INPUT:" and "Expected output:". Please send the Markdown chunk (7 of 9) to translate.
[+] field 3 reflects into the confirmation message
[payload (bypass position)] occurrences: LIVE=1 ESCAPED/INERT=2
LIVE -> ...<input onmouseover=alert(poc93485)>...
[control] parent-CVE payload escaped everywhere: YES (fix works at value positions)
=== VERDICT: VULNERABLE — payload reaches attribute-NAME position unescaped ===
poc.py autodescubre el formulario, los campos y el campo que refleja; envía de forma anónima; y
clasifica cada ocurrencia como LIVE (inyección en el nombre del atributo) frente a ESCAPED (valor entrecomillado,
la forma del CVE principal). Código de salida 0 = vulnerable.
El mensaje de confirmación del formulario, redactado por el administrador, debe contener una Smart Tag desnuda que refleje la entrada dentro del marcado en la posición del nombre del atributo. Los investigadores de Patchstack evaluaron esto como un patrón de redacción poco realista; no obstante, es una posición de característica documentada, el escapado existe precisamente para hacer segura la sustitución en contexto de marcado, y el autor no recibe ningún error — solo una inyección silenciosa.
Codificar = en el escapado de contexto de marcado, o analizar contextualmente el marcado de confirmación
y permitir la sustitución de Smart Tag solo en posiciones de texto y de valor de atributo entrecomillado.
poc.py — PoC autocontenido (sin pasos del lado del servidor, sin dependencias)poc.sh — variante mínima basada en curlSolo para pruebas autorizadas y fines educativos. El investigador publica esto después de que se rechazara un intento de envío coordinado; el hallazgo técnico no fue disputado.
Ashish Kunwar (@D0rkerdevil)