The Analyzer v2.0 — 30 attack vectors, 9 new exploiters
New vectors added: - 22: SSRF Proof — cloud metadata exfiltration (CRITICAL) - 23: Prototype Pollution — Node.js client/server (HIGH) - 24: WebSocket Hijack — WS origin bypass + injection (HIGH) - 25: Mass Assignment — protected field modification (HIGH) - 26: HTTP Parameter Pollution — WAF bypass (HIGH) - 27: Insecure Deserialization — PHP/Java/Node (CRITICAL) - 28: OAuth Takeover — redirect_uri / state / CSRF (CRITICAL) - 29: Web Cache Poisoning — unkeyed header injection (HIGH) - 30: CRLF Injection — HTTP response splitting (CRITICAL) All vectors PROVE exploitation by dumping data/credentials, not just detecting config issues.
This commit is contained in:
561
engine/hunter.py
Normal file
561
engine/hunter.py
Normal file
@@ -0,0 +1,561 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
No-API Vulnerability Hunter
|
||||
Discovers vulnerable websites using only free/open data sources
|
||||
Pipeline: Target Discovery → Tech Detection → CVE Scanning → Exploitation
|
||||
"""
|
||||
import subprocess, json, sys, os, time
|
||||
from pathlib import Path
|
||||
from collections import Counter
|
||||
from urllib.parse import urlparse
|
||||
|
||||
ANALYZER_DIR = os.path.expanduser("~/the-analyzer")
|
||||
REPORTS_DIR = f"{ANALYZER_DIR}/reports"
|
||||
TARGETS_DIR = f"{ANALYZER_DIR}/targets"
|
||||
WORK_DIR = "/tmp/analyzer-hunter"
|
||||
HTTPX = os.path.expanduser("~/go/bin/httpx")
|
||||
NUCLEI_TEMPLATES = os.path.expanduser("~/nuclei-templates")
|
||||
|
||||
os.makedirs(WORK_DIR, exist_ok=True)
|
||||
os.makedirs(REPORTS_DIR, exist_ok=True)
|
||||
os.makedirs(TARGETS_DIR, exist_ok=True)
|
||||
|
||||
def log(msg): print(f"\033[94m[*]\033[0m {msg}")
|
||||
def ok(msg): print(f"\033[92m[✓]\033[0m {msg}")
|
||||
def warn(msg): print(f"\033[93m[!]\033[0m {msg}")
|
||||
def bad(msg): print(f"\033[91m[✗]\033[0m {msg}")
|
||||
|
||||
def run(cmd, timeout=120):
|
||||
"""Run a shell command and return output"""
|
||||
try:
|
||||
r = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=timeout)
|
||||
return r.stdout, r.stderr, r.returncode
|
||||
except subprocess.TimeoutExpired:
|
||||
return "", "TIMEOUT", 124
|
||||
|
||||
# ============================================================
|
||||
# PHASE 1: TARGET DISCOVERY
|
||||
# ============================================================
|
||||
|
||||
def discover_tranco_deep(count=2000, offset=50000):
|
||||
"""Get mid-tier sites from Tranco (50K-52K range)"""
|
||||
log(f"Fetching Tranco sites (offset={offset}, count={count})...")
|
||||
|
||||
# Download if not cached
|
||||
csv_file = f"{WORK_DIR}/top-1m.csv"
|
||||
if not os.path.exists(csv_file):
|
||||
out, _, _ = run("curl -skL https://tranco-list.eu/top-1m.csv.zip -o /tmp/t1m.zip && unzip -o /tmp/t1m.zip -d /tmp/tranco/ 2>/dev/null && echo OK", timeout=30)
|
||||
if "OK" in out:
|
||||
os.system("cp /tmp/tranco/top-1m.csv " + csv_file)
|
||||
|
||||
if os.path.exists(csv_file):
|
||||
targets = []
|
||||
with open(csv_file) as f:
|
||||
for i, line in enumerate(f):
|
||||
if i < offset: continue
|
||||
if i >= offset + count: break
|
||||
parts = line.strip().split(",")
|
||||
if len(parts) >= 2:
|
||||
targets.append(parts[1].strip())
|
||||
|
||||
outfile = f"{WORK_DIR}/tranco_deep_targets.txt"
|
||||
with open(outfile, "w") as f:
|
||||
f.write("\n".join(targets))
|
||||
ok(f"{len(targets)} targets from Tranco (offset {offset})")
|
||||
return outfile
|
||||
|
||||
def discover_bug_bounty_targets():
|
||||
"""Add known bug bounty targets for subdomain recon"""
|
||||
targets = [
|
||||
# High-value targets that often have bug bounty programs
|
||||
"hackerone.com", "bugcrowd.com", "intigriti.com",
|
||||
"yeswehack.com", "synack.com", "cobalt.io",
|
||||
# Major platforms with bounty programs
|
||||
"facebook.com", "twitter.com", "instagram.com", "linkedin.com",
|
||||
"github.com", "gitlab.com", "atlassian.com", "slack.com",
|
||||
"shopify.com", "stripe.com", "square.com", "paypal.com",
|
||||
"discord.com", "reddit.com", "twitch.com", "spotify.com",
|
||||
"cloudflare.com", "digitalocean.com", "heroku.com",
|
||||
# E-commerce (user's niche)
|
||||
"magento.com", "shopware.com", "woocommerce.com",
|
||||
"bigcommerce.com", "salesforce.com", "hubspot.com",
|
||||
# Google
|
||||
"google.com", "youtube.com", "gmail.com", "android.com",
|
||||
# Microsoft
|
||||
"microsoft.com", "office.com", "azure.com", "live.com",
|
||||
# Apple
|
||||
"apple.com", "icloud.com",
|
||||
]
|
||||
|
||||
# Also add targets from the Analyzer's list
|
||||
analyzer_targets = f"{TARGETS_DIR}/top50.txt"
|
||||
if os.path.exists(analyzer_targets):
|
||||
with open(analyzer_targets) as f:
|
||||
targets.extend([l.strip() for l in f if l.strip()])
|
||||
|
||||
outfile = f"{WORK_DIR}/bb_targets.txt"
|
||||
with open(outfile, "w") as f:
|
||||
f.write("\n".join(sorted(set(targets))))
|
||||
|
||||
return outfile
|
||||
|
||||
def discover_commoncrawl_vuln_patterns():
|
||||
"""Search CommonCrawl for URLs matching vulnerable software patterns"""
|
||||
log("Searching CommonCrawl for vulnerable software patterns...")
|
||||
|
||||
CC_INDEX = "CC-MAIN-2026-21"
|
||||
BASE = f"http://index.commoncrawl.org/{CC_INDEX}-index"
|
||||
|
||||
patterns = [
|
||||
("phpMyAdmin", "phpmyadmin"),
|
||||
("WordPress admin", "wp-admin"),
|
||||
("WordPress plugins", "wp-content/plugins"),
|
||||
("Jenkins", "jenkins"),
|
||||
("phpinfo()", "phpinfo.php"),
|
||||
("Server status", "server-status"),
|
||||
(".env files", ".env"),
|
||||
("Actuator/Spring", "actuator"),
|
||||
("Git exposure", ".git/config"),
|
||||
("Laravel debug", "laravel/debug"),
|
||||
]
|
||||
|
||||
targets = set()
|
||||
for name, pattern in patterns:
|
||||
try:
|
||||
import urllib.request, urllib.parse, json
|
||||
url = f"{BASE}?url=*.{pattern}/*&output=json&limit=20"
|
||||
r = urllib.request.urlopen(url, timeout=10)
|
||||
data = r.read().decode()
|
||||
|
||||
for line in data.strip().split("\n"):
|
||||
if not line.strip(): continue
|
||||
try:
|
||||
d = json.loads(line)
|
||||
u = d.get("url", "")
|
||||
if u:
|
||||
parsed = urlparse(u)
|
||||
domain = parsed.netloc or parsed.path.split("/")[0]
|
||||
targets.add(domain)
|
||||
except: pass
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
if targets:
|
||||
outfile = f"{WORK_DIR}/commoncrawl_targets.txt"
|
||||
with open(outfile, "w") as f:
|
||||
f.write("\n".join(sorted(targets)))
|
||||
ok(f"{len(targets)} targets from CommonCrawl patterns")
|
||||
return outfile
|
||||
else:
|
||||
warn("CommonCrawl returned no targets (API limiting)")
|
||||
return None
|
||||
|
||||
# ============================================================
|
||||
# PHASE 2: SUBDOMAIN ENUMERATION
|
||||
# ============================================================
|
||||
|
||||
def enumerate_subdomains_shodan(domain):
|
||||
"""Use free Shodan DNS API for subdomain discovery"""
|
||||
try:
|
||||
import urllib.request, json
|
||||
key = "8IPLCrASad9cLHqo6xwzGNxOPcldnGDG"
|
||||
url = f"https://api.shodan.io/dns/domain/{domain}?key={key}"
|
||||
r = urllib.request.urlopen(url, timeout=10)
|
||||
data = json.loads(r.read().decode())
|
||||
|
||||
subdomains = data.get("subdomains", [])
|
||||
full_domains = [f"{sd}.{domain}" for sd in subdomains]
|
||||
|
||||
if full_domains:
|
||||
ok(f"Found {len(full_domains)} subdomains for {domain}")
|
||||
return full_domains
|
||||
return []
|
||||
except Exception as e:
|
||||
warn(f"Shodan DNS for {domain}: {e}")
|
||||
return []
|
||||
|
||||
# ============================================================
|
||||
# PHASE 3: TECH DETECTION
|
||||
# ============================================================
|
||||
|
||||
def tech_detect(targets_file, output_file=None):
|
||||
"""Run httpx tech detection on targets"""
|
||||
log(f"Tech detection on targets from {targets_file}...")
|
||||
|
||||
if not output_file:
|
||||
output_file = f"{WORK_DIR}/tech_detected.json"
|
||||
|
||||
stdout, stderr, rc = run(
|
||||
f"cat {targets_file} | {HTTPX} -rl 30 -timeout 5 -tech-detect -j -o {output_file} -silent 2>/dev/null",
|
||||
timeout=180
|
||||
)
|
||||
|
||||
count = 0
|
||||
if os.path.exists(output_file):
|
||||
with open(output_file) as f:
|
||||
count = sum(1 for _ in f)
|
||||
|
||||
return output_file, count
|
||||
|
||||
def tech_summary(tech_file):
|
||||
"""Summarize detected technologies"""
|
||||
techs = Counter()
|
||||
targets = []
|
||||
|
||||
with open(tech_file) as f:
|
||||
for line in f:
|
||||
try:
|
||||
d = json.loads(line)
|
||||
url = d.get("url", "?")
|
||||
targets.append(url)
|
||||
for t in d.get("tech", []):
|
||||
techs[t] += 1
|
||||
except: pass
|
||||
|
||||
return targets, techs
|
||||
|
||||
# ============================================================
|
||||
# PHASE 4: TARGETED CVE SCANNING
|
||||
# ============================================================
|
||||
|
||||
def scan_with_nuclei(targets_file, severity="critical,high", output_file=None):
|
||||
"""Run nuclei CVE scan on targets"""
|
||||
log(f"Nuclei CVE scan (severity: {severity})...")
|
||||
|
||||
if not output_file:
|
||||
output_file = f"{REPORTS_DIR}/hunt_scan_{int(time.time())}.json"
|
||||
|
||||
stdout, stderr, rc = run(
|
||||
f"nuclei -l {targets_file} -j -rl 15 -c 8 "
|
||||
f"-t {NUCLEI_TEMPLATES}/http/cves/ "
|
||||
f"-severity {severity} "
|
||||
f"-o {output_file} "
|
||||
f"-silent 2>/dev/null",
|
||||
timeout=300
|
||||
)
|
||||
|
||||
count = 0
|
||||
if os.path.exists(output_file):
|
||||
with open(output_file) as f:
|
||||
count = sum(1 for _ in f)
|
||||
|
||||
return output_file, count
|
||||
|
||||
def tech_targeted_scan(tech_file):
|
||||
"""Run technology-specific CVE scans based on detected tech"""
|
||||
log("Running tech-targeted CVE scans...")
|
||||
|
||||
# Read tech data
|
||||
tech_targets = {}
|
||||
with open(tech_file) as f:
|
||||
for line in f:
|
||||
try:
|
||||
d = json.loads(line)
|
||||
url = d.get("url", "")
|
||||
for t in d.get("tech", []):
|
||||
tech_targets.setdefault(t, []).append(url)
|
||||
except: pass
|
||||
|
||||
# Map tech to relevant CVE template tags
|
||||
tech_cve_map = {
|
||||
"WordPress": ["wordpress", "cves"],
|
||||
"Apache HTTP Server": ["apache", "cves"],
|
||||
"Nginx": ["nginx", "cves"],
|
||||
"PHP": ["php", "cves"],
|
||||
"Jenkins": ["jenkins", "cves"],
|
||||
"jQuery": [],
|
||||
"Bootstrap": [],
|
||||
"MySQL": ["mysql", "cves"],
|
||||
"OpenSSL": ["openssl", "cves"],
|
||||
"OpenSSH": ["openssh", "cves"],
|
||||
"phpMyAdmin": ["phpmyadmin", "cves"],
|
||||
"Tomcat": ["tomcat", "cves"],
|
||||
"Drupal": ["drupal", "cves"],
|
||||
"Joomla": ["joomla", "cves"],
|
||||
"GitLab": ["gitlab", "cves"],
|
||||
"Jenkins": ["jenkins", "cves"],
|
||||
}
|
||||
|
||||
all_findings = []
|
||||
|
||||
for tech, urls in tech_targets.items():
|
||||
# Check if this tech has known CVE templates
|
||||
matched_tech = None
|
||||
for known_tech, tags in tech_cve_map.items():
|
||||
if known_tech.lower() in tech.lower() or tech.lower() in known_tech.lower():
|
||||
matched_tech = known_tech
|
||||
break
|
||||
|
||||
if not matched_tech:
|
||||
continue
|
||||
|
||||
# Write targets for this tech
|
||||
tech_file = f"{WORK_DIR}/tech_{tech.lower().replace(' ', '_')}.txt"
|
||||
with open(tech_file, "w") as f:
|
||||
f.write("\n".join(urls[:20]))
|
||||
|
||||
# Run focused scan
|
||||
tech_output = f"{WORK_DIR}/scan_{tech.lower().replace(' ', '_')}.json"
|
||||
stdout, stderr, rc = run(
|
||||
f"nuclei -l {tech_file} -j -rl 10 -c 5 "
|
||||
f"-t {NUCLEI_TEMPLATES}/http/cves/ "
|
||||
f"-severity critical,high,medium "
|
||||
f"-o {tech_output} -silent 2>/dev/null",
|
||||
timeout=120
|
||||
)
|
||||
|
||||
if os.path.exists(tech_output):
|
||||
with open(tech_output) as f:
|
||||
findings = [l for l in f if l.strip()]
|
||||
if findings:
|
||||
all_findings.extend(findings)
|
||||
ok(f"{tech}: {len(findings)} findings")
|
||||
|
||||
# Merge all findings
|
||||
merged_file = f"{REPORTS_DIR}/tech_targeted_{int(time.time())}.json"
|
||||
with open(merged_file, "w") as f:
|
||||
f.write("\n".join(all_findings))
|
||||
|
||||
return merged_file, len(all_findings)
|
||||
|
||||
# ============================================================
|
||||
# PHASE 5: REPORT
|
||||
# ============================================================
|
||||
|
||||
def generate_report(findings_file, tech_summary_data, targets_count):
|
||||
"""Generate a comprehensive report"""
|
||||
report_file = f"{REPORTS_DIR}/hunter_report_{int(time.time())}.md"
|
||||
|
||||
findings = []
|
||||
cves = Counter()
|
||||
severities = Counter()
|
||||
target_urls = set()
|
||||
|
||||
if os.path.exists(findings_file):
|
||||
with open(findings_file) as f:
|
||||
for line in f:
|
||||
if not line.strip(): continue
|
||||
try:
|
||||
d = json.loads(line)
|
||||
sev = d.get("info", {}).get("severity", "unknown")
|
||||
severities[sev] += 1
|
||||
|
||||
# Extract CVE
|
||||
cve_list = []
|
||||
for ref in d.get("info", {}).get("classification", {}).get("cve", []):
|
||||
cve_list.append(ref.get("id", ""))
|
||||
cve_id = cve_list[0] if cve_list else "N/A"
|
||||
for c in cve_list:
|
||||
cves[c] += 1
|
||||
|
||||
findings.append({
|
||||
"url": d.get("matched-at", d.get("host", "?")),
|
||||
"cve": cve_id,
|
||||
"severity": sev,
|
||||
"name": d.get("info", {}).get("name", "?"),
|
||||
"extracted": d.get("extracted-results", []),
|
||||
})
|
||||
target_urls.add(d.get("matched-at", d.get("host", "?")))
|
||||
except: pass
|
||||
|
||||
# Build report
|
||||
icons = {"critical": "🔴", "high": "🟠", "medium": "🟡", "low": "🔵", "unknown": "⚪"}
|
||||
|
||||
lines = []
|
||||
lines.append(f"# Mass Vulnerability Hunter Report")
|
||||
lines.append(f"Generated: {time.strftime('%Y-%m-%d %H:%M')}")
|
||||
lines.append(f"")
|
||||
lines.append(f"## Summary")
|
||||
lines.append(f"| Metric | Value |")
|
||||
lines.append(f"|--------|-------|")
|
||||
lines.append(f"| Targets Probed | {targets_count} |")
|
||||
if tech_summary_data:
|
||||
_, techs = tech_summary_data
|
||||
lines.append(f"| Live Hosts | {len(tech_summary_data[0])} |")
|
||||
lines.append(f"| Technologies Detected | {len(techs)} |")
|
||||
lines.append(f"| Total Findings | {len(findings)} |")
|
||||
lines.append(f"| Unique Vulnerable Hosts | {len(target_urls)} |")
|
||||
|
||||
for sev in ["critical", "high", "medium", "low"]:
|
||||
if severities[sev]:
|
||||
lines.append(f"| {icons.get(sev, '?')} {sev.capitalize()} | {severities[sev]} |")
|
||||
|
||||
if cves:
|
||||
lines.append(f"")
|
||||
lines.append(f"## CVEs Detected")
|
||||
for cve, cnt in cves.most_common(30):
|
||||
lines.append(f"- [{cve}](https://nvd.nist.gov/vuln/detail/{cve}): {cnt} occurrences")
|
||||
|
||||
if findings:
|
||||
lines.append(f"")
|
||||
lines.append(f"## All Findings")
|
||||
for f_data in findings:
|
||||
icon = icons.get(f_data["severity"], "?")
|
||||
lines.append(f"- {icon} [{f_data['cve']}] {f_data['name']} @ {f_data['url']}")
|
||||
if f_data["extracted"]:
|
||||
for ex in f_data["extracted"][:3]:
|
||||
lines.append(f" - `{ex}`")
|
||||
|
||||
if tech_summary_data:
|
||||
_, techs = tech_summary_data
|
||||
if techs:
|
||||
lines.append(f"")
|
||||
lines.append(f"## Detected Technologies")
|
||||
for tech, cnt in techs.most_common(20):
|
||||
lines.append(f"- {tech}: {cnt} targets")
|
||||
|
||||
with open(report_file, "w") as f:
|
||||
f.write("\n".join(lines))
|
||||
|
||||
return report_file, findings
|
||||
|
||||
# ============================================================
|
||||
# MAIN
|
||||
# ============================================================
|
||||
|
||||
def main():
|
||||
mode = sys.argv[1] if len(sys.argv) > 1 else "auto"
|
||||
|
||||
print()
|
||||
print("╔═══════════════════════════════════════════╗")
|
||||
print("║ NO-API VULNERABILITY HUNTER ║")
|
||||
print("╚═══════════════════════════════════════════╝")
|
||||
print()
|
||||
|
||||
merged_targets = f"{WORK_DIR}/merged_targets.txt"
|
||||
|
||||
if mode == "target" and len(sys.argv) >= 3:
|
||||
# Hunt a specific target domain
|
||||
domain = sys.argv[2]
|
||||
log(f"Hunting target domain: {domain}")
|
||||
|
||||
# Phase 1: Subdomain enumeration
|
||||
log("[1/4] Subdomain enumeration...")
|
||||
subdomains = enumerate_subdomains_shodan(domain)
|
||||
|
||||
if subdomains:
|
||||
ok(f"Discovered {len(subdomains)} subdomains for {domain}")
|
||||
with open(merged_targets, "w") as f:
|
||||
f.write("\n".join(subdomains))
|
||||
else:
|
||||
warn(f"No subdomains found via Shodan. Using root domain.")
|
||||
with open(merged_targets, "w") as f:
|
||||
f.write(domain)
|
||||
|
||||
elif mode == "deep":
|
||||
# Deep scan - target mid-tier Tranco sites
|
||||
log("[1/4] Target discovery (Tranco deep)...")
|
||||
targets_file = discover_tranco_deep(count=2000, offset=50000)
|
||||
if not targets_file:
|
||||
bad("Failed to get target list")
|
||||
return
|
||||
|
||||
# Also try CommonCrawl
|
||||
cc_file = discover_commoncrawl_vuln_patterns()
|
||||
|
||||
# Merge targets
|
||||
all_targets = []
|
||||
with open(targets_file) as f:
|
||||
all_targets.extend([l.strip() for l in f if l.strip()])
|
||||
|
||||
if cc_file and os.path.exists(cc_file):
|
||||
with open(cc_file) as f:
|
||||
all_targets.extend([l.strip() for l in f if l.strip()])
|
||||
|
||||
with open(merged_targets, "w") as f:
|
||||
f.write("\n".join(sorted(set(all_targets))))
|
||||
|
||||
ok(f"{len(set(all_targets))} unique targets")
|
||||
|
||||
else:
|
||||
# Auto mode - balanced approach
|
||||
log("[1/4] Target discovery...")
|
||||
|
||||
# Get targets from multiple sources
|
||||
tranco_file = discover_tranco_deep(count=500, offset=50000)
|
||||
|
||||
# Merge
|
||||
all_targets = []
|
||||
if tranco_file and os.path.exists(tranco_file):
|
||||
with open(tranco_file) as f:
|
||||
all_targets.extend([l.strip() for l in f if l.strip()])
|
||||
|
||||
with open(merged_targets, "w") as f:
|
||||
f.write("\n".join(sorted(set(all_targets))))
|
||||
|
||||
ok(f"{len(set(all_targets))} unique targets")
|
||||
|
||||
# Phase 2: Tech Detection
|
||||
targets_count = 0
|
||||
if os.path.exists(merged_targets):
|
||||
with open(merged_targets) as f:
|
||||
targets_count = sum(1 for _ in f)
|
||||
|
||||
if targets_count == 0:
|
||||
bad("No targets to scan")
|
||||
return
|
||||
|
||||
print()
|
||||
log(f"[2/4] Tech detection on {targets_count} targets...")
|
||||
tech_file, live_count = tech_detect(merged_targets)
|
||||
|
||||
if live_count == 0:
|
||||
warn(f"No live hosts found. Try different target range.")
|
||||
return
|
||||
|
||||
ok(f"{live_count} live hosts detected")
|
||||
|
||||
# Show tech summary
|
||||
targets, techs = tech_summary(tech_file)
|
||||
print(f"\nTop technologies:")
|
||||
for tech, cnt in techs.most_common(15):
|
||||
print(f" {tech:35} {cnt} targets")
|
||||
|
||||
# Phase 3: CVE Scanning
|
||||
print()
|
||||
log("[3/4] CVE scanning...")
|
||||
|
||||
# Run mass CVE scan (critical/high)
|
||||
mass_output, mass_count = scan_with_nuclei(merged_targets, "critical,high")
|
||||
ok(f"Mass CVE scan: {mass_count} findings")
|
||||
|
||||
# Run tech-targeted scans (medium too, since we know the tech)
|
||||
tech_output, tech_count = tech_targeted_scan(tech_file)
|
||||
ok(f"Tech-targeted scan: {tech_count} findings")
|
||||
|
||||
# Merge findings
|
||||
merged_findings = f"{REPORTS_DIR}/hunt_all_findings.json"
|
||||
all_findings = []
|
||||
for f in [mass_output, tech_output]:
|
||||
if os.path.exists(f):
|
||||
with open(f) as fh:
|
||||
all_findings.extend([l for l in fh if l.strip()])
|
||||
|
||||
with open(merged_findings, "w") as f:
|
||||
f.write("\n".join(all_findings))
|
||||
|
||||
# Phase 4: Report
|
||||
print()
|
||||
log("[4/4] Generating report...")
|
||||
report_file, findings = generate_report(merged_findings, (targets, techs), targets_count)
|
||||
|
||||
print()
|
||||
print("=" * 60)
|
||||
print(f" ✅ HUNT COMPLETE")
|
||||
print(f" Targets scanned: {targets_count}")
|
||||
print(f" Live hosts: {live_count}")
|
||||
print(f" Total findings: {len(findings)}")
|
||||
print(f" Report: {report_file}")
|
||||
print("=" * 60)
|
||||
|
||||
# Output findings summary
|
||||
if findings:
|
||||
print("\nTop CVEs found:")
|
||||
cves = Counter(f["cve"] for f in findings)
|
||||
for cve, cnt in cves.most_common(10):
|
||||
print(f" {cve}: {cnt} occurrences")
|
||||
|
||||
return report_file
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user