""" ๐ก๏ธ AI Security Scanner Downloads tools on first launch, then runs normally. """ import gradio as gr import subprocess import os import json import re import urllib.request import zipfile import tarfile import stat import shutil from datetime import datetime from pathlib import Path # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ # TOOL SETUP - Downloads pre-built binaries on first run # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ BIN_DIR = os.path.join(os.path.expanduser("~"), "bin") RESULTS_DIR = "/tmp/scan-results" WORDLIST_PATH = "/tmp/wordlist.txt" os.makedirs(BIN_DIR, exist_ok=True) os.makedirs(RESULTS_DIR, exist_ok=True) os.environ["PATH"] = BIN_DIR + ":" + os.environ.get("PATH", "") TOOLS = { "subfinder": { "url": "https://github.com/projectdiscovery/subfinder/releases/download/v2.6.7/subfinder_2.6.7_linux_amd64.zip", "type": "zip", "binary": "subfinder" }, "httpx": { "url": "https://github.com/projectdiscovery/httpx/releases/download/v1.6.9/httpx_1.6.9_linux_amd64.zip", "type": "zip", "binary": "httpx" }, "nuclei": { "url": "https://github.com/projectdiscovery/nuclei/releases/download/v3.3.7/nuclei_3.3.7_linux_amd64.zip", "type": "zip", "binary": "nuclei" }, "gau": { "url": "https://github.com/lc/gau/releases/download/v2.2.4/gau_2.2.4_linux_amd64.tar.gz", "type": "tar", "binary": "gau" }, "dalfox": { "url": "https://github.com/hahwul/dalfox/releases/download/v2.9.3/dalfox_2.9.3_linux_amd64.tar.gz", "type": "tar", "binary": "dalfox" }, "ffuf": { "url": "https://github.com/ffuf/ffuf/releases/download/v2.1.0/ffuf_2.1.0_linux_amd64.tar.gz", "type": "tar", "binary": "ffuf" }, } WORDLIST_CONTENT = """admin api .env .git .gitignore backup config console dashboard db debug dev docs download error files graphql health help images info internal login logout logs metrics monitor panel phpinfo private readme register reset robots.txt rss search security server-status settings setup signin signup sitemap.xml staging static status swagger system test tmp token upload uploads user users version wp-admin wp-content wp-login.php xmlrpc.php """ def download_tool(name, info): """Download and extract a single tool.""" binary_path = os.path.join(BIN_DIR, info["binary"]) if os.path.exists(binary_path): return True # Already downloaded tmp = f"/tmp/dl_{name}" os.makedirs(tmp, exist_ok=True) try: print(f"โฌ๏ธ Downloading {name}...") if info["type"] == "zip": dl_path = f"{tmp}/{name}.zip" urllib.request.urlretrieve(info["url"], dl_path) with zipfile.ZipFile(dl_path, 'r') as z: z.extractall(tmp) else: dl_path = f"{tmp}/{name}.tar.gz" urllib.request.urlretrieve(info["url"], dl_path) with tarfile.open(dl_path, 'r:gz') as t: t.extractall(tmp) # Find and move binary for root, dirs, files in os.walk(tmp): for f in files: if f == info["binary"]: src = os.path.join(root, f) shutil.copy2(src, binary_path) os.chmod(binary_path, os.stat(binary_path).st_mode | stat.S_IEXEC | stat.S_IXGRP | stat.S_IXOTH) print(f"โ {name} installed") return True print(f"โ ๏ธ Binary not found for {name}") return False except Exception as e: print(f"โ Failed to install {name}: {e}") return False finally: shutil.rmtree(tmp, ignore_errors=True) def setup_tools(): """Download all tools on startup.""" print("=" * 50) print("๐ก๏ธ Setting up security tools...") print("=" * 50) status = {} for name, info in TOOLS.items(): status[name] = download_tool(name, info) # Create wordlist if not os.path.exists(WORDLIST_PATH): with open(WORDLIST_PATH, "w") as f: f.write(WORDLIST_CONTENT.strip()) print("โ Wordlist created") installed = sum(1 for v in status.values() if v) print(f"\nโ {installed}/{len(TOOLS)} tools ready") print("=" * 50) return status # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ # SCANNER FUNCTIONS # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ def clean(target): t = target.strip().lower() t = re.sub(r'^https?://', '', t).rstrip('/') if not re.match(r'^[a-zA-Z0-9][a-zA-Z0-9\-\.]*\.[a-zA-Z]{2,}$', t): return None return t def run(cmd, timeout=300): try: p = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout) return p.stdout, p.stderr, p.returncode except subprocess.TimeoutExpired: return "", "โฐ Timeout", 1 except FileNotFoundError: return "", "โ Tool not installed", 1 except Exception as e: return "", str(e), 1 def hdr(tool, target): return f"{'='*55}\n๐ง {tool}\n๐ฏ {target}\n๐ {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n{'='*55}\n" def do_subfinder(target): t = clean(target) if not t: return "โ Invalid domain format. Use: example.com" out = f"{RESULTS_DIR}/subfinder_{t}.txt" if os.path.exists(out): os.remove(out) stdout, stderr, _ = run(["subfinder", "-d", t, "-silent", "-o", out], 120) res = hdr("Subfinder โ Subdomain Discovery", t) if os.path.exists(out): data = open(out).read().strip() n = len(data.splitlines()) if data else 0 res += f"\nโ Found {n} subdomains\n\n" + (data if data else "No subdomains found.") else: res += f"\n{stdout}\n{stderr}" return res def do_httpx(target): t = clean(target) if not t: return "โ Invalid domain format. Use: example.com" sf = f"{RESULTS_DIR}/subfinder_{t}.txt" if os.path.exists(sf): cmd = ["httpx", "-l", sf, "-silent", "-status-code", "-title", "-tech-detect"] else: cmd = ["httpx", "-u", t, "-silent", "-status-code", "-title", "-tech-detect"] stdout, stderr, _ = run(cmd, 180) res = hdr("HTTPX โ HTTP Probe & Tech Detect", t) res += f"\n{stdout}" if stdout.strip() else "\nNo live hosts found." return res def do_gau(target): t = clean(target) if not t: return "โ Invalid domain format. Use: example.com" out = f"{RESULTS_DIR}/gau_{t}.txt" if os.path.exists(out): os.remove(out) run(["gau", t, "--threads", "5", "--o", out], 120) res = hdr("GAU โ URL Discovery", t) if os.path.exists(out): data = open(out).read().strip() lines = data.splitlines() if data else [] res += f"\nโ Found {len(lines)} URLs\n\n" res += "\n".join(lines[:150]) if len(lines) > 150: res += f"\n\n... +{len(lines)-150} more" else: res += "\nNo URLs found." return res def do_nuclei(target, severity): t = clean(target) if not t: return "โ Invalid domain format. Use: example.com" out = f"{RESULTS_DIR}/nuclei_{t}.json" if os.path.exists(out): os.remove(out) cmd = ["nuclei", "-u", f"https://{t}", "-severity", severity, "-silent", "-jsonl", "-o", out, "-rate-limit", "50", "-no-update-templates"] stdout, stderr, _ = run(cmd, 600) res = hdr("Nuclei โ Vulnerability Scan", t) res += f"๐ Severity: {severity}\n" if os.path.exists(out): data = open(out).read().strip() if data: findings = data.splitlines() res += f"\n๐จ {len(findings)} vulnerabilities found\n\n" for line in findings: try: j = json.loads(line) s = j.get("info", {}).get("severity", "?").upper() name = j.get("info", {}).get("name", "N/A") url = j.get("matched-at", "N/A") tid = j.get("template-id", "") icon = {"CRITICAL": "๐ด", "HIGH": "๐ ", "MEDIUM": "๐ก", "LOW": "๐ข"}.get(s, "โช") res += f"{icon} [{s}] {name}\n Template: {tid}\n URL: {url}\n\n" except: res += line + "\n" else: res += "\nโ No vulnerabilities found at this severity." else: res += f"\n{stdout}\n{stderr}" return res def do_dalfox(target): t = clean(target) if not t: return "โ Invalid domain format. Use: example.com" out = f"{RESULTS_DIR}/dalfox_{t}.txt" if os.path.exists(out): os.remove(out) gau_f = f"{RESULTS_DIR}/gau_{t}.txt" if os.path.exists(gau_f): urls = [l.strip() for l in open(gau_f) if "?" in l and "=" in l] if urls: inp = f"{RESULTS_DIR}/dalfox_in_{t}.txt" open(inp, "w").write("\n".join(urls[:30])) cmd = ["dalfox", "file", inp, "-o", out, "--silence", "--worker", "5", "--timeout", "10"] else: cmd = ["dalfox", "url", f"https://{t}", "-o", out, "--silence", "--worker", "5"] else: cmd = ["dalfox", "url", f"https://{t}", "-o", out, "--silence", "--worker", "5"] stdout, stderr, _ = run(cmd, 300) res = hdr("DalFox โ XSS Scanner", t) if os.path.exists(out): data = open(out).read().strip() if data: res += f"\n๐จ {len(data.splitlines())} potential XSS found\n\n{data}" else: res += "\nโ No XSS found." else: res += f"\n{stdout}\n{stderr}" return res def do_ffuf(target): t = clean(target) if not t: return "โ Invalid domain format. Use: example.com" out = f"{RESULTS_DIR}/ffuf_{t}.json" if os.path.exists(out): os.remove(out) cmd = ["ffuf", "-u", f"https://{t}/FUZZ", "-w", WORDLIST_PATH, "-mc", "200,201,301,302,403", "-t", "20", "-timeout", "10", "-o", out, "-of", "json", "-s"] stdout, stderr, _ = run(cmd, 180) res = hdr("FFUF โ Directory Fuzzer", t) if os.path.exists(out): try: items = json.load(open(out)).get("results", []) res += f"\nโ Found {len(items)} paths\n\n" for r in items: s = r.get("status", "?") u = r.get("url", "?") icon = {200: "๐ข", 301: "๐ต", 302: "๐ต", 403: "๐ก"}.get(s, "โช") res += f"{icon} [{s}] {u}\n" except: res += stdout else: res += f"\n{stdout}\n{stderr}" return res def do_full(target, severity): t = clean(target) if not t: return ("โ Invalid domain format. Use: example.com",) * 6 return (do_subfinder(t), do_httpx(t), do_gau(t), do_nuclei(t, severity), do_dalfox(t), do_ffuf(t)) def check_tools(): """Check which tools are installed.""" lines = [] for name in TOOLS: path = os.path.join(BIN_DIR, name) if os.path.exists(path): lines.append(f"โ {name} โ installed") else: lines.append(f"โ {name} โ NOT installed") return "\n".join(lines) # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ # INSTALL TOOLS ON STARTUP # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ TOOL_STATUS = setup_tools() # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ # GRADIO UI # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ CSS = """ .gradio-container { max-width: 1200px !important; } .hdr { text-align:center; padding:1.5rem; background:linear-gradient(135deg,#0a0e17,#1a1a2e,#16213e); border-radius:12px; margin-bottom:1rem; border-bottom:2px solid #06d6a0; } .hdr h1 { font-size:2rem; background:linear-gradient(90deg,#06d6a0,#00b4d8); -webkit-background-clip:text; -webkit-text-fill-color:transparent; margin:0; } .hdr p { color:#94a3b8; margin-top:0.3rem; font-size:0.9rem; } .warn { background:rgba(239,68,68,0.08); border:1px solid #ef4444; border-radius:8px; padding:0.8rem; text-align:center; color:#f59e0b; font-weight:600; margin-bottom:1rem; } footer { display:none !important; } """ with gr.Blocks(title="Security Scanner", css=CSS) as app: gr.HTML("""
subfinder ยท httpx ยท gau ยท nuclei ยท dalfox ยท ffuf