moriarty-project 0.1.25__py3-none-any.whl → 0.1.27__py3-none-any.whl
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.
- moriarty/__init__.py +1 -1
- moriarty/cli/app.py +2 -2
- moriarty/cli/domain_cmd.py +8 -5
- moriarty/modules/domain_scanner.py +188 -60
- moriarty/modules/port_scanner_nmap.py +557 -0
- moriarty/modules/web_crawler.py +774 -152
- {moriarty_project-0.1.25.dist-info → moriarty_project-0.1.27.dist-info}/METADATA +7 -5
- {moriarty_project-0.1.25.dist-info → moriarty_project-0.1.27.dist-info}/RECORD +10 -11
- moriarty/cli/wifippler.py +0 -124
- moriarty/modules/port_scanner.py +0 -942
- {moriarty_project-0.1.25.dist-info → moriarty_project-0.1.27.dist-info}/WHEEL +0 -0
- {moriarty_project-0.1.25.dist-info → moriarty_project-0.1.27.dist-info}/entry_points.txt +0 -0
moriarty/__init__.py
CHANGED
moriarty/cli/app.py
CHANGED
@@ -156,14 +156,14 @@ def self_update():
|
|
156
156
|
try:
|
157
157
|
# Verifica se está instalado com pipx
|
158
158
|
if check_pipx_installed():
|
159
|
-
console.print("🔄 Atualizando via pipx...", style="bold
|
159
|
+
console.print("🔄 Atualizando via pipx...", style="bold red")
|
160
160
|
result = subprocess.run(
|
161
161
|
["pipx", "install", "--upgrade", "moriarty-project"],
|
162
162
|
capture_output=True,
|
163
163
|
text=True
|
164
164
|
)
|
165
165
|
else:
|
166
|
-
console.print("🔄 Atualizando via pip...", style="bold
|
166
|
+
console.print("🔄 Atualizando via pip...", style="bold red")
|
167
167
|
result = subprocess.run(
|
168
168
|
[sys.executable, "-m", "pip", "install", "--upgrade", "moriarty-project"],
|
169
169
|
capture_output=True,
|
moriarty/cli/domain_cmd.py
CHANGED
@@ -1,14 +1,12 @@
|
|
1
1
|
"""Comandos de scanning de domínios/IPs."""
|
2
2
|
|
3
|
-
import
|
4
|
-
import json
|
5
|
-
from typing import Optional, Dict, List
|
3
|
+
from typing import Optional
|
6
4
|
|
7
5
|
import typer
|
8
6
|
|
9
7
|
from moriarty.modules.web_crawler import WebCrawler
|
10
8
|
|
11
|
-
from moriarty.modules.
|
9
|
+
from moriarty.modules.port_scanner_nmap import PortScanner, PROFILES
|
12
10
|
from moriarty.modules.passive_recon import PassiveRecon
|
13
11
|
from rich.console import Console
|
14
12
|
|
@@ -28,6 +26,7 @@ def scan_full(
|
|
28
26
|
stealth: int = typer.Option(0, "--stealth", "-s", help="Stealth level (0-4)"),
|
29
27
|
threads: int = typer.Option(10, "--threads", "-t", help="Threads concorrentes"),
|
30
28
|
timeout: int = typer.Option(30, "--timeout", help="Timeout em segundos"),
|
29
|
+
ports: str = typer.Option("quick", "--ports", "-p", help="Perfil de portas: quick, web, db, full, all"),
|
31
30
|
output: str = typer.Option(None, "--output", "-o", help="Arquivo de saída"),
|
32
31
|
verbose: bool = typer.Option(False, "--verbose", help="Ativar saída detalhada"),
|
33
32
|
):
|
@@ -48,9 +47,13 @@ def scan_full(
|
|
48
47
|
logging.getLogger("httpcore").setLevel(logging.ERROR)
|
49
48
|
logging.getLogger("moriarty").setLevel(logging.ERROR)
|
50
49
|
|
50
|
+
# Converte a string de módulos para lista
|
51
|
+
modules_list = modules.split(",") if modules != "all" else None
|
52
|
+
|
51
53
|
scanner = DomainScanner(
|
52
54
|
target=target,
|
53
|
-
modules=
|
55
|
+
modules=modules_list,
|
56
|
+
ports_profile=ports,
|
54
57
|
stealth_level=stealth,
|
55
58
|
threads=threads,
|
56
59
|
timeout=timeout,
|
@@ -76,6 +76,7 @@ class DomainScanner:
|
|
76
76
|
stealth_level: int = 0,
|
77
77
|
threads: int = 10,
|
78
78
|
timeout: int = 30,
|
79
|
+
ports_profile: str = "quick",
|
79
80
|
verbose: bool = False,
|
80
81
|
):
|
81
82
|
self.target = target
|
@@ -83,6 +84,7 @@ class DomainScanner:
|
|
83
84
|
self.stealth_level = stealth_level
|
84
85
|
self.threads = threads
|
85
86
|
self.timeout = timeout
|
87
|
+
self.ports_profile = ports_profile.lower()
|
86
88
|
self.verbose = verbose
|
87
89
|
self.result = ScanResult(target=target)
|
88
90
|
self.stealth = None
|
@@ -102,12 +104,20 @@ class DomainScanner:
|
|
102
104
|
async def run(self):
|
103
105
|
"""Executa scan completo."""
|
104
106
|
self._prepare_modules()
|
107
|
+
# Resolve o IP do domínio
|
108
|
+
try:
|
109
|
+
import socket
|
110
|
+
ip = socket.gethostbyname(self.target)
|
111
|
+
target_display = f"{self.target} [{ip}]"
|
112
|
+
except Exception:
|
113
|
+
target_display = self.target
|
114
|
+
|
105
115
|
# Banner profissional
|
106
116
|
banner = Panel(
|
107
|
-
f"[bold white]Target:[/bold white] [
|
117
|
+
f"[bold white]Target:[/bold white] [red]{target_display}[/red]\n"
|
108
118
|
f"[dim]Modules: {', '.join(self.modules)} | Stealth: {self.stealth_level}[/dim]",
|
109
|
-
title="[bold
|
110
|
-
border_style="
|
119
|
+
title="[bold red]🌐 Domain Scanner[/bold red]",
|
120
|
+
border_style="red",
|
111
121
|
padding=(1, 2),
|
112
122
|
)
|
113
123
|
console.print(banner)
|
@@ -148,7 +158,7 @@ class DomainScanner:
|
|
148
158
|
|
149
159
|
async def _run_dns(self):
|
150
160
|
"""Módulo DNS."""
|
151
|
-
console.print("\n[bold
|
161
|
+
console.print("\n[bold red]▶ DNS Enumeration[/bold red]")
|
152
162
|
|
153
163
|
try:
|
154
164
|
from moriarty.net.dns_client import DNSClient
|
@@ -179,7 +189,7 @@ class DomainScanner:
|
|
179
189
|
|
180
190
|
async def _run_subdiscover(self):
|
181
191
|
"""Módulo Subdomain Discovery."""
|
182
|
-
console.print("\n[bold
|
192
|
+
console.print("\n[bold red]▶ Subdomain Discovery[/bold red]")
|
183
193
|
|
184
194
|
try:
|
185
195
|
from moriarty.modules.subdomain_discovery import SubdomainDiscovery
|
@@ -215,7 +225,7 @@ class DomainScanner:
|
|
215
225
|
|
216
226
|
async def _run_wayback(self):
|
217
227
|
"""Módulo Wayback Machine."""
|
218
|
-
console.print("\n[bold
|
228
|
+
console.print("\n[bold red]▶ Wayback Machine[/bold red]")
|
219
229
|
|
220
230
|
try:
|
221
231
|
from moriarty.modules.wayback_discovery import WaybackDiscovery
|
@@ -244,37 +254,87 @@ class DomainScanner:
|
|
244
254
|
|
245
255
|
async def _run_ports(self):
|
246
256
|
"""Módulo Port Scan."""
|
247
|
-
console.print("\n[bold
|
257
|
+
console.print("\n[bold red]▶ Port Scanning[/bold red]")
|
248
258
|
|
249
259
|
try:
|
250
|
-
from moriarty.modules.
|
260
|
+
from moriarty.modules.port_scanner_nmap import PortScanner
|
251
261
|
|
252
|
-
|
262
|
+
# Usa o perfil de portas fornecido ou define baseado no timeout se não especificado
|
263
|
+
profile = self.ports_profile if hasattr(self, 'ports_profile') else ("extended" if self.timeout > 45 else "quick")
|
264
|
+
|
265
|
+
# Log do perfil que será usado
|
266
|
+
console.print(f" 🔧 Usando perfil de portas: [bold]{profile}[/]")
|
267
|
+
|
268
|
+
# Cria o scanner com as configurações apropriadas
|
253
269
|
scanner = PortScanner(
|
254
270
|
target=self.target,
|
255
|
-
|
256
|
-
concurrency=max(40, self.threads * 30),
|
257
|
-
timeout=max(0.5, min(2.0, self.timeout / 12)),
|
271
|
+
ports=profile,
|
258
272
|
stealth_level=self.stealth_level,
|
273
|
+
resolve_services=True,
|
274
|
+
check_vulns=False
|
259
275
|
)
|
276
|
+
|
260
277
|
results = await scanner.scan()
|
278
|
+
|
279
|
+
if not results:
|
280
|
+
console.print(" ℹ️ Nenhuma porta aberta encontrada.")
|
281
|
+
self.result.port_details = []
|
282
|
+
self.result.open_ports = []
|
283
|
+
return
|
284
|
+
|
285
|
+
# Processa os resultados
|
261
286
|
self.result.port_details = [asdict(entry) for entry in results]
|
262
287
|
self.result.open_ports = [entry.port for entry in results]
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
preview = ", ".join(
|
267
|
-
f"{entry.port} ({entry.banner[:18]}...)" if entry.banner else str(entry.port)
|
268
|
-
for entry in results[:5]
|
269
|
-
)
|
270
|
-
console.print(f" [dim]→[/dim] {preview}")
|
288
|
+
|
289
|
+
# Exibe os resultados em uma tabela
|
290
|
+
self._display_port_results(results)
|
271
291
|
|
272
292
|
except Exception as e:
|
273
|
-
|
293
|
+
import traceback
|
294
|
+
console.print(f" [red]✗[/red] Port scan failed: {str(e)}")
|
295
|
+
console.print(f"[yellow]Detalhes:[/yellow] {traceback.format_exc()}")
|
296
|
+
|
297
|
+
def _display_port_results(self, results):
|
298
|
+
"""Exibe os resultados da varredura de portas em formato de tabela."""
|
299
|
+
from rich.table import Table, box
|
300
|
+
|
301
|
+
# Filtra apenas portas abertas
|
302
|
+
open_ports = [r for r in results if getattr(r, 'status', '').lower() == 'open']
|
303
|
+
|
304
|
+
if not open_ports:
|
305
|
+
console.print("ℹ️ Nenhuma porta aberta encontrada.")
|
306
|
+
return
|
307
|
+
|
308
|
+
# Cria tabela de resultados
|
309
|
+
table = Table(title="🚪 Portas abertas:", box=box.ROUNDED)
|
310
|
+
table.add_column("Porta", style="cyan")
|
311
|
+
table.add_column("Status", style="green")
|
312
|
+
table.add_column("Serviço", style="yellow")
|
313
|
+
table.add_column("Detalhes", style="white")
|
314
|
+
|
315
|
+
for entry in open_ports:
|
316
|
+
service = getattr(entry, 'service', None)
|
317
|
+
service_name = getattr(service, 'name', 'desconhecido') if service else 'desconhecido'
|
318
|
+
version = getattr(service, 'version', '')
|
319
|
+
details = version if version else ""
|
320
|
+
|
321
|
+
# Adiciona informações de vulnerabilidades se disponíveis
|
322
|
+
if service and hasattr(service, 'vulns') and service.vulns:
|
323
|
+
vulns = ", ".join(service.vulns[:2])
|
324
|
+
if len(service.vulns) > 2:
|
325
|
+
vulns += f" (+{len(service.vulns)-2} mais)"
|
326
|
+
details += f"\n🔴 {vulns}"
|
327
|
+
|
328
|
+
table.add_row(
|
329
|
+
str(entry.port),
|
330
|
+
"🟢 ABERTA",
|
331
|
+
service_name,
|
332
|
+
details.strip() or "-"
|
333
|
+
)
|
274
334
|
|
275
335
|
async def _run_ssl(self):
|
276
336
|
"""Módulo SSL/TLS."""
|
277
|
-
console.print("\n[bold
|
337
|
+
console.print("\n[bold red]▶ SSL/TLS Analysis[/bold red]")
|
278
338
|
|
279
339
|
try:
|
280
340
|
from moriarty.modules.tls_validator import TLSCertificateValidator
|
@@ -287,7 +347,7 @@ class DomainScanner:
|
|
287
347
|
|
288
348
|
async def _run_template_scan(self):
|
289
349
|
"""Módulo Template Scanner."""
|
290
|
-
console.print("\n[bold
|
350
|
+
console.print("\n[bold red]▶ Template Scanner[/bold red]")
|
291
351
|
|
292
352
|
try:
|
293
353
|
from moriarty.modules.template_scanner import TemplateScanner
|
@@ -338,49 +398,109 @@ class DomainScanner:
|
|
338
398
|
|
339
399
|
async def _run_crawl(self):
|
340
400
|
"""Executa crawler leve para inventário de rotas."""
|
341
|
-
console.print("\n[bold
|
401
|
+
console.print("\n[bold red]▶ Web Crawler[/bold red]")
|
342
402
|
try:
|
403
|
+
console.print(" 🔍 Iniciando configuração do Web Crawler...")
|
343
404
|
from moriarty.modules.web_crawler import WebCrawler
|
344
405
|
|
345
406
|
base_url = self._default_base_url()
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
)
|
407
|
+
console.print(f" 🌐 URL base: {base_url}")
|
408
|
+
|
409
|
+
# Configurações do crawler
|
410
|
+
max_pages = max(50, self.threads * 10)
|
411
|
+
max_depth = 2
|
412
|
+
concurrency = max(5, self.threads)
|
413
|
+
|
414
|
+
console.print(f" ⚙️ Configurações: max_pages={max_pages}, max_depth={max_depth}, concurrency={concurrency}")
|
415
|
+
|
354
416
|
try:
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
417
|
+
console.print(" 🚀 Iniciando crawler...")
|
418
|
+
try:
|
419
|
+
# Log de debug: Verificando se o WebCrawler pode ser instanciado
|
420
|
+
console.print(" 🔧 Instanciando WebCrawler...")
|
421
|
+
crawler = WebCrawler(
|
422
|
+
base_url=base_url,
|
423
|
+
max_pages=max_pages,
|
424
|
+
max_depth=max_depth,
|
425
|
+
concurrency=concurrency,
|
426
|
+
follow_subdomains=False,
|
427
|
+
stealth=self.stealth,
|
428
|
+
)
|
429
|
+
console.print(" ✅ WebCrawler instanciado com sucesso!")
|
430
|
+
except Exception as e:
|
431
|
+
console.print(f" ❌ [red]Erro ao instanciar WebCrawler: {str(e)}[/red]")
|
432
|
+
logger.error("webcrawler.init_error", error=str(e), exc_info=True)
|
433
|
+
raise
|
434
|
+
|
435
|
+
try:
|
436
|
+
console.print(" 🔄 Executando varredura...")
|
437
|
+
pages = await crawler.crawl()
|
438
|
+
console.print(f" ✅ Varredura concluída! {len(pages)} páginas encontradas.")
|
439
|
+
|
440
|
+
# Log detalhado das páginas encontradas
|
441
|
+
for i, (url, page) in enumerate(pages.items(), 1):
|
442
|
+
console.print(f" {i}. [blue]{url}[/blue] (Status: {page.status})")
|
443
|
+
if page.error:
|
444
|
+
console.print(f" [red]Erro: {page.error}[/red]")
|
445
|
+
except Exception as e:
|
446
|
+
console.print(f" ❌ [red]Erro durante o crawler: {str(e)}[/red]")
|
447
|
+
logger.error("webcrawler.crawl_error", error=str(e), exc_info=True)
|
448
|
+
raise
|
449
|
+
|
450
|
+
try:
|
451
|
+
self.result.crawl_map = {
|
452
|
+
url: {
|
453
|
+
"status": page.status,
|
454
|
+
"title": page.title,
|
455
|
+
"forms": page.forms,
|
456
|
+
"links": page.links,
|
457
|
+
}
|
458
|
+
for url, page in pages.items()
|
459
|
+
}
|
460
|
+
console.print(" 📊 Dados do crawl processados com sucesso!")
|
461
|
+
except Exception as e:
|
462
|
+
console.print(f" ❌ [red]Erro ao processar resultados do crawl: {str(e)}[/red]")
|
463
|
+
logger.error("webcrawler.process_error", error=str(e), exc_info=True)
|
464
|
+
raise
|
465
|
+
|
466
|
+
try:
|
467
|
+
extracted = self._extract_targets_from_crawl(pages)
|
468
|
+
console.print(f" 🔗 {len(extracted)} alvos extraídos para fuzzing")
|
469
|
+
|
470
|
+
for definition in extracted:
|
471
|
+
self._register_web_target(
|
472
|
+
definition["url"],
|
473
|
+
definition["method"],
|
474
|
+
definition["params"]
|
475
|
+
)
|
476
|
+
|
477
|
+
console.print(f" [green]✓[/green] Crawled {len(pages)} pages")
|
478
|
+
if extracted:
|
479
|
+
console.print(f" [dim]→[/dim] {len(extracted)} endpoints para fuzz")
|
480
|
+
except Exception as e:
|
481
|
+
console.print(f"❌ [red]Erro ao extrair alvos do crawl: {str(e)}[/red]")
|
482
|
+
logger.error("webcrawler.extract_error", error=str(e), exc_info=True)
|
483
|
+
raise
|
484
|
+
|
485
|
+
except Exception as e:
|
486
|
+
console.print(f" [red]✗[/red] Crawl falhou: {str(e)}")
|
487
|
+
logger.error("domain.crawl.failed", error=str(e), exc_info=True)
|
488
|
+
# Tenta fechar o crawler mesmo em caso de erro
|
489
|
+
if 'crawler' in locals():
|
490
|
+
try:
|
491
|
+
await crawler.close()
|
492
|
+
except:
|
493
|
+
pass
|
494
|
+
raise
|
376
495
|
|
377
496
|
except Exception as exc:
|
378
|
-
console.print(" [red]✗[/red]
|
379
|
-
logger.
|
497
|
+
console.print(f" [red]✗[/red] Erro fatal no Web Crawler: {str(exc)}")
|
498
|
+
logger.error("domain.crawl.fatal_error", error=str(exc), exc_info=True)
|
499
|
+
raise
|
380
500
|
|
381
501
|
async def _run_fuzzer(self):
|
382
502
|
"""Executa directory fuzzing para expandir superfícies."""
|
383
|
-
console.print("\n[bold
|
503
|
+
console.print("\n[bold red]▶ Directory Fuzzing[/bold red]")
|
384
504
|
try:
|
385
505
|
from moriarty.modules.directory_fuzzer import DirectoryFuzzer
|
386
506
|
|
@@ -406,7 +526,7 @@ class DomainScanner:
|
|
406
526
|
|
407
527
|
async def _run_vuln_scan(self):
|
408
528
|
"""Executa detecção de vulnerabilidades XSS/SQLi."""
|
409
|
-
console.print("\n[bold
|
529
|
+
console.print("\n[bold red]▶ Web Vulnerability Scan[/bold red]")
|
410
530
|
|
411
531
|
if not self.web_targets:
|
412
532
|
console.print(" [yellow]⚠️ Nenhum endpoint coletado para testar[/yellow]")
|
@@ -430,7 +550,7 @@ class DomainScanner:
|
|
430
550
|
|
431
551
|
async def _run_waf_detect(self):
|
432
552
|
"""Módulo WAF Detection."""
|
433
|
-
console.print("\n[bold
|
553
|
+
console.print("\n[bold red]▶ WAF Detection[/bold red]")
|
434
554
|
|
435
555
|
try:
|
436
556
|
from moriarty.modules.waf_detector import WAFDetector
|
@@ -461,7 +581,7 @@ class DomainScanner:
|
|
461
581
|
def _show_summary(self):
|
462
582
|
"""Mostra resumo final."""
|
463
583
|
# Tree de resultados
|
464
|
-
tree = Tree(f"\n[bold
|
584
|
+
tree = Tree(f"\n[bold red]📊 Scan Summary[/bold red]")
|
465
585
|
|
466
586
|
if self.result.dns_info:
|
467
587
|
dns_node = tree.add("[bold]DNS Records[/bold]")
|
@@ -610,7 +730,15 @@ class DomainScanner:
|
|
610
730
|
self.result.web_targets = self.web_targets
|
611
731
|
|
612
732
|
def _default_base_url(self) -> str:
|
613
|
-
|
733
|
+
"""Retorna a URL base para o crawler, garantindo que tenha o esquema correto."""
|
734
|
+
target = self.target
|
735
|
+
|
736
|
+
# Se o alvo já tiver esquema, retorna como está
|
737
|
+
if target.startswith(('http://', 'https://')):
|
738
|
+
return target
|
739
|
+
|
740
|
+
# Se não tiver esquema, adiciona https://
|
741
|
+
return f"https://{target}"
|
614
742
|
|
615
743
|
def export(self, output: str):
|
616
744
|
"""Exporta resultados."""
|