
Análisis de causa raíz, verificador de versión pasivo y PoC de laboratorio para CVE-2026-18322, una escalada de privilegios no autenticada en el plugin de WordPress Smart Popup by Supsystic.
Investigación de seguridad sobre CVE-2026-18322, una vulnerabilidad de escalada de
privilegios no autenticada en el plugin de WordPress Smart Popup by Supsystic
(popup-by-supsystic). Este repositorio contiene un análisis de causa raíz derivado del
diff del código fuente upstream, los parches extraídos, una herramienta de detección
(fingerprinter pasivo de versiones) para identificar instalaciones afectadas y un laboratorio para el PoC de explotación.
La vulnerabilidad es pública y está parcheada. Este trabajo se publica para uso defensivo: ayudar a los operadores a encontrar y remediar hosts afectados.
| Rango de versiones | Estado |
|---|---|
< 1.13.0 | Vulnerable |
>= 1.13.0 | Corregida |
1.13.0 (publicada el 31.07.2026) es la primera versión parcheada; repara los tres eslabones de la cadena descrita a continuación. Véase §1 del análisis.
Tres debilidades independientes se combinan para permitir la creación de un administrador sin autenticación:
1. Una colisión en el mapa de permisos — havePermissions() combinaba dos mapas de permisos con
array_merge(). Ambos usan la clave de tipo string PPS_USERLEVELS ('userlevels'), y
array_merge() sobrescribe las claves de tipo string, por lo que la lista corta por defecto del
controlador base reemplazó por completo la lista del módulo popup — eliminando silenciosamente la
restricción de administrador de save y otros nueve métodos:
$permissions = $mod->getController()->getPermissions(); // [... 'save' ...]
$permissionsBase = $mod->getController()->getBasePermissions(); // ['getListForTbl','removeGroup','clear']
$permissions = array_merge($permissions, $permissionsBase); // base wins — 'save' is gone
La comprobación falla abierta: una acción ausente del mapa nunca se deniega.
2. Un nonce reutilizable enviado por correo a desconocidos — save seguía requiriendo un
pps_nonce, pero el correo de confirmación de suscripción incluía esa misma acción de nonce. Para
usuarios no autenticados, un nonce de WordPress se basa en uid=0, por lo que el token generado para
un suscriptor anónimo es válido para cualquier atacante anónimo.
3. Sin lista de roles permitidos del lado del servidor — createWpSubscriber() pasaba el rol
configurado directamente a WP_User::set_role(). La lista de roles seguros del plugin (que excluye
administrator) solo se aplicaba al renderizar el desplegable de administración — un control
únicamente de UI.
Encadenado: suscribirse para obtener un nonce → reutilizarlo contra popup::save para establecer
sub_wp_create_user_role=administrator → activar el flujo de suscripción → cuenta de administrador
persistente.
Recorrido completo con referencias a archivo/línea: docs/ANALYSIS.md.
poc/cve_2026_18322_check.py determina si un sitio ejecuta una versión afectada. Nunca
intenta la explotación: emite GETs HTTP simples y lee la versión que la instalación
publica sobre sí misma.
git clone <this-repo> && cd CVE-2026-18322
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
requests es recomendado pero opcional — la herramienta recurre a la biblioteca estándar.
# Single target
python3 poc/cve_2026_18322_check.py https://example.com
# With supporting evidence
python3 poc/cve_2026_18322_check.py https://example.com -v
# Many targets, concurrently, exporting results
python3 poc/cve_2026_18322_check.py -f targets.txt -t 20 --json results.json
python3 poc/cve_2026_18322_check.py -f targets.txt --csv results.csv --only-vulnerable
# Through a proxy, ignoring TLS errors (lab use)
python3 poc/cve_2026_18322_check.py https://staging.internal -k --proxy http://127.0.0.1:8080
Los objetivos pueden indicarse como argumentos o mediante -f (- lee de stdin). Un nombre de host
sin esquema se asume como https://.
$ python3 poc/cve_2026_18322_check.py https://example.com -v
[VULNERABLE] https://example.com/ (Smart Popup by Supsystic 1.11.2) < 1.13.0 affected by CVE-2026-18322; upgrade to 1.13.0+
- asset-version: plugin asset enqueued with ?ver=1.11.2 (https://example.com/)
- homepage-reference: references /plugins/popup-by-supsystic/ (https://example.com/)
- readme: plugin readme.txt is publicly readable (https://example.com/wp-content/plugins/popup-by-supsystic/readme.txt)
- readme-stable-tag: Stable tag: 1.11.2 (.../readme.txt)
| Estado | Significado |
|---|---|
VULNERABLE | Plugin detectado en < 1.13.0 |
NOT_VULNERABLE | Plugin detectado en >= 1.13.0 |
PLUGIN_DETECTED_VERSION_UNKNOWN | Plugin presente, versión no determinable — verificar manualmente |
PLUGIN_NOT_DETECTED | Sin evidencia del plugin (no es prueba de ausencia) |
ERROR | Objetivo inalcanzable o malformado |
Códigos de salida: 0 = sin objetivos vulnerables, 1 = al menos uno vulnerable, 2 = error de uso.
Dos señales pasivas, ambas GETs HTTP simples de recursos públicos:
?ver=PPS_VERSION
(classes/frame.php:410,473), por lo que la página de inicio a menudo filtra la versión exacta.readme.txt — Stable tag: es autoritativo y tiene precedencia sobre un asset en caché
potencialmente obsoleto; el changelog sirve como respaldo.El escaneo de la página de inicio también descubre directorios wp-content renombrados (p. ej. /app/
de Bedrock) para que la búsqueda del readme siga la estructura real del sitio.
La herramienta de detección no intenta la explotación. Aquí no se publica ningún exploit armamentizado para este CVE: el único código que ejecuta la cadena completa está estrictamente restringido al laboratorio local (véase más abajo).
El comprobador pasivo solo realiza fingerprinting de versiones. Su HttpClient no expone ningún
verbo HTTP distinto de GET — impuesto por construcción y verificado en la suite de tests, por lo
que no puede emitir una petición que cambie el estado ni siquiera por error. Nunca llama a
popup::save, envía un parámetro sub_wp_create_user_role, envía un formulario de suscripción,
dispara un correo de confirmación, ni crea o modifica ningún usuario, popup o ajuste. Lee dos recursos
públicos — la página de inicio y readme.txt — y nada más.
Esa seguridad tiene un coste, dicho claramente: el veredicto es tan bueno como los metadatos de versión que publique el host. Compromisos y modos de fallo: §9.