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.
Files changed (111) hide show
  1. oxhunter-2.0.0/0xhunter.py +679 -0
  2. oxhunter-2.0.0/LICENSE +14 -0
  3. oxhunter-2.0.0/MANIFEST.in +15 -0
  4. oxhunter-2.0.0/PKG-INFO +656 -0
  5. oxhunter-2.0.0/README.md +584 -0
  6. oxhunter-2.0.0/ai/__init__.py +0 -0
  7. oxhunter-2.0.0/ai/attack_chaining.py +368 -0
  8. oxhunter-2.0.0/ai/exploit_generator.py +342 -0
  9. oxhunter-2.0.0/ai/fp_reducer.py +218 -0
  10. oxhunter-2.0.0/ai/nl_report.py +256 -0
  11. oxhunter-2.0.0/ai/payload_generator.py +177 -0
  12. oxhunter-2.0.0/ai/vuln_chaining.py +197 -0
  13. oxhunter-2.0.0/asn_scan.py +25 -0
  14. oxhunter-2.0.0/compliance/bug_bounty.py +128 -0
  15. oxhunter-2.0.0/compliance/owasp_mapping.py +95 -0
  16. oxhunter-2.0.0/compliance/pci_iso_report.py +144 -0
  17. oxhunter-2.0.0/config.yaml +44 -0
  18. oxhunter-2.0.0/core/__init__.py +0 -0
  19. oxhunter-2.0.0/core/auth.py +421 -0
  20. oxhunter-2.0.0/core/config.py +308 -0
  21. oxhunter-2.0.0/core/context_analyzer.py +198 -0
  22. oxhunter-2.0.0/core/crawler.py +227 -0
  23. oxhunter-2.0.0/core/cve_mapping.py +139 -0
  24. oxhunter-2.0.0/core/headless.py +186 -0
  25. oxhunter-2.0.0/core/http_client.py +432 -0
  26. oxhunter-2.0.0/core/logger.py +120 -0
  27. oxhunter-2.0.0/core/paths.py +19 -0
  28. oxhunter-2.0.0/core/payload_engine.py +354 -0
  29. oxhunter-2.0.0/core/pdf_report.py +565 -0
  30. oxhunter-2.0.0/core/proxy.py +142 -0
  31. oxhunter-2.0.0/core/rate_limiter.py +50 -0
  32. oxhunter-2.0.0/core/reporter.py +608 -0
  33. oxhunter-2.0.0/core/resume.py +127 -0
  34. oxhunter-2.0.0/core/scan_db.py +523 -0
  35. oxhunter-2.0.0/core/scanner.py +456 -0
  36. oxhunter-2.0.0/core/screenshots.py +194 -0
  37. oxhunter-2.0.0/core/severity.py +178 -0
  38. oxhunter-2.0.0/core/validator.py +88 -0
  39. oxhunter-2.0.0/core/waf_detector.py +139 -0
  40. oxhunter-2.0.0/dashboard_server.py +120 -0
  41. oxhunter-2.0.0/integrations/burp_export.py +361 -0
  42. oxhunter-2.0.0/integrations/cicd.py +208 -0
  43. oxhunter-2.0.0/integrations/jira_github.py +142 -0
  44. oxhunter-2.0.0/integrations/slack_webhook.py +138 -0
  45. oxhunter-2.0.0/mass_scan.py +91 -0
  46. oxhunter-2.0.0/modules/__init__.py +0 -0
  47. oxhunter-2.0.0/modules/api_versioning.py +194 -0
  48. oxhunter-2.0.0/modules/business_logic.py +201 -0
  49. oxhunter-2.0.0/modules/cmd_injection.py +371 -0
  50. oxhunter-2.0.0/modules/cors.py +309 -0
  51. oxhunter-2.0.0/modules/csrf.py +136 -0
  52. oxhunter-2.0.0/modules/directory_brute.py +397 -0
  53. oxhunter-2.0.0/modules/email_harvest.py +128 -0
  54. oxhunter-2.0.0/modules/git_exposure.py +401 -0
  55. oxhunter-2.0.0/modules/graphql.py +555 -0
  56. oxhunter-2.0.0/modules/headers.py +217 -0
  57. oxhunter-2.0.0/modules/http_smuggling.py +441 -0
  58. oxhunter-2.0.0/modules/idor.py +389 -0
  59. oxhunter-2.0.0/modules/js_analysis.py +502 -0
  60. oxhunter-2.0.0/modules/jwt_attacks.py +442 -0
  61. oxhunter-2.0.0/modules/lfi.py +291 -0
  62. oxhunter-2.0.0/modules/oauth_tester.py +347 -0
  63. oxhunter-2.0.0/modules/open_redirect.py +207 -0
  64. oxhunter-2.0.0/modules/password_policy.py +506 -0
  65. oxhunter-2.0.0/modules/prototype_pollution.py +389 -0
  66. oxhunter-2.0.0/modules/race_condition.py +323 -0
  67. oxhunter-2.0.0/modules/session_fixation.py +530 -0
  68. oxhunter-2.0.0/modules/sqli.py +302 -0
  69. oxhunter-2.0.0/modules/ssl_tls.py +496 -0
  70. oxhunter-2.0.0/modules/ssrf.py +303 -0
  71. oxhunter-2.0.0/modules/subdomain.py +279 -0
  72. oxhunter-2.0.0/modules/supply_chain.py +306 -0
  73. oxhunter-2.0.0/modules/tech_fingerprint.py +595 -0
  74. oxhunter-2.0.0/modules/waf_bypass.py +536 -0
  75. oxhunter-2.0.0/modules/websocket.py +154 -0
  76. oxhunter-2.0.0/modules/xss.py +258 -0
  77. oxhunter-2.0.0/modules/xxe.py +408 -0
  78. oxhunter-2.0.0/nuclei_integration.py +71 -0
  79. oxhunter-2.0.0/oxhunter.egg-info/PKG-INFO +656 -0
  80. oxhunter-2.0.0/oxhunter.egg-info/SOURCES.txt +109 -0
  81. oxhunter-2.0.0/oxhunter.egg-info/dependency_links.txt +1 -0
  82. oxhunter-2.0.0/oxhunter.egg-info/entry_points.txt +2 -0
  83. oxhunter-2.0.0/oxhunter.egg-info/requires.txt +33 -0
  84. oxhunter-2.0.0/oxhunter.egg-info/top_level.txt +14 -0
  85. oxhunter-2.0.0/oxhunter_cli.py +36 -0
  86. oxhunter-2.0.0/oxhunter_main.py +16 -0
  87. oxhunter-2.0.0/payloads/auth_bypass/auth_bypass.txt +74 -0
  88. oxhunter-2.0.0/payloads/cmd_injection/cmd_injection.txt +165 -0
  89. oxhunter-2.0.0/payloads/cors/cors.txt +21 -0
  90. oxhunter-2.0.0/payloads/csrf/csrf.txt +10 -0
  91. oxhunter-2.0.0/payloads/graphql/graphql.txt +23 -0
  92. oxhunter-2.0.0/payloads/http_smuggling/http_smuggling.txt +16 -0
  93. oxhunter-2.0.0/payloads/idor/idor_params.txt +83 -0
  94. oxhunter-2.0.0/payloads/jwt/jwt.txt +61 -0
  95. oxhunter-2.0.0/payloads/lfi/lfi.txt +139 -0
  96. oxhunter-2.0.0/payloads/open_redirect/open_redirect.txt +68 -0
  97. oxhunter-2.0.0/payloads/prototype_pollution/prototype_pollution.txt +55 -0
  98. oxhunter-2.0.0/payloads/sqli/sqli.txt +221 -0
  99. oxhunter-2.0.0/payloads/ssrf/ssrf.txt +184 -0
  100. oxhunter-2.0.0/payloads/ssti/ssti.txt +87 -0
  101. oxhunter-2.0.0/payloads/waf_bypass/waf_bypass.txt +95 -0
  102. oxhunter-2.0.0/payloads/wordlists/common_dirs.txt +163 -0
  103. oxhunter-2.0.0/payloads/wordlists/sensitive_files.txt +111 -0
  104. oxhunter-2.0.0/payloads/wordlists/subdomains.txt +127 -0
  105. oxhunter-2.0.0/payloads/xss/xss.txt +270 -0
  106. oxhunter-2.0.0/payloads/xxe/xxe.txt +60 -0
  107. oxhunter-2.0.0/pyproject.toml +107 -0
  108. oxhunter-2.0.0/recon/passive_recon.py +433 -0
  109. oxhunter-2.0.0/setup.cfg +4 -0
  110. oxhunter-2.0.0/utils/__init__.py +0 -0
  111. 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()