oxhunter 2.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- oxhunter-2.0.0/0xhunter.py +679 -0
- oxhunter-2.0.0/LICENSE +14 -0
- oxhunter-2.0.0/MANIFEST.in +15 -0
- oxhunter-2.0.0/PKG-INFO +656 -0
- oxhunter-2.0.0/README.md +584 -0
- oxhunter-2.0.0/ai/__init__.py +0 -0
- oxhunter-2.0.0/ai/attack_chaining.py +368 -0
- oxhunter-2.0.0/ai/exploit_generator.py +342 -0
- oxhunter-2.0.0/ai/fp_reducer.py +218 -0
- oxhunter-2.0.0/ai/nl_report.py +256 -0
- oxhunter-2.0.0/ai/payload_generator.py +177 -0
- oxhunter-2.0.0/ai/vuln_chaining.py +197 -0
- oxhunter-2.0.0/asn_scan.py +25 -0
- oxhunter-2.0.0/compliance/bug_bounty.py +128 -0
- oxhunter-2.0.0/compliance/owasp_mapping.py +95 -0
- oxhunter-2.0.0/compliance/pci_iso_report.py +144 -0
- oxhunter-2.0.0/config.yaml +44 -0
- oxhunter-2.0.0/core/__init__.py +0 -0
- oxhunter-2.0.0/core/auth.py +421 -0
- oxhunter-2.0.0/core/config.py +308 -0
- oxhunter-2.0.0/core/context_analyzer.py +198 -0
- oxhunter-2.0.0/core/crawler.py +227 -0
- oxhunter-2.0.0/core/cve_mapping.py +139 -0
- oxhunter-2.0.0/core/headless.py +186 -0
- oxhunter-2.0.0/core/http_client.py +432 -0
- oxhunter-2.0.0/core/logger.py +120 -0
- oxhunter-2.0.0/core/paths.py +19 -0
- oxhunter-2.0.0/core/payload_engine.py +354 -0
- oxhunter-2.0.0/core/pdf_report.py +565 -0
- oxhunter-2.0.0/core/proxy.py +142 -0
- oxhunter-2.0.0/core/rate_limiter.py +50 -0
- oxhunter-2.0.0/core/reporter.py +608 -0
- oxhunter-2.0.0/core/resume.py +127 -0
- oxhunter-2.0.0/core/scan_db.py +523 -0
- oxhunter-2.0.0/core/scanner.py +456 -0
- oxhunter-2.0.0/core/screenshots.py +194 -0
- oxhunter-2.0.0/core/severity.py +178 -0
- oxhunter-2.0.0/core/validator.py +88 -0
- oxhunter-2.0.0/core/waf_detector.py +139 -0
- oxhunter-2.0.0/dashboard_server.py +120 -0
- oxhunter-2.0.0/integrations/burp_export.py +361 -0
- oxhunter-2.0.0/integrations/cicd.py +208 -0
- oxhunter-2.0.0/integrations/jira_github.py +142 -0
- oxhunter-2.0.0/integrations/slack_webhook.py +138 -0
- oxhunter-2.0.0/mass_scan.py +91 -0
- oxhunter-2.0.0/modules/__init__.py +0 -0
- oxhunter-2.0.0/modules/api_versioning.py +194 -0
- oxhunter-2.0.0/modules/business_logic.py +201 -0
- oxhunter-2.0.0/modules/cmd_injection.py +371 -0
- oxhunter-2.0.0/modules/cors.py +309 -0
- oxhunter-2.0.0/modules/csrf.py +136 -0
- oxhunter-2.0.0/modules/directory_brute.py +397 -0
- oxhunter-2.0.0/modules/email_harvest.py +128 -0
- oxhunter-2.0.0/modules/git_exposure.py +401 -0
- oxhunter-2.0.0/modules/graphql.py +555 -0
- oxhunter-2.0.0/modules/headers.py +217 -0
- oxhunter-2.0.0/modules/http_smuggling.py +441 -0
- oxhunter-2.0.0/modules/idor.py +389 -0
- oxhunter-2.0.0/modules/js_analysis.py +502 -0
- oxhunter-2.0.0/modules/jwt_attacks.py +442 -0
- oxhunter-2.0.0/modules/lfi.py +291 -0
- oxhunter-2.0.0/modules/oauth_tester.py +347 -0
- oxhunter-2.0.0/modules/open_redirect.py +207 -0
- oxhunter-2.0.0/modules/password_policy.py +506 -0
- oxhunter-2.0.0/modules/prototype_pollution.py +389 -0
- oxhunter-2.0.0/modules/race_condition.py +323 -0
- oxhunter-2.0.0/modules/session_fixation.py +530 -0
- oxhunter-2.0.0/modules/sqli.py +302 -0
- oxhunter-2.0.0/modules/ssl_tls.py +496 -0
- oxhunter-2.0.0/modules/ssrf.py +303 -0
- oxhunter-2.0.0/modules/subdomain.py +279 -0
- oxhunter-2.0.0/modules/supply_chain.py +306 -0
- oxhunter-2.0.0/modules/tech_fingerprint.py +595 -0
- oxhunter-2.0.0/modules/waf_bypass.py +536 -0
- oxhunter-2.0.0/modules/websocket.py +154 -0
- oxhunter-2.0.0/modules/xss.py +258 -0
- oxhunter-2.0.0/modules/xxe.py +408 -0
- oxhunter-2.0.0/nuclei_integration.py +71 -0
- oxhunter-2.0.0/oxhunter.egg-info/PKG-INFO +656 -0
- oxhunter-2.0.0/oxhunter.egg-info/SOURCES.txt +109 -0
- oxhunter-2.0.0/oxhunter.egg-info/dependency_links.txt +1 -0
- oxhunter-2.0.0/oxhunter.egg-info/entry_points.txt +2 -0
- oxhunter-2.0.0/oxhunter.egg-info/requires.txt +33 -0
- oxhunter-2.0.0/oxhunter.egg-info/top_level.txt +14 -0
- oxhunter-2.0.0/oxhunter_cli.py +36 -0
- oxhunter-2.0.0/oxhunter_main.py +16 -0
- oxhunter-2.0.0/payloads/auth_bypass/auth_bypass.txt +74 -0
- oxhunter-2.0.0/payloads/cmd_injection/cmd_injection.txt +165 -0
- oxhunter-2.0.0/payloads/cors/cors.txt +21 -0
- oxhunter-2.0.0/payloads/csrf/csrf.txt +10 -0
- oxhunter-2.0.0/payloads/graphql/graphql.txt +23 -0
- oxhunter-2.0.0/payloads/http_smuggling/http_smuggling.txt +16 -0
- oxhunter-2.0.0/payloads/idor/idor_params.txt +83 -0
- oxhunter-2.0.0/payloads/jwt/jwt.txt +61 -0
- oxhunter-2.0.0/payloads/lfi/lfi.txt +139 -0
- oxhunter-2.0.0/payloads/open_redirect/open_redirect.txt +68 -0
- oxhunter-2.0.0/payloads/prototype_pollution/prototype_pollution.txt +55 -0
- oxhunter-2.0.0/payloads/sqli/sqli.txt +221 -0
- oxhunter-2.0.0/payloads/ssrf/ssrf.txt +184 -0
- oxhunter-2.0.0/payloads/ssti/ssti.txt +87 -0
- oxhunter-2.0.0/payloads/waf_bypass/waf_bypass.txt +95 -0
- oxhunter-2.0.0/payloads/wordlists/common_dirs.txt +163 -0
- oxhunter-2.0.0/payloads/wordlists/sensitive_files.txt +111 -0
- oxhunter-2.0.0/payloads/wordlists/subdomains.txt +127 -0
- oxhunter-2.0.0/payloads/xss/xss.txt +270 -0
- oxhunter-2.0.0/payloads/xxe/xxe.txt +60 -0
- oxhunter-2.0.0/pyproject.toml +107 -0
- oxhunter-2.0.0/recon/passive_recon.py +433 -0
- oxhunter-2.0.0/setup.cfg +4 -0
- oxhunter-2.0.0/utils/__init__.py +0 -0
- oxhunter-2.0.0/utils/logger.py +111 -0
|
@@ -0,0 +1,679 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
0xHunter - Web Vulnerability Scanner
|
|
4
|
+
Main CLI Entry Point
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import sys
|
|
9
|
+
import os
|
|
10
|
+
import json
|
|
11
|
+
import pathlib
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Optional, List
|
|
14
|
+
|
|
15
|
+
# Load .env / env file automatically
|
|
16
|
+
try:
|
|
17
|
+
from dotenv import load_dotenv
|
|
18
|
+
_base = pathlib.Path(__file__).parent
|
|
19
|
+
# Try all common env file names
|
|
20
|
+
for _name in ['.env', 'env', '.env.local', 'env.local']:
|
|
21
|
+
_env_path = _base / _name
|
|
22
|
+
if _env_path.exists():
|
|
23
|
+
load_dotenv(dotenv_path=_env_path, override=True)
|
|
24
|
+
break
|
|
25
|
+
else:
|
|
26
|
+
load_dotenv(override=True) # fallback — search default locations
|
|
27
|
+
except ImportError:
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
import typer
|
|
31
|
+
from rich.console import Console
|
|
32
|
+
from rich.panel import Panel
|
|
33
|
+
from rich.table import Table
|
|
34
|
+
from rich.text import Text
|
|
35
|
+
from rich.rule import Rule
|
|
36
|
+
from rich import box
|
|
37
|
+
from typer import rich_utils as _ru
|
|
38
|
+
|
|
39
|
+
_ru.STYLE_HELPTEXT = ""
|
|
40
|
+
_ru.STYLE_HELPTEXT_FIRST_LINE = "bold white"
|
|
41
|
+
_ru.STYLE_OPTION = "bold cyan"
|
|
42
|
+
_ru.STYLE_ARGUMENT = "bold cyan"
|
|
43
|
+
_ru.STYLE_METAVAR = "cyan"
|
|
44
|
+
_ru.STYLE_METAVAR_SEPARATOR = "dim"
|
|
45
|
+
_ru.STYLE_USAGE = "bold white"
|
|
46
|
+
_ru.STYLE_USAGE_COMMAND = "bold red"
|
|
47
|
+
_ru.STYLE_REQUIRED_LONG = "bold red"
|
|
48
|
+
_ru.STYLE_REQUIRED_SHORT = "bold red"
|
|
49
|
+
_ru.STYLE_OPTIONS_PANEL_BORDER = "red"
|
|
50
|
+
_ru.STYLE_ARGUMENTS_PANEL_BORDER = "red"
|
|
51
|
+
_ru.OPTIONS_PANEL_TITLE = " Options"
|
|
52
|
+
_ru.ARGUMENTS_PANEL_TITLE = " Arguments"
|
|
53
|
+
|
|
54
|
+
from core.validator import AuthorizationChecker
|
|
55
|
+
from core.scanner import ScannerEngine
|
|
56
|
+
from utils.logger import setup_logger
|
|
57
|
+
|
|
58
|
+
app = typer.Typer(
|
|
59
|
+
name="0xHunter",
|
|
60
|
+
help="[bold white]Web Vulnerability Scanner[/bold white] [dim]|[/dim] [red]For Authorized Testing Only[/red]",
|
|
61
|
+
add_completion=False,
|
|
62
|
+
rich_markup_mode="rich",
|
|
63
|
+
)
|
|
64
|
+
console = Console()
|
|
65
|
+
logger = setup_logger("0xHunter")
|
|
66
|
+
|
|
67
|
+
SEVERITY_STYLES = {
|
|
68
|
+
"Critical": "bold red",
|
|
69
|
+
"High" : "red",
|
|
70
|
+
"Medium" : "yellow",
|
|
71
|
+
"Low" : "green",
|
|
72
|
+
"Info" : "cyan",
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
BANNER = r"""
|
|
76
|
+
___ _ _ _ _ _ _ _____ _____ ____
|
|
77
|
+
/ _ \__ _| | | | | | | \ | |_ _| ____| _ \
|
|
78
|
+
| | | \ \/ / |_| | | | | \| | | | | _| | |_) |
|
|
79
|
+
| |_| |> <| _ | |_| | |\ | | | | |___| _ <
|
|
80
|
+
\___//_/\_\_| |_|\___/|_| \_| |_| |_____|_| \_\
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def print_banner():
|
|
85
|
+
console.print(f"[bold red]{BANNER}[/bold red]")
|
|
86
|
+
console.print(
|
|
87
|
+
Panel.fit(
|
|
88
|
+
"[bold white]Web Vulnerability Scanner[/bold white] [dim]|[/dim] "
|
|
89
|
+
"[bold red]For Authorized Testing Only[/bold red] [dim]|[/dim] "
|
|
90
|
+
"[dim]v2.0.0[/dim]",
|
|
91
|
+
border_style="red",
|
|
92
|
+
padding=(0, 2),
|
|
93
|
+
)
|
|
94
|
+
)
|
|
95
|
+
console.print()
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _report_path(output: Optional[str], extension: str, timestamp: str) -> Path:
|
|
99
|
+
"""Return a unique path for one report format."""
|
|
100
|
+
if output:
|
|
101
|
+
path = Path(output).expanduser()
|
|
102
|
+
path = path.with_suffix(extension)
|
|
103
|
+
else:
|
|
104
|
+
safe_ts = timestamp.replace(':', '-').replace('.', '-')
|
|
105
|
+
path = Path(f"0xHunter_Report_{safe_ts}{extension}")
|
|
106
|
+
if not path.is_absolute():
|
|
107
|
+
path = Path('reports') / path
|
|
108
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
109
|
+
return path.resolve()
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _save_json(result, output: Optional[str]) -> str:
|
|
113
|
+
data = {
|
|
114
|
+
"target" : result.target,
|
|
115
|
+
"start_time" : result.start_time,
|
|
116
|
+
"end_time" : result.end_time,
|
|
117
|
+
"urls_crawled": result.urls_crawled,
|
|
118
|
+
"forms_found" : result.forms_found,
|
|
119
|
+
"findings" : result.findings,
|
|
120
|
+
"summary" : result.summary,
|
|
121
|
+
}
|
|
122
|
+
path = Path(output) if output else _report_path(None, '.json', result.start_time)
|
|
123
|
+
if not path.is_absolute():
|
|
124
|
+
path = Path('reports') / path
|
|
125
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
126
|
+
path.write_text(json.dumps(data, indent=2, ensure_ascii=False), encoding='utf-8')
|
|
127
|
+
return str(path.resolve())
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@app.command()
|
|
131
|
+
def scan(
|
|
132
|
+
url: str = typer.Argument(..., help="Target URL (e.g., https://example.com)"),
|
|
133
|
+
|
|
134
|
+
# ── Authorization ─────────────────────────────────────────────────────────
|
|
135
|
+
confirm : bool = typer.Option(False, "--confirm", "-c", help="Confirm you have authorization to scan"),
|
|
136
|
+
|
|
137
|
+
# ── Scan Mode ─────────────────────────────────────────────────────────────
|
|
138
|
+
full : bool = typer.Option(False, "--full", "-f", help="Full scan — all 61 modules"),
|
|
139
|
+
verbose : bool = typer.Option(False, "--verbose", "-v", help="Verbose output"),
|
|
140
|
+
|
|
141
|
+
# ── Report ────────────────────────────────────────────────────────────────
|
|
142
|
+
report : Optional[str]= typer.Option(None, "--report", "-r", help="Report format: html | json | both"),
|
|
143
|
+
output : Optional[str]= typer.Option(None, "--output", "-o", help="Output file path"),
|
|
144
|
+
|
|
145
|
+
# ── Auth ──────────────────────────────────────────────────────────────────
|
|
146
|
+
cookie : Optional[str]= typer.Option(None, "--cookie", help="Session cookie (e.g. 'session=abc123')"),
|
|
147
|
+
token : Optional[str]= typer.Option(None, "--token", help="Bearer / JWT token"),
|
|
148
|
+
auth : Optional[str]= typer.Option(None, "--auth", help="Basic auth user:pass"),
|
|
149
|
+
|
|
150
|
+
# ── Proxy ─────────────────────────────────────────────────────────────────
|
|
151
|
+
proxy : Optional[str]= typer.Option(None, "--proxy", help="Proxy URL (e.g. http://127.0.0.1:8080)"),
|
|
152
|
+
|
|
153
|
+
# ── AI Features ───────────────────────────────────────────────────────────
|
|
154
|
+
ai : bool = typer.Option(False, "--ai", help="Enable AI-powered features (requires GROQ_API_KEY)"),
|
|
155
|
+
lang : str = typer.Option("en", "--lang", help="Report language: en | ur"),
|
|
156
|
+
ai_chain: bool = typer.Option(False, "--ai-chain", help="AI vulnerability chaining"),
|
|
157
|
+
ai_fp : bool = typer.Option(False, "--ai-fp", help="AI false positive reducer"),
|
|
158
|
+
|
|
159
|
+
# ── WAF ───────────────────────────────────────────────────────────────────
|
|
160
|
+
waf_detect : bool = typer.Option(False, "--waf-detect", help="Detect WAF"),
|
|
161
|
+
waf_bypass : bool = typer.Option(False, "--waf-bypass", help="Auto WAF bypass mode"),
|
|
162
|
+
|
|
163
|
+
# ── Compliance ────────────────────────────────────────────────────────────
|
|
164
|
+
compliance: Optional[str]= typer.Option(None, "--compliance", help="Compliance: owasp | pci | iso"),
|
|
165
|
+
bug_bounty: bool = typer.Option(False, "--bug-bounty", help="Bug bounty mode"),
|
|
166
|
+
scope : Optional[str]= typer.Option(None, "--scope", help="In-scope domains (e.g. '*.example.com')"),
|
|
167
|
+
|
|
168
|
+
# ── Integrations ──────────────────────────────────────────────────────────
|
|
169
|
+
notify : Optional[str]= typer.Option(None, "--notify", help="Notifications: slack | discord | both"),
|
|
170
|
+
github_issues: bool = typer.Option(False, "--github-issues", help="Auto-create GitHub issues"),
|
|
171
|
+
jira : bool = typer.Option(False, "--jira", help="Auto-create Jira tickets"),
|
|
172
|
+
|
|
173
|
+
# ── Burp ──────────────────────────────────────────────────────────────────
|
|
174
|
+
export_burp: Optional[str]= typer.Option(None,"--export-burp", help="Export findings to Burp XML"),
|
|
175
|
+
|
|
176
|
+
# ── Scan Control ──────────────────────────────────────────────────────────
|
|
177
|
+
threads : int = typer.Option(10, "--threads", "-t", help="Number of threads"),
|
|
178
|
+
timeout : int = typer.Option(10, "--timeout", help="Request timeout (seconds)"),
|
|
179
|
+
delay : float = typer.Option(0.5, "--delay", help="Delay between requests"),
|
|
180
|
+
resume : Optional[str]= typer.Option(None, "--resume", help="Resume scan by scan ID"),
|
|
181
|
+
targets : Optional[str]= typer.Option(None, "--targets", help="File with multiple target URLs"),
|
|
182
|
+
|
|
183
|
+
# ── New Features ──────────────────────────────────────────────────────────
|
|
184
|
+
passive_recon : bool = typer.Option(False, "--passive-recon", help="Shodan + Wayback + Google Dorks + crt.sh"),
|
|
185
|
+
recon_only : bool = typer.Option(False, "--recon-only", help="Passive recon only, no active scanning"),
|
|
186
|
+
exploit_gen : bool = typer.Option(False, "--exploit-gen", help="AI PoC exploit generator (requires --ai)"),
|
|
187
|
+
chain_attacks : bool = typer.Option(False, "--chain-attacks", help="Smart attack chaining (SSRF→RCE, XSS→ATO)"),
|
|
188
|
+
oauth : bool = typer.Option(False, "--oauth", help="OAuth 2.0 / OIDC security testing"),
|
|
189
|
+
supply_chain : bool = typer.Option(False, "--supply-chain", help="Supply chain attack detector (npm/pip)"),
|
|
190
|
+
):
|
|
191
|
+
"""
|
|
192
|
+
[bold white]Scan a target URL for web vulnerabilities.[/bold white]
|
|
193
|
+
|
|
194
|
+
[dim]────────────────────────────────────────[/dim]
|
|
195
|
+
|
|
196
|
+
[bold red]Examples:[/bold red]
|
|
197
|
+
|
|
198
|
+
[cyan]python 0xhunter.py scan https://example.com --confirm[/cyan]
|
|
199
|
+
|
|
200
|
+
[cyan]python 0xhunter.py scan https://example.com --confirm --full --report html[/cyan]
|
|
201
|
+
|
|
202
|
+
[cyan]python 0xhunter.py scan https://example.com --confirm --ai --lang ur[/cyan]
|
|
203
|
+
|
|
204
|
+
[cyan]python 0xhunter.py scan https://example.com --confirm --proxy http://127.0.0.1:8080[/cyan]
|
|
205
|
+
|
|
206
|
+
[cyan]python 0xhunter.py scan https://example.com --confirm --compliance owasp[/cyan]
|
|
207
|
+
|
|
208
|
+
[cyan]python 0xhunter.py scan https://example.com --confirm --waf-detect --waf-bypass[/cyan]
|
|
209
|
+
|
|
210
|
+
[dim]────────────────────────────────────────[/dim]
|
|
211
|
+
"""
|
|
212
|
+
print_banner()
|
|
213
|
+
|
|
214
|
+
# ── Authorization Check ───────────────────────────────────────────────────
|
|
215
|
+
if not confirm:
|
|
216
|
+
console.print(
|
|
217
|
+
Panel(
|
|
218
|
+
"[bold red]Authorization Required[/bold red]\n\n"
|
|
219
|
+
"Use [bold yellow]--confirm[/bold yellow] flag to confirm you have "
|
|
220
|
+
"[bold]written permission[/bold] to scan the target.\n\n"
|
|
221
|
+
"[dim red]Unauthorized scanning is ILLEGAL.[/dim red]",
|
|
222
|
+
border_style="red",
|
|
223
|
+
title="[red]⛔ ACCESS DENIED[/red]",
|
|
224
|
+
padding=(1, 2),
|
|
225
|
+
)
|
|
226
|
+
)
|
|
227
|
+
raise typer.Exit(code=3)
|
|
228
|
+
|
|
229
|
+
if not AuthorizationChecker.confirm():
|
|
230
|
+
console.print("\n[bold red]Scan aborted. Authorization not confirmed.[/bold red]")
|
|
231
|
+
raise typer.Exit(code=3)
|
|
232
|
+
|
|
233
|
+
# ── URL Validation ────────────────────────────────────────────────────────
|
|
234
|
+
if not url.startswith(("http://", "https://")):
|
|
235
|
+
console.print(Panel(
|
|
236
|
+
"[bold red]Invalid URL[/bold red] — must start with [yellow]http://[/yellow] or [yellow]https://[/yellow]",
|
|
237
|
+
border_style="red", padding=(0, 2)))
|
|
238
|
+
raise typer.Exit(code=2)
|
|
239
|
+
|
|
240
|
+
# ── AI Key Check ──────────────────────────────────────────────────────────
|
|
241
|
+
if ai and not os.getenv("GROQ_API_KEY"):
|
|
242
|
+
console.print(Panel(
|
|
243
|
+
"[bold yellow]GROQ_API_KEY not set![/bold yellow]\n\n"
|
|
244
|
+
"Set it with:\n"
|
|
245
|
+
"[cyan]$env:GROQ_API_KEY = 'your_key_here'[/cyan] (Windows)\n"
|
|
246
|
+
"[cyan]export GROQ_API_KEY='your_key_here'[/cyan] (Linux/Mac)\n\n"
|
|
247
|
+
"[dim]Get free key at: console.groq.com[/dim]",
|
|
248
|
+
border_style="yellow", title="[yellow]⚠ AI Key Missing[/yellow]", padding=(1,2)))
|
|
249
|
+
raise typer.Exit(code=2)
|
|
250
|
+
|
|
251
|
+
# ── Scan Config Panel ─────────────────────────────────────────────────────
|
|
252
|
+
ai_status = "[green]ON[/green]" if ai else "[dim]OFF[/dim]"
|
|
253
|
+
waf_status = "[green]ON[/green]" if waf_bypass else ("[yellow]DETECT[/yellow]" if waf_detect else "[dim]OFF[/dim]")
|
|
254
|
+
prx_status = f"[cyan]{proxy}[/cyan]" if proxy else "[dim]None[/dim]"
|
|
255
|
+
cmp_status = f"[cyan]{compliance.upper()}[/cyan]" if compliance else "[dim]None[/dim]"
|
|
256
|
+
bb_status = "[green]ON[/green]" if bug_bounty else "[dim]OFF[/dim]"
|
|
257
|
+
|
|
258
|
+
console.print(
|
|
259
|
+
Panel(
|
|
260
|
+
f"[bold white]Target :[/bold white] [cyan]{url}[/cyan]\n"
|
|
261
|
+
f"[bold white]Mode :[/bold white] [yellow]{'Full' if full else 'Standard'} Scan[/yellow] "
|
|
262
|
+
f"([dim]threads:{threads} timeout:{timeout}s delay:{delay}s[/dim])\n"
|
|
263
|
+
f"[bold white]Report :[/bold white] [green]{report.upper() if report else 'None'}[/green]\n"
|
|
264
|
+
f"[bold white]AI :[/bold white] {ai_status} [dim]lang:{lang}[/dim]\n"
|
|
265
|
+
f"[bold white]WAF :[/bold white] {waf_status}\n"
|
|
266
|
+
f"[bold white]Proxy :[/bold white] {prx_status}\n"
|
|
267
|
+
f"[bold white]Compliance :[/bold white] {cmp_status}\n"
|
|
268
|
+
f"[bold white]Bug Bounty :[/bold white] {bb_status}",
|
|
269
|
+
title="[bold red]⚡ Scan Configuration[/bold red]",
|
|
270
|
+
border_style="red",
|
|
271
|
+
padding=(1, 2),
|
|
272
|
+
)
|
|
273
|
+
)
|
|
274
|
+
console.print()
|
|
275
|
+
|
|
276
|
+
# ── Run Scan ──────────────────────────────────────────────────────────────
|
|
277
|
+
async def _run():
|
|
278
|
+
engine = ScannerEngine(
|
|
279
|
+
url,
|
|
280
|
+
threads = threads,
|
|
281
|
+
timeout = timeout,
|
|
282
|
+
delay = delay,
|
|
283
|
+
proxy = proxy,
|
|
284
|
+
cookie = cookie,
|
|
285
|
+
token = token,
|
|
286
|
+
auth = auth,
|
|
287
|
+
verbose = verbose,
|
|
288
|
+
)
|
|
289
|
+
result = await engine.run(full_scan=full)
|
|
290
|
+
return engine, result
|
|
291
|
+
|
|
292
|
+
try:
|
|
293
|
+
engine, result = asyncio.run(_run())
|
|
294
|
+
findings = result.findings or []
|
|
295
|
+
|
|
296
|
+
# ── Results Summary ───────────────────────────────────────────────────
|
|
297
|
+
console.print(Rule("[bold red]SCAN RESULTS[/bold red]", style="red"))
|
|
298
|
+
|
|
299
|
+
table = Table(
|
|
300
|
+
title="[bold]Vulnerability Summary[/bold]",
|
|
301
|
+
box=box.ROUNDED, border_style="red",
|
|
302
|
+
header_style="bold red", show_lines=True,
|
|
303
|
+
)
|
|
304
|
+
table.add_column("Severity", justify="center", style="bold", min_width=12)
|
|
305
|
+
table.add_column("Count", justify="center", min_width=8)
|
|
306
|
+
|
|
307
|
+
has_findings = False
|
|
308
|
+
for sev, count in result.summary.items():
|
|
309
|
+
if count > 0:
|
|
310
|
+
has_findings = True
|
|
311
|
+
style = SEVERITY_STYLES.get(sev, "white")
|
|
312
|
+
table.add_row(f"[{style}]{sev}[/{style}]", f"[{style}]{count}[/{style}]")
|
|
313
|
+
|
|
314
|
+
if not has_findings:
|
|
315
|
+
table.add_row("[green]No Issues Found[/green]", "[green]0[/green]")
|
|
316
|
+
console.print(table)
|
|
317
|
+
|
|
318
|
+
# ── Detailed Findings ─────────────────────────────────────────────────
|
|
319
|
+
if findings:
|
|
320
|
+
console.print(f"\n[bold red]Detailed Findings[/bold red]")
|
|
321
|
+
console.print(Rule(style="red"))
|
|
322
|
+
for i, f in enumerate(findings, 1):
|
|
323
|
+
sev = f.get("severity", "Info")
|
|
324
|
+
style = SEVERITY_STYLES.get(sev, "white")
|
|
325
|
+
console.print(Panel(
|
|
326
|
+
f"[bold white]Parameter :[/bold white] {f.get('parameter','N/A')}\n"
|
|
327
|
+
f"[bold white]Confidence:[/bold white] {f.get('confidence','N/A')}\n"
|
|
328
|
+
f"[bold white]Evidence :[/bold white] {f.get('evidence','N/A')}",
|
|
329
|
+
title=f"[{style}]#{i} [{f.get('type')}] {sev} — {f.get('url')}[/{style}]",
|
|
330
|
+
border_style=style, padding=(0, 2),
|
|
331
|
+
))
|
|
332
|
+
|
|
333
|
+
# ── WAF Detection ─────────────────────────────────────────────────────
|
|
334
|
+
if waf_detect or waf_bypass:
|
|
335
|
+
console.print(Rule("[bold red]WAF Analysis[/bold red]", style="red"))
|
|
336
|
+
from core.waf_detector import WAFDetector
|
|
337
|
+
wd = WAFDetector(proxy=proxy)
|
|
338
|
+
det = wd.detect(url)
|
|
339
|
+
waf_name = det.get("waf","Unknown")
|
|
340
|
+
if det.get("detected"):
|
|
341
|
+
console.print(f" [yellow]WAF Detected:[/yellow] [bold]{waf_name}[/bold] (confidence:{det.get('confidence')})")
|
|
342
|
+
if waf_bypass:
|
|
343
|
+
bypasses = wd.test_bypasses(url, waf_name)
|
|
344
|
+
working = [b for b in bypasses if b.get("bypassed")]
|
|
345
|
+
console.print(f" [cyan]Bypass Results:[/cyan] {len(working)}/{len(bypasses)} payloads bypassed WAF")
|
|
346
|
+
else:
|
|
347
|
+
console.print(" [green]No WAF detected[/green]")
|
|
348
|
+
|
|
349
|
+
# ── AI Features ───────────────────────────────────────────────────────
|
|
350
|
+
if ai and findings:
|
|
351
|
+
console.print(Rule("[bold red]AI Analysis[/bold red]", style="red"))
|
|
352
|
+
|
|
353
|
+
# AI Payload Generator
|
|
354
|
+
try:
|
|
355
|
+
from ai.payload_generator import AIPayloadGenerator
|
|
356
|
+
gen = AIPayloadGenerator()
|
|
357
|
+
vuln_types = list(set(f.get("type","xss") for f in findings))
|
|
358
|
+
console.print(f" [cyan]AI Payload Generator:[/cyan] Generating for {vuln_types}")
|
|
359
|
+
for vt in vuln_types[:3]:
|
|
360
|
+
payloads = gen.generate(vt, url)
|
|
361
|
+
console.print(f" [dim]→ {vt}: {len(payloads)} AI payloads generated[/dim]")
|
|
362
|
+
except Exception as e:
|
|
363
|
+
console.print(f" [yellow]AI Payload Generator skipped: {e}[/yellow]")
|
|
364
|
+
|
|
365
|
+
# False Positive Reducer
|
|
366
|
+
if ai_fp:
|
|
367
|
+
try:
|
|
368
|
+
from ai.fp_reducer import FalsePositiveReducer
|
|
369
|
+
fpr = FalsePositiveReducer()
|
|
370
|
+
before = len(findings)
|
|
371
|
+
findings = fpr.filter(findings, url)
|
|
372
|
+
console.print(f" [cyan]False Positive Reducer:[/cyan] {before} → {len(findings)} findings")
|
|
373
|
+
except Exception as e:
|
|
374
|
+
console.print(f" [yellow]FP Reducer skipped: {e}[/yellow]")
|
|
375
|
+
|
|
376
|
+
# Vulnerability Chaining
|
|
377
|
+
if ai_chain:
|
|
378
|
+
try:
|
|
379
|
+
from ai.vuln_chaining import VulnChainer
|
|
380
|
+
chainer = VulnChainer()
|
|
381
|
+
chains = chainer.chain(findings)
|
|
382
|
+
if chains:
|
|
383
|
+
console.print(f" [cyan]Vuln Chains Found:[/cyan] {len(chains)}")
|
|
384
|
+
for ch in chains[:3]:
|
|
385
|
+
console.print(f" [dim red]→ {ch}[/dim red]")
|
|
386
|
+
except Exception as e:
|
|
387
|
+
console.print(f" [yellow]Chaining skipped: {e}[/yellow]")
|
|
388
|
+
|
|
389
|
+
# NL Report
|
|
390
|
+
try:
|
|
391
|
+
from ai.nl_report import NLReportGenerator
|
|
392
|
+
nlr = NLReportGenerator(language=lang)
|
|
393
|
+
nl_text = nlr.generate(findings, url)
|
|
394
|
+
if nl_text:
|
|
395
|
+
console.print(Panel(
|
|
396
|
+
nl_text[:800],
|
|
397
|
+
title=f"[bold red]AI Report ({'Urdu' if lang=='ur' else 'English'})[/bold red]",
|
|
398
|
+
border_style="red", padding=(1,2),
|
|
399
|
+
))
|
|
400
|
+
except Exception as e:
|
|
401
|
+
console.print(f" [yellow]NL Report skipped: {e}[/yellow]")
|
|
402
|
+
|
|
403
|
+
# ── Compliance ────────────────────────────────────────────────────────
|
|
404
|
+
if compliance:
|
|
405
|
+
console.print(Rule("[bold red]Compliance Report[/bold red]", style="red"))
|
|
406
|
+
findings_dicts = [{"vuln_type": f.get("type",""), "severity": f.get("severity","")} for f in findings]
|
|
407
|
+
if compliance == "owasp":
|
|
408
|
+
from compliance.owasp_mapping import OWASPMapper
|
|
409
|
+
score = OWASPMapper.compliance_score(findings_dicts)
|
|
410
|
+
console.print(f" [cyan]OWASP Top 10:[/cyan] Score [bold]{score['score']}/100[/bold] ({score['status']})")
|
|
411
|
+
console.print(f" Passed: [green]{score['passed']}[/green] Failed: [red]{score['failed']}[/red]")
|
|
412
|
+
elif compliance == "pci":
|
|
413
|
+
from compliance.pci_iso_report import ComplianceReporter
|
|
414
|
+
rpt = ComplianceReporter(findings_dicts, url)
|
|
415
|
+
r = rpt.pci_report()
|
|
416
|
+
console.print(f" [cyan]PCI-DSS v4.0:[/cyan] Score [bold]{r['score']}/100[/bold] ({r['status']})")
|
|
417
|
+
elif compliance == "iso":
|
|
418
|
+
from compliance.pci_iso_report import ComplianceReporter
|
|
419
|
+
rpt = ComplianceReporter(findings_dicts, url)
|
|
420
|
+
r = rpt.iso_report()
|
|
421
|
+
console.print(f" [cyan]ISO 27001:2022:[/cyan] Score [bold]{r['score']}/100[/bold] ({r['status']})")
|
|
422
|
+
|
|
423
|
+
# ── Bug Bounty Filter ─────────────────────────────────────────────────
|
|
424
|
+
if bug_bounty and scope:
|
|
425
|
+
from compliance.bug_bounty import setup as bb_setup
|
|
426
|
+
bb = bb_setup(scope.split(","))
|
|
427
|
+
valid = bb.filter_findings([{"vuln_type":f.get("type",""),"severity":f.get("severity",""),"url":f.get("url","")} for f in findings])
|
|
428
|
+
console.print(Rule("[bold red]Bug Bounty Mode[/bold red]", style="red"))
|
|
429
|
+
console.print(f" [cyan]In-Scope Findings:[/cyan] [bold]{len(valid)}[/bold] / {len(findings)}")
|
|
430
|
+
|
|
431
|
+
# ── Notifications ─────────────────────────────────────────────────────
|
|
432
|
+
if notify and findings:
|
|
433
|
+
findings_dicts = [{"vuln_type":f.get("type",""),"severity":f.get("severity",""),"url":f.get("url",""),"detail":f.get("evidence","")} for f in findings]
|
|
434
|
+
from integrations.slack_webhook import AlertManager
|
|
435
|
+
slack_url = os.getenv("SLACK_WEBHOOK_URL","")
|
|
436
|
+
discord_url = os.getenv("DISCORD_WEBHOOK_URL","")
|
|
437
|
+
am = AlertManager(
|
|
438
|
+
slack_url = slack_url if notify in ["slack","both"] else "",
|
|
439
|
+
discord_url = discord_url if notify in ["discord","both"] else "",
|
|
440
|
+
)
|
|
441
|
+
am.alert_summary(url, findings_dicts)
|
|
442
|
+
console.print(f" [green]✔ Notifications sent via {notify}[/green]")
|
|
443
|
+
|
|
444
|
+
# ── GitHub Issues ─────────────────────────────────────────────────────
|
|
445
|
+
if github_issues and findings:
|
|
446
|
+
from integrations.jira_github import GitHubIntegration
|
|
447
|
+
gh = GitHubIntegration(
|
|
448
|
+
token = os.getenv("GITHUB_TOKEN",""),
|
|
449
|
+
repo = os.getenv("GITHUB_REPO",""),
|
|
450
|
+
)
|
|
451
|
+
findings_dicts = [{"vuln_type":f.get("type",""),"severity":f.get("severity",""),"url":f.get("url",""),"detail":f.get("evidence",""),"cvss_score":0} for f in findings]
|
|
452
|
+
created = gh.create_bulk(findings_dicts, "HIGH")
|
|
453
|
+
console.print(f" [green]✔ {len(created)} GitHub issues created[/green]")
|
|
454
|
+
|
|
455
|
+
# ── Jira ──────────────────────────────────────────────────────────────
|
|
456
|
+
if jira and findings:
|
|
457
|
+
from integrations.jira_github import JiraIntegration
|
|
458
|
+
ji = JiraIntegration(
|
|
459
|
+
url = os.getenv("JIRA_URL",""),
|
|
460
|
+
user = os.getenv("JIRA_USER",""),
|
|
461
|
+
token = os.getenv("JIRA_TOKEN",""),
|
|
462
|
+
project = os.getenv("JIRA_PROJECT","SEC"),
|
|
463
|
+
)
|
|
464
|
+
findings_dicts = [{"vuln_type":f.get("type",""),"severity":f.get("severity",""),"url":f.get("url",""),"detail":f.get("evidence",""),"cvss_score":0} for f in findings]
|
|
465
|
+
created = ji.create_bulk(findings_dicts, "HIGH")
|
|
466
|
+
console.print(f" [green]✔ {len(created)} Jira tickets created[/green]")
|
|
467
|
+
|
|
468
|
+
# ── Burp Export ───────────────────────────────────────────────────────
|
|
469
|
+
if export_burp:
|
|
470
|
+
from integrations.burp_export import BurpExporter
|
|
471
|
+
findings_dicts = [{"vuln_type":f.get("type",""),"severity":f.get("severity",""),"url":f.get("url",""),"payload":f.get("evidence",""),"detail":f.get("evidence",""),"cvss_score":0,"remediation":"","evidence":""} for f in findings]
|
|
472
|
+
path = BurpExporter().export(findings_dicts, export_burp)
|
|
473
|
+
console.print(f" [green]✔ Burp XML exported → {path}[/green]")
|
|
474
|
+
|
|
475
|
+
# ── Reports ───────────────────────────────────────────────────────────
|
|
476
|
+
console.print()
|
|
477
|
+
console.print(Panel("[bold green]✔ Scan completed successfully![/bold green]", border_style="green", padding=(0,2)))
|
|
478
|
+
|
|
479
|
+
if report:
|
|
480
|
+
fmt = report.lower()
|
|
481
|
+
if fmt in ["html", "both"]:
|
|
482
|
+
console.print("\n[cyan]Generating HTML report...[/cyan]")
|
|
483
|
+
html_path = _report_path(output, '.html', result.start_time)
|
|
484
|
+
path = engine.generate_html_report(str(html_path))
|
|
485
|
+
console.print(Panel(f"[bold green]HTML Report:[/bold green]\n[cyan]{os.path.abspath(path)}[/cyan]", border_style="green", padding=(0,2)))
|
|
486
|
+
if fmt in ["json", "both"]:
|
|
487
|
+
console.print("\n[cyan]Generating JSON report...[/cyan]")
|
|
488
|
+
json_path = _report_path(output, '.json', result.start_time)
|
|
489
|
+
path = _save_json(result, str(json_path))
|
|
490
|
+
console.print(Panel(f"[bold green]JSON Report:[/bold green]\n[cyan]{path}[/cyan]", border_style="green", padding=(0,2)))
|
|
491
|
+
if fmt not in ["html","json","both"]:
|
|
492
|
+
console.print(f"[yellow]Unknown format '{fmt}'. Use: html | json | both[/yellow]")
|
|
493
|
+
|
|
494
|
+
# ── Passive Recon ─────────────────────────────────────────────────────
|
|
495
|
+
if passive_recon or recon_only:
|
|
496
|
+
console.print(Rule("[bold red]Passive Recon[/bold red]", style="red"))
|
|
497
|
+
try:
|
|
498
|
+
from recon.passive_recon import PassiveRecon
|
|
499
|
+
pr = PassiveRecon(shodan_key=os.getenv("SHODAN_API_KEY",""))
|
|
500
|
+
recon_r = pr.run(url)
|
|
501
|
+
console.print(f" [cyan]Subdomains:[/cyan] {len(recon_r.get('subdomains',[]))}")
|
|
502
|
+
console.print(f" [cyan]Historical URLs:[/cyan] {len(recon_r.get('historical_urls',[]))}")
|
|
503
|
+
console.print(f" [cyan]Exposed Services:[/cyan] {len(recon_r.get('exposed_services',[]))}")
|
|
504
|
+
console.print(f" [cyan]Google Dorks:[/cyan] {len(recon_r.get('google_dorks',[]))}")
|
|
505
|
+
# Save HTML report
|
|
506
|
+
os.makedirs("reports", exist_ok=True)
|
|
507
|
+
recon_html = pr.html_report(recon_r)
|
|
508
|
+
with open("reports/passive_recon.html","w",encoding="utf-8") as f:
|
|
509
|
+
f.write(recon_html)
|
|
510
|
+
console.print(" [green]✔ Recon report: reports/passive_recon.html[/green]")
|
|
511
|
+
except Exception as e:
|
|
512
|
+
console.print(f" [yellow]Passive recon error: {e}[/yellow]")
|
|
513
|
+
|
|
514
|
+
# ── OAuth Testing ──────────────────────────────────────────────────────
|
|
515
|
+
if oauth:
|
|
516
|
+
console.print(Rule("[bold red]OAuth/OIDC Testing[/bold red]", style="red"))
|
|
517
|
+
try:
|
|
518
|
+
from modules.oauth_tester import OAuthTester
|
|
519
|
+
import asyncio as _asyncio
|
|
520
|
+
ot = OAuthTester()
|
|
521
|
+
oauth_f = _asyncio.get_event_loop().run_until_complete(ot.scan([url]))
|
|
522
|
+
console.print(f" [cyan]OAuth Issues:[/cyan] {len(oauth_f)}")
|
|
523
|
+
for of in oauth_f:
|
|
524
|
+
console.print(f" [{SEVERITY_STYLES.get(of.severity.capitalize(),'white')}][{of.severity}][/] {of.type} — {of.detail[:60]}")
|
|
525
|
+
except Exception as e:
|
|
526
|
+
console.print(f" [yellow]OAuth test error: {e}[/yellow]")
|
|
527
|
+
|
|
528
|
+
# ── Supply Chain ───────────────────────────────────────────────────────
|
|
529
|
+
if supply_chain:
|
|
530
|
+
console.print(Rule("[bold red]Supply Chain Check[/bold red]", style="red"))
|
|
531
|
+
try:
|
|
532
|
+
from modules.supply_chain import SupplyChainDetector
|
|
533
|
+
import asyncio as _asyncio2
|
|
534
|
+
sc = SupplyChainDetector()
|
|
535
|
+
sc_f = _asyncio2.get_event_loop().run_until_complete(sc.scan([url]))
|
|
536
|
+
console.print(f" [cyan]Supply Chain Issues:[/cyan] {len(sc_f)}")
|
|
537
|
+
for sf in sc_f:
|
|
538
|
+
console.print(f" [yellow][{sf.severity}][/yellow] {sf.type} — {sf.package[:40]}")
|
|
539
|
+
except Exception as e:
|
|
540
|
+
console.print(f" [yellow]Supply chain error: {e}[/yellow]")
|
|
541
|
+
|
|
542
|
+
# ── AI Exploit Generator ───────────────────────────────────────────────
|
|
543
|
+
if exploit_gen and ai and findings:
|
|
544
|
+
console.print(Rule("[bold red]AI Exploit Generator[/bold red]", style="red"))
|
|
545
|
+
try:
|
|
546
|
+
from ai.exploit_generator import AIExploitGenerator
|
|
547
|
+
eg = AIExploitGenerator(api_key=os.getenv("GROQ_API_KEY",""))
|
|
548
|
+
exploits = eg.generate_for_all(findings, max_exploits=5)
|
|
549
|
+
saved = eg.save_exploits(exploits)
|
|
550
|
+
console.print(f" [green]✔ {len(exploits)} exploits generated[/green]")
|
|
551
|
+
for s in saved:
|
|
552
|
+
console.print(f" [cyan]→ {s}[/cyan]")
|
|
553
|
+
# HTML report
|
|
554
|
+
exp_html = eg.generate_report(exploits, url)
|
|
555
|
+
with open("reports/exploits.html","w",encoding="utf-8") as f:
|
|
556
|
+
f.write(exp_html)
|
|
557
|
+
console.print(" [green]✔ Exploit report: reports/exploits.html[/green]")
|
|
558
|
+
except Exception as e:
|
|
559
|
+
console.print(f" [yellow]Exploit gen error: {e}[/yellow]")
|
|
560
|
+
|
|
561
|
+
# ── Smart Attack Chaining ──────────────────────────────────────────────
|
|
562
|
+
if chain_attacks and findings:
|
|
563
|
+
console.print(Rule("[bold red]Attack Chain Analysis[/bold red]", style="red"))
|
|
564
|
+
try:
|
|
565
|
+
from ai.attack_chaining import SmartAttackChainer
|
|
566
|
+
chainer = SmartAttackChainer(api_key=os.getenv("GROQ_API_KEY",""))
|
|
567
|
+
chains = chainer.ai_chain_analysis(findings) if ai else chainer.find_chains(findings)
|
|
568
|
+
summary = chainer.summary(chains)
|
|
569
|
+
console.print(f" [cyan]Chains Found:[/cyan] {summary['total_chains']}")
|
|
570
|
+
console.print(f" [red]Critical Chains:[/red] {summary['critical_chains']}")
|
|
571
|
+
console.print(f" [cyan]Highest CVSS:[/cyan] {summary['highest_cvss']}")
|
|
572
|
+
for c in chains[:3]:
|
|
573
|
+
console.print(f" [red][{c.severity}][/red] {c.name} (CVSS:{c.cvss})")
|
|
574
|
+
# Save report
|
|
575
|
+
chain_html = chainer.html_report(chains, url)
|
|
576
|
+
with open("reports/attack_chains.html","w",encoding="utf-8") as f:
|
|
577
|
+
f.write(chain_html)
|
|
578
|
+
console.print(" [green]✔ Chain report: reports/attack_chains.html[/green]")
|
|
579
|
+
except Exception as e:
|
|
580
|
+
console.print(f" [yellow]Chain analysis error: {e}[/yellow]")
|
|
581
|
+
|
|
582
|
+
# ── CI/CD Exit Code ───────────────────────────────────────────────────
|
|
583
|
+
critical = [f for f in findings if f.get("severity") in ["Critical","High"]]
|
|
584
|
+
if critical:
|
|
585
|
+
raise typer.Exit(code=1)
|
|
586
|
+
|
|
587
|
+
except typer.Exit:
|
|
588
|
+
raise
|
|
589
|
+
except Exception as e:
|
|
590
|
+
logger.error(f"Scan failed: {e}")
|
|
591
|
+
console.print(Panel(
|
|
592
|
+
f"[bold red]Scan Failed[/bold red]\n\n{e}",
|
|
593
|
+
border_style="red", title="[red]✖ ERROR[/red]", padding=(1,2)))
|
|
594
|
+
raise typer.Exit(code=1)
|
|
595
|
+
|
|
596
|
+
|
|
597
|
+
@app.command("dashboard")
|
|
598
|
+
def dashboard(host: str = typer.Option("127.0.0.1", help="Bind address; keep localhost for local use"),
|
|
599
|
+
port: int = typer.Option(8787, help="Dashboard port")):
|
|
600
|
+
"""Start the local live scan dashboard."""
|
|
601
|
+
from dashboard_server import ThreadingHTTPServer, Handler
|
|
602
|
+
console.print(f"[green]Dashboard:[/green] http://{host}:{port}")
|
|
603
|
+
ThreadingHTTPServer((host, port), Handler).serve_forever()
|
|
604
|
+
|
|
605
|
+
|
|
606
|
+
@app.command("mass-scan")
|
|
607
|
+
def mass_scan(targets_file: Optional[str] = typer.Option(None, help="File with authorized URLs/hosts"),
|
|
608
|
+
cidr: Optional[str] = typer.Option(None, help="One authorized CIDR; limited by --max-hosts"),
|
|
609
|
+
domains_file: Optional[str] = typer.Option(None, help="File with authorized domains"),
|
|
610
|
+
asn: Optional[str] = typer.Option(None, help="ASN for passive prefix discovery, e.g. AS13335"),
|
|
611
|
+
max_hosts: int = typer.Option(256, min=1, max=2048),
|
|
612
|
+
concurrency: int = typer.Option(10, min=1, max=50),
|
|
613
|
+
confirm: bool = typer.Option(False, "--confirm", help="Confirm written authorization"),
|
|
614
|
+
output: Optional[str] = typer.Option(None, help="JSON output path")):
|
|
615
|
+
"""Probe an explicitly authorized asset inventory; never use without written permission."""
|
|
616
|
+
if not confirm:
|
|
617
|
+
raise typer.BadParameter("--confirm is required for mass scanning")
|
|
618
|
+
from mass_scan import MassScanner
|
|
619
|
+
from asn_scan import ASNScanner
|
|
620
|
+
import json
|
|
621
|
+
def lines(path):
|
|
622
|
+
return [x.strip() for x in Path(path).read_text(encoding="utf-8").splitlines() if x.strip()] if path else []
|
|
623
|
+
targets = lines(targets_file)
|
|
624
|
+
domains = lines(domains_file)
|
|
625
|
+
cidrs = [cidr] if cidr else []
|
|
626
|
+
async def run():
|
|
627
|
+
if asn:
|
|
628
|
+
cidrs.extend(await ASNScanner().prefixes(asn))
|
|
629
|
+
return await MassScanner(concurrency=concurrency, max_hosts=max_hosts).scan(
|
|
630
|
+
targets=targets, cidrs=cidrs, domains=domains, authorized=True)
|
|
631
|
+
results = asyncio.run(run())
|
|
632
|
+
text = json.dumps(results, indent=2)
|
|
633
|
+
if output:
|
|
634
|
+
Path(output).write_text(text, encoding="utf-8")
|
|
635
|
+
console.print(f"[green]Mass-scan results:[/green] {Path(output).resolve()}")
|
|
636
|
+
else:
|
|
637
|
+
console.print(text)
|
|
638
|
+
|
|
639
|
+
|
|
640
|
+
@app.command("nuclei")
|
|
641
|
+
def nuclei(targets_file: str = typer.Option(..., help="File with authorized HTTP(S) targets"),
|
|
642
|
+
templates: str = typer.Option("nuclei-templates", help="Local Nuclei templates directory"),
|
|
643
|
+
severity: str = typer.Option("info,low,medium,high,critical"),
|
|
644
|
+
concurrency: int = typer.Option(10, min=1, max=50),
|
|
645
|
+
rate_limit: int = typer.Option(50, min=1, max=500),
|
|
646
|
+
confirm: bool = typer.Option(False, "--confirm", help="Confirm written authorization"),
|
|
647
|
+
output: Optional[str] = typer.Option(None, help="JSONL output path")):
|
|
648
|
+
"""Run local Nuclei templates against an explicitly authorized target file."""
|
|
649
|
+
if not confirm:
|
|
650
|
+
raise typer.BadParameter("--confirm is required for Nuclei scans")
|
|
651
|
+
from nuclei_integration import NucleiRunner
|
|
652
|
+
targets = [x.strip() for x in Path(targets_file).read_text(encoding="utf-8").splitlines() if x.strip()]
|
|
653
|
+
findings = asyncio.run(NucleiRunner(concurrency=concurrency, rate_limit=rate_limit).run(
|
|
654
|
+
targets, templates, authorized=True, severities=severity.split(",")))
|
|
655
|
+
text = "\\n".join(json.dumps(x, ensure_ascii=False) for x in findings) + ("\\n" if findings else "")
|
|
656
|
+
if output:
|
|
657
|
+
Path(output).write_text(text, encoding="utf-8")
|
|
658
|
+
console.print(f"[green]Nuclei results:[/green] {Path(output).resolve()}")
|
|
659
|
+
else:
|
|
660
|
+
console.print(text)
|
|
661
|
+
|
|
662
|
+
|
|
663
|
+
@app.command()
|
|
664
|
+
def version():
|
|
665
|
+
"""Show version information."""
|
|
666
|
+
print_banner()
|
|
667
|
+
console.print(Panel(
|
|
668
|
+
"[bold white]Version :[/bold white] [cyan]2.0.0[/cyan]\n"
|
|
669
|
+
"[bold white]Features :[/bold white] [cyan]61[/cyan]\n"
|
|
670
|
+
"[bold white]AI Engine :[/bold white] [cyan]Groq (LLaMA 3)[/cyan]\n"
|
|
671
|
+
"[bold white]License :[/bold white] [dim]MIT with Ethical Use Clause[/dim]\n\n"
|
|
672
|
+
"[dim red]For authorized security testing only.[/dim red]",
|
|
673
|
+
title="[bold red]ℹ 0xHunter[/bold red]",
|
|
674
|
+
border_style="red", padding=(1,2),
|
|
675
|
+
))
|
|
676
|
+
|
|
677
|
+
|
|
678
|
+
if __name__ == "__main__":
|
|
679
|
+
app()
|