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.
562 lines
20 KiB
Python
562 lines
20 KiB
Python
#!/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()
|