
Reproduce CVE-2026-1581, una inyección SQL basada en tiempo no autenticada en wpForo Forum <=2.4.14, con un laboratorio Docker y PoC para demostrar la vulnerabilidad y verificar el parche.
| Campo | Detalle |
|---|---|
| ID CVE | CVE-2026-1581 |
| Plugin | wpForo Forum |
| Versiones Afectadas | <= 2.4.14 |
| Versión Corregida | 2.4.15 |
| Tipo de Vulnerabilidad | Inyección SQL Basada en Tiempo No Autenticada (ORDER BY) |
| Puntuación CVSS | 7.5 (Alta) |
| Vector CVSS | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N |
CVE-2026-1581 es una vulnerabilidad de Inyección SQL Basada en Tiempo No Autenticada en el plugin wpForo Forum (<= 2.4.14). El parámetro wpfob se utiliza en una cláusula ORDER BY con solo saneamiento de texto aplicado, lo que permite a un atacante no autenticado inyectar expresiones SQL arbitrarias y leer datos de la base de datos.
El proveedor corrigió esto en la versión 2.4.15 reemplazando sanitize_text_field() por wpforo_sanitize_orderby(), que aplica una lista blanca consciente del contexto.
Ejecutar solo en localhost + Docker Compose.
El PoC es una prueba de sincronización basada en tiempo para demostrar la diferencia entre las versiones vulnerable y corregida.
No utilizar contra ningún sistema sin autorización explícita.
Prueba de versión: La página /community/ carga /wp-content/plugins/wpforo/assets/js/frontend.js?ver=2.4.14 (vulnerable) vs 2.4.15 (corregida).
Prueba de código: sanitize_text_field(WPF()->GET['wpfob']) → wpforo_sanitize_orderby(..., context, default)
Prueba de comportamiento: wpfob=modified,(SELECT SLEEP(5)) causa un retraso de ~5s en la versión vulnerable; la versión corregida responde cerca de la línea base.
El aviso CVE solo indica que se trata de una inyección SQL basada en tiempo a través del parámetro wpfob, corregida en 2.4.15. En el momento del análisis, no había ningún PoC público disponible.
Por lo tanto, este informe se construyó mediante comparación de código fuente entre 2.4.14 y 2.4.15, rastreando el parámetro desde la entrada HTTP a través del saneamiento hasta el punto donde se utiliza para construir la consulta SQL — con el fin de comprender la causa raíz y reproducir el problema.

wpfobComenzando con una búsqueda de wpfob en el código fuente, se encontró que la página Recientes toma el valor directamente de un parámetro GET y lo asigna como argumento orderby.

Vulnerable (2.4.14) — themes/classic/recent.php:
32 | $args['orderby'] = ( ! empty( WPF()->GET['wpfob'] ) ) ? sanitize_text_field( WPF()->GET['wpfob'] ) : 'modified';
74 | $args['orderby'] = ( ! empty( WPF()->GET['wpfob'] ) ) ? sanitize_text_field( WPF()->GET['wpfob'] ) : 'created';
Corregido (2.4.15) — mismo archivo, saneador reemplazado:
32 | $args['orderby'] = ( ! empty( WPF()->GET['wpfob'] ) ) ? wpforo_sanitize_orderby( WPF()->GET['wpfob'], 'topics', 'modified' ) : 'modified';
74 | $args['orderby'] = ( ! empty( WPF()->GET['wpfob'] ) ) ? wpforo_sanitize_orderby( WPF()->GET['wpfob'], 'posts', 'created' ) : 'created';
¿Por qué centrarse en
recent.php? Porque es una ruta activable dondewpfobse asigna directamente a$args['orderby'].
ORDER BY ...Una vez que $args['orderby'] está establecido, fluye hacia el constructor de consultas de wpForo para construir la cláusula ORDER BY.
Concatenación de ORDER BY (vuln 2.4.14)
classes/Topics.php:

classes/Posts.php:

Explicación
sanitize_text_field() solo elimina/limpia la cadena — no aplica una lista blanca de nombres de columnas permitidos.orderby se concatena directamente en ORDER BY <orderby>, un atacante puede inyectar expresiones SQL arbitrarias en la posición de ORDER BY.Referencia: https://developer.wordpress.org/reference/functions/sanitize_text_field/
recent.php32c32
< $args['orderby'] = ( ! empty( WPF()->GET['wpfob'] ) ) ? sanitize_text_field( WPF()->GET['wpfob'] ) : 'modified';
---
> $args['orderby'] = ( ! empty( WPF()->GET['wpfob'] ) ) ? wpforo_sanitize_orderby( WPF()->GET['wpfob'], 'topics', 'modified' ) : 'modified';
74c74
< $args['orderby'] = ( ! empty( WPF()->GET['wpfob'] ) ) ? sanitize_text_field( WPF()->GET['wpfob'] ) : 'created';
---
> $args['orderby'] = ( ! empty( WPF()->GET['wpfob'] ) ) ? wpforo_sanitize_orderby( WPF()->GET['wpfob'], 'posts', 'created' ) : 'created';
wpforo.php1036c1036
< $args['orderby'] = sanitize_text_field( $get['wpfob'] );
---
> $args['orderby'] = wpforo_sanitize_orderby( $get['wpfob'], 'search', 'relevancy' );
1077c1077
< $args['orderby'] = ( ! empty( WPF()->GET['wpfob'] ) ) ? sanitize_text_field( WPF()->GET['wpfob'] ) : 'modified';
---
> $args['orderby'] = ( ! empty( WPF()->GET['wpfob'] ) ) ? wpforo_sanitize_orderby( WPF()->GET['wpfob'], 'topics', 'modified' ) : 'modified';
1153c1153
< $args['orderby'] = ( ! empty( WPF()->GET['wpfob'] ) ) ? sanitize_text_field( WPF()->GET['wpfob'] ) : 'created';
---
> $args['orderby'] = ( ! empty( WPF()->GET['wpfob'] ) ) ? wpforo_sanitize_orderby( WPF()->GET['wpfob'], 'posts', 'created' ) : 'created';
wpforo_sanitize_orderby()La versión 2.4.15 introduce un saneador de lista blanca consciente del contexto que devuelve el valor predeterminado si la entrada no está en la lista permitida:

wp_vuln (WordPress + wpForo 2.4.14) → http://localhost:8081wp_patched (WordPress + wpForo 2.4.15) → http://localhost:8082db_vuln / db_patched (MariaDB)seed_vuln / seed_patched — utiliza wp-cli para instalar WordPress, instalar el plugin, crear la página /community/ con el shortcode [wpforo], configurar enlaces permanentes, generar .htaccess y crear artefactos de verificación.Al leer el código fuente, wpfob se utiliza explícitamente en la página recientes:
http://localhost:8081/community/recent/?view=openedhttp://localhost:8082/community/recent/?view=openedDebe existir al menos 1 tema y 1 publicación antes de probar.