moriarty-project 0.1.26__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.
@@ -1,19 +1,16 @@
1
1
  """Comandos de scanning de domínios/IPs."""
2
2
 
3
- import asyncio
4
- import json
5
- from typing import Optional, Dict, List
3
+ from typing import Optional
6
4
 
7
5
  import typer
8
- from rich.console import Console
9
- from rich.panel import Panel
10
- from rich.table import Table, box
11
6
 
12
7
  from moriarty.modules.web_crawler import WebCrawler
13
- from moriarty.modules.port_scanner import PortScanner, PROFILES
8
+
9
+ from moriarty.modules.port_scanner_nmap import PortScanner, PROFILES
14
10
  from moriarty.modules.passive_recon import PassiveRecon
11
+ from rich.console import Console
15
12
 
16
- app = typer.Typer(help="🔍 Ferramentas avançadas de reconhecimento de domínios")
13
+ app = typer.Typer(name="domain", help="🌐 Domain/IP reconnaissance and scanning.")
17
14
  console = Console()
18
15
 
19
16
 
@@ -29,6 +26,7 @@ def scan_full(
29
26
  stealth: int = typer.Option(0, "--stealth", "-s", help="Stealth level (0-4)"),
30
27
  threads: int = typer.Option(10, "--threads", "-t", help="Threads concorrentes"),
31
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"),
32
30
  output: str = typer.Option(None, "--output", "-o", help="Arquivo de saída"),
33
31
  verbose: bool = typer.Option(False, "--verbose", help="Ativar saída detalhada"),
34
32
  ):
@@ -49,9 +47,13 @@ def scan_full(
49
47
  logging.getLogger("httpcore").setLevel(logging.ERROR)
50
48
  logging.getLogger("moriarty").setLevel(logging.ERROR)
51
49
 
50
+ # Converte a string de módulos para lista
51
+ modules_list = modules.split(",") if modules != "all" else None
52
+
52
53
  scanner = DomainScanner(
53
54
  target=target,
54
- modules=modules.split(",") if modules != "all" else None,
55
+ modules=modules_list,
56
+ ports_profile=ports,
55
57
  stealth_level=stealth,
56
58
  threads=threads,
57
59
  timeout=timeout,
@@ -62,7 +64,7 @@ def scan_full(
62
64
 
63
65
  if output:
64
66
  scanner.export(output)
65
- console.print(f"[green]✅[/green] Results saved to: [red]{output}[/red]\n")
67
+ console.print(f"[green]✅[/green] Results saved to: [cyan]{output}[/cyan]\n")
66
68
 
67
69
 
68
70
  @app.command("subdiscover")
@@ -102,7 +104,7 @@ def subdomain_discovery(
102
104
  from moriarty.modules.subdomain_discovery import SubdomainDiscovery
103
105
  import asyncio
104
106
 
105
- console.print(f"[bold red]🔍 Descobrindo subdomínios de:[/bold red] {domain}\n")
107
+ console.print(f"[bold cyan]🔍 Descobrindo subdomínios de:[/bold cyan] {domain}\n")
106
108
 
107
109
  discovery = SubdomainDiscovery(
108
110
  domain=domain,
@@ -142,7 +144,7 @@ def wayback_urls(
142
144
  from moriarty.modules.wayback_discovery import WaybackDiscovery
143
145
  import asyncio
144
146
 
145
- console.print(f"[bold red]🕰️ Buscando URLs históricas de:[/bold red] {domain}\n")
147
+ console.print(f"[bold cyan]🕰️ Buscando URLs históricas de:[/bold cyan] {domain}\n")
146
148
 
147
149
  wayback = WaybackDiscovery(
148
150
  domain=domain,
@@ -182,7 +184,7 @@ def template_scan(
182
184
 
183
185
  from moriarty.modules.template_scanner import TemplateScanner
184
186
 
185
- console.print(f"[bold red]📝 Template scan em:[/bold red] {target}\n")
187
+ console.print(f"[bold cyan]📝 Template scan em:[/bold cyan] {target}\n")
186
188
 
187
189
  scanner = TemplateScanner(
188
190
  target=target,
@@ -228,7 +230,7 @@ def run_pipeline(
228
230
  from moriarty.modules.pipeline_orchestrator import PipelineOrchestrator
229
231
  import asyncio
230
232
 
231
- console.print(f"[bold red]🔄 Executando pipeline:[/bold red] {pipeline_file}\n")
233
+ console.print(f"[bold cyan]🔄 Executando pipeline:[/bold cyan] {pipeline_file}\n")
232
234
 
233
235
  orchestrator = PipelineOrchestrator(
234
236
  pipeline_file=pipeline_file,
@@ -284,7 +286,41 @@ def stealth_command(
284
286
  console.print(f"[red]❌ Ação inválida: {action}[/red]")
285
287
 
286
288
 
287
- # A função port_scan foi movida para a implementação mais completa abaixo
289
+ @app.command("ports")
290
+ def port_scan(
291
+ target: str = typer.Argument(..., help="IP ou domínio"),
292
+ ports: str = typer.Option("common", "--ports", "-p", help="Portas: common, all, 1-1000, 80,443"),
293
+ scan_type: str = typer.Option("syn", "--type", "-t", help="Tipo: syn, tcp, udp"),
294
+ stealth: int = typer.Option(0, "--stealth", "-s", help="Stealth level (0-4)"),
295
+ output: str = typer.Option(None, "--output", "-o", help="Arquivo de saída"),
296
+ verbose: bool = typer.Option(False, "--verbose", help="Ativar saída detalhada"),
297
+ ):
298
+ """
299
+ 🔌 Scan avançado de portas.
300
+
301
+ Exemplos:
302
+ moriarty domain ports 8.8.8.8
303
+ moriarty domain ports target.com --ports 1-10000
304
+ moriarty domain ports target.com --type syn --stealth 3
305
+ """
306
+ from moriarty.modules.port_scanner import PortScanner
307
+ import asyncio
308
+
309
+ console.print(f"[bold cyan]🔌 Port scan em:[/bold cyan] {target}\n")
310
+
311
+ scanner = PortScanner(
312
+ target=target,
313
+ ports=ports,
314
+ scan_type=scan_type,
315
+ stealth_level=stealth,
316
+ )
317
+
318
+ results = asyncio.run(scanner.scan())
319
+
320
+ console.print(f"\n[green]✅ {len(results)} portas abertas encontradas[/green]")
321
+
322
+ if output:
323
+ scanner.export(results, output)
288
324
 
289
325
 
290
326
  @app.command("waf-detect")
@@ -309,7 +345,7 @@ def waf_detection(
309
345
  from moriarty.modules.waf_detector import WAFDetector
310
346
  import asyncio
311
347
 
312
- console.print(f"[bold red]🛡️ Detectando WAF em:[/bold red] {target}\n")
348
+ console.print(f"[bold cyan]🛡️ Detectando WAF em:[/bold cyan] {target}\n")
313
349
 
314
350
  detector = WAFDetector(target=target)
315
351
  waf_info = asyncio.run(detector.detect())
@@ -319,7 +355,7 @@ def waf_detection(
319
355
  console.print(f"[dim]Confidence: {waf_info['confidence']}%[/dim]")
320
356
 
321
357
  if bypass:
322
- console.print("\n[red]🔓 Tentando bypass...[/red]")
358
+ console.print("\n[cyan]🔓 Tentando bypass...[/cyan]")
323
359
  bypass_methods = asyncio.run(detector.attempt_bypass())
324
360
 
325
361
  for method in bypass_methods:
@@ -360,12 +396,12 @@ def passive_recon(
360
396
  console = Console()
361
397
 
362
398
  # Cabeçalho
363
- console.print(f"\n[bold red]🌐 Moriarty Passive Recon[/bold red]")
399
+ console.print(f"\n[bold cyan]🌐 Moriarty Passive Recon[/bold cyan]")
364
400
  console.print(f"[dim]Alvo:[/dim] {domain}")
365
401
  console.print(f"[dim]Data:[/dim] {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
366
402
 
367
403
  async def _run():
368
- with console.status(f"[bold green]Coletando informações sobre [red]{domain}[/red]..."):
404
+ with console.status(f"[bold green]Coletando informações sobre [cyan]{domain}[/cyan]..."):
369
405
  recon = PassiveRecon(domain)
370
406
  try:
371
407
  return await recon.collect()
@@ -379,15 +415,15 @@ def passive_recon(
379
415
  if status_only:
380
416
  sub_count = sum(len(v) for v in payload["subdomains"].values())
381
417
  console.print(f"[bold]🔍 Resumo do Reconhecimento[/bold]")
382
- console.print(f" • [red]Subdomínios:[/red] {sub_count} encontrados")
383
- console.print(f" • [red]Fontes:[/red] {', '.join(payload['subdomains'].keys()) or 'nenhuma'}")
384
- console.print(f" • [red]Credenciais vazadas:[/red] {len(payload['leaks'])}")
418
+ console.print(f" • [cyan]Subdomínios:[/cyan] {sub_count} encontrados")
419
+ console.print(f" • [cyan]Fontes:[/cyan] {', '.join(payload['subdomains'].keys()) or 'nenhuma'}")
420
+ console.print(f" • [cyan]Credenciais vazadas:[/cyan] {len(payload['leaks'])}")
385
421
  return
386
422
 
387
423
  # Seção de Subdomínios
388
424
  if payload.get("subdomains"):
389
425
  table = Table(title="[bold]🌐 Subdomínios Encontrados[/bold]", box=box.ROUNDED)
390
- table.add_column("Subdomínio", style="red")
426
+ table.add_column("Subdomínio", style="cyan")
391
427
  table.add_column("Fonte", style="green")
392
428
 
393
429
  for source, subdomains in payload["subdomains"].items():
@@ -399,7 +435,7 @@ def passive_recon(
399
435
  # Seção de Tecnologias
400
436
  if payload.get("technologies", {}).get("detections"):
401
437
  tech_table = Table(title="[bold]🛠️ Tecnologias Detectadas[/bold]", box=box.ROUNDED)
402
- tech_table.add_column("Tecnologia", style="red")
438
+ tech_table.add_column("Tecnologia", style="cyan")
403
439
  tech_table.add_column("Confiança", style="green")
404
440
  tech_table.add_column("Categoria", style="magenta")
405
441
 
@@ -414,7 +450,7 @@ def passive_recon(
414
450
 
415
451
  # Seção de Segurança
416
452
  security_table = Table(title="[bold]🔒 Análise de Segurança[/bold]", box=box.ROUNDED)
417
- security_table.add_column("Item", style="red")
453
+ security_table.add_column("Item", style="cyan")
418
454
  security_table.add_column("Status", style="green")
419
455
 
420
456
  # Verifica HSTS
@@ -435,7 +471,7 @@ def passive_recon(
435
471
  # Seção de Reputação
436
472
  if payload.get("reputation"):
437
473
  rep_table = Table(title="[bold]📊 Reputação do Domínio[/bold]", box=box.ROUNDED)
438
- rep_table.add_column("Fonte", style="red")
474
+ rep_table.add_column("Fonte", style="cyan")
439
475
  rep_table.add_column("Status", style="green")
440
476
 
441
477
  for source, data in payload["reputation"].items():
@@ -458,7 +494,7 @@ def passive_recon(
458
494
  if output:
459
495
  with open(output, "w", encoding="utf-8") as handle:
460
496
  json.dump(payload, handle, indent=2, ensure_ascii=False)
461
- console.print(f"\n[green]✓[/green] Resultados salvos em: [red]{output}[/red]")
497
+ console.print(f"\n[green]✓[/green] Resultados salvos em: [cyan]{output}[/cyan]")
462
498
 
463
499
  except Exception as e:
464
500
  console.print(f"[red]✗ Erro durante o reconhecimento:[/red] {str(e)}")
@@ -468,384 +504,6 @@ def passive_recon(
468
504
  console.print(f"[yellow]⚠ Log de erro salvo em: {output}[/yellow]")
469
505
 
470
506
 
471
- import asyncio
472
-
473
- async def _port_scan_async(
474
- target: str,
475
- profile: str,
476
- stealth: int,
477
- resolve_services: bool,
478
- check_vulns: bool,
479
- format: str,
480
- output: Optional[str]
481
- ):
482
- """Função assíncrona que realiza a varredura de portas."""
483
- # Código da função port_scan aqui...
484
- console = Console()
485
-
486
- # Valida os parâmetros
487
- def validate_parameters():
488
- if stealth < 0 or stealth > 5:
489
- console.print("[red]Erro:[/red] O nível de stealth deve estar entre 0 e 5")
490
- raise typer.Exit(1)
491
-
492
- if profile not in PROFILES:
493
- console.print(f"[red]Erro:[/red] Perfil inválido. Use um dos seguintes: {', '.join(PROFILES.keys())}")
494
- raise typer.Exit(1)
495
-
496
- # Configura o console do Rich
497
- console = Console()
498
-
499
- # Valida os parâmetros
500
- validate_parameters()
501
-
502
- # Usa o perfil especificado ou o padrão 'quick'
503
- ports = PROFILES.get(profile.lower(), PROFILES["quick"])
504
-
505
- # Configura o arquivo de saída
506
- output_path = Path(output) if output else None
507
- if output_path:
508
- if output_path.suffix not in (".json", ".txt", ".md"):
509
- console.print("[yellow]Aviso:[/yellow] Extensão de arquivo não suportada. Usando .json")
510
- output_path = output_path.with_suffix(".json")
511
-
512
- # Configura a barra de progresso
513
- progress_columns = [
514
- SpinnerColumn(),
515
- TextColumn("[progress.description]{task.description}"),
516
- BarColumn(bar_width=None),
517
- TaskProgressColumn(),
518
- TimeElapsedColumn(),
519
- ]
520
-
521
- # Cabeçalho da varredura
522
- console.rule(f"🔍 [bold red]Varredura de Portas[/bold red]")
523
-
524
- # Tabela de informações
525
- info_table = Table.grid(padding=(0, 1))
526
- info_table.add_row("🌐 Alvo:", f"[bold]{target}")
527
- info_table.add_row("🔢 Portas:", f"[red]{ports}")
528
- info_table.add_row("🛡️ Nível de stealth:", f"[yellow]{stealth}")
529
- info_table.add_row("🔍 Detecção de serviços:", "[green]Ativada[/green]" if resolve_services else "[red]Desativada[/red]")
530
- info_table.add_row("⚠️ Verificação de vulnerabilidades:", "[green]Ativada[/green]" if check_vulns else "[red]Desativada[/red]")
531
-
532
- console.print(Panel(info_table, title="[bold]Configuração da Varredura[/bold]", border_style="red"))
533
- console.print()
534
-
535
- async def run_scan() -> List[PortScanResult]:
536
- """Executa a varredura de portas."""
537
- # Usa sempre TCP Connect que não requer privilégios de root
538
- console.print("[yellow]Aviso:[/yellow] Usando varredura TCP Connect (não requer privilégios de root).")
539
- scan_type = "tcp"
540
-
541
- # Cria o scanner Nmap
542
- scanner = PortScanner(
543
- target=target,
544
- ports=ports,
545
- scan_type=scan_type,
546
- stealth_level=stealth,
547
- resolve_services=resolve_services,
548
- check_vulns=check_vulns,
549
- )
550
-
551
- # Executa o scan e retorna os resultados
552
- return await scanner.scan()
553
-
554
- console.print("🚀 Iniciando varredura de portas...\n")
555
-
556
- # Executa a varredura com barra de progresso
557
- with Progress(*progress_columns) as progress:
558
- task = progress.add_task("[red]Escaneando portas...", total=100)
559
-
560
- try:
561
- # Executa o scan de forma assíncrona
562
- results = await run_scan()
563
- progress.update(task, completed=100)
564
-
565
- if not results:
566
- console.print("\n[yellow]⚠️ Nenhuma porta aberta detectada[/yellow]")
567
- return
568
-
569
- output_format = format.lower()
570
-
571
- if output_format == "json":
572
- output_text = json.dumps([r.to_dict() for r in results], indent=2, ensure_ascii=False)
573
- if not output_path:
574
- console.print_json(data=json.loads(output_text))
575
- elif output_format == "markdown":
576
- # Formata a saída em Markdown
577
- output_lines = [
578
- f"# Resultados da varredura de portas em {target}\n",
579
- "| Porta | Protocolo | Status | Serviço | Versão | Vulnerabilidades |",
580
- "|-------|-----------|--------|---------|---------|------------------|"
581
- ]
582
-
583
- for result in results:
584
- service = getattr(result, 'service', None)
585
- status = "🟢 ABERTA" if getattr(result, 'status', '') == "open" else "🔴 FECHADA"
586
- service_name = getattr(service, 'name', 'desconhecido') if service else "desconhecido"
587
- version = getattr(service, 'version', '') if service else ""
588
- vulns = ", ".join(service.vulns) if service and hasattr(service, 'vulns') and service.vulns else "-"
589
-
590
- output_lines.append(
591
- f"| {getattr(result, 'port', '')} | "
592
- f"{getattr(result, 'protocol', 'tcp').upper()} | "
593
- f"{status} | "
594
- f"{service_name} | "
595
- f"{version} | "
596
- f"{vulns if vulns and vulns != '[]' else '-'} |"
597
- )
598
-
599
- # Adiciona resumo
600
- open_ports = [r for r in results if getattr(r, 'status', '') == "open"]
601
- output_text = "\n".join(output_lines)
602
- output_text += f"\n\n✅ **Varredura concluída!** {len(open_ports)} porta(s) aberta(s) encontrada(s)."
603
-
604
- if not output_path:
605
- console.print(output_text)
606
- else: # Formato de texto simples
607
- # Cria tabela de resultados
608
- table = Table(
609
- title=f"🔍 Resultados da varredura em {target}",
610
- box=box.ROUNDED,
611
- header_style="bold magenta",
612
- expand=True
613
- )
614
-
615
- # Adiciona colunas
616
- table.add_column("Porta", style="red", justify="right")
617
- table.add_column("Protocolo", style="magenta")
618
- table.add_column("Status", style="green")
619
- table.add_column("Serviço", style="yellow")
620
- table.add_column("Versão", style="blue")
621
- table.add_column("Vulnerabilidades", style="red")
622
-
623
- # Adiciona linhas com os resultados
624
- for result in results:
625
- service = getattr(result, 'service', None)
626
- status = "[green]ABERTA[/green]" if getattr(result, 'status', '') == "open" else "[red]FECHADA[/red]"
627
- service_name = getattr(service, 'name', 'desconhecido') if service else "desconhecido"
628
- version = getattr(service, 'version', '') if service else ""
629
- vulns = ", ".join(service.vulns) if service and hasattr(service, 'vulns') and service.vulns else "-"
630
-
631
- table.add_row(
632
- str(getattr(result, 'port', '')),
633
- getattr(result, 'protocol', 'tcp').upper(),
634
- status,
635
- service_name,
636
- version,
637
- vulns if vulns and vulns != "[]" else "-"
638
- )
639
-
640
- # Exibe a tabela
641
- console.print(table)
642
-
643
- # Resumo
644
- open_ports = [r for r in results if getattr(r, 'status', '') == "open"]
645
- console.print(f"\n✅ [bold green]Varredura concluída![/bold green] {len(open_ports)} porta(s) aberta(s) encontrada(s).")
646
-
647
- # Prepara o texto para salvar em arquivo
648
- output_text = str(table)
649
-
650
- # Salva em arquivo se especificado
651
- if output_path:
652
- try:
653
- output_path.parent.mkdir(parents=True, exist_ok=True)
654
- with open(output_path, "w", encoding="utf-8") as f:
655
- f.write(output_text)
656
- console.print(f"\n💾 [bold]Resultados salvos em:[/bold] [red]{output_path.absolute()}[/red]")
657
- except Exception as e:
658
- console.print(f"\n❌ [bold red]Erro ao salvar arquivo:[/bold red] {str(e)}")
659
-
660
- except KeyboardInterrupt:
661
- console.print("\n❌ [bold red]Varredura cancelada pelo usuário[/bold red]")
662
- raise typer.Exit(1)
663
- except Exception as e:
664
- console.print(f"\n❌ [bold red]Erro durante a varredura:[/bold red] {str(e)}")
665
- if "Errno 8" in str(e) or "Name or service not known" in str(e):
666
- console.print("💡 [yellow]Dica:[/yellow] Não foi possível resolver o nome do host. Verifique a conexão com a internet ou o nome do domínio.")
667
- if "No module named 'nmap'" in str(e):
668
- console.print("💡 [yellow]Dica:[/yellow] O módulo 'python-nmap' não está instalado. Instale com: [red]pip install python-nmap[/red]")
669
- raise typer.Exit(1)
670
-
671
- async def run_port_scan(
672
- target: str,
673
- profile: str,
674
- stealth: int,
675
- concurrency: int,
676
- timeout: float,
677
- output: Optional[str],
678
- format: str,
679
- ):
680
- """Função assíncrona que executa a varredura de portas."""
681
- from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn, TaskProgressColumn, TimeElapsedColumn
682
- from rich.panel import Panel
683
- from rich.table import Table
684
- from pathlib import Path
685
- import json
686
-
687
- # Valida os parâmetros
688
- if stealth < 0 or stealth > 5:
689
- console.print("[red]Erro:[/red] O nível de stealth deve estar entre 0 e 5")
690
- raise typer.Exit(1)
691
-
692
- if profile not in PROFILES:
693
- console.print(f"[red]Erro:[/red] Perfil inválido. Use um dos seguintes: {', '.join(PROFILES.keys())}")
694
- raise typer.Exit(1)
695
-
696
- # Usa o perfil especificado ou o padrão 'quick'
697
- ports = PROFILES.get(profile.lower(), PROFILES["quick"])
698
-
699
- # Configura o arquivo de saída
700
- output_path = Path(output) if output else None
701
- if output_path:
702
- if output_path.suffix not in (".json", ".txt", ".md"):
703
- console.print("[yellow]Aviso:[/yellow] Extensão de arquivo não suportada. Usando .json")
704
- output_path = output_path.with_suffix(".json")
705
-
706
- # Configura a barra de progresso
707
- progress_columns = [
708
- SpinnerColumn(),
709
- TextColumn("[progress.description]{task.description}"),
710
- BarColumn(bar_width=None),
711
- TaskProgressColumn(),
712
- TimeElapsedColumn(),
713
- ]
714
-
715
- # Cabeçalho da varredura
716
- console.rule(f"🔍 [bold red]Varredura de Portas[/bold red]")
717
-
718
- # Tabela de informações
719
- info_table = Table.grid(padding=(0, 1))
720
- info_table.add_row("🌐 Alvo:", f"[bold]{target}")
721
- info_table.add_row("🔢 Portas:", f"[red]{ports}")
722
- info_table.add_row("🛡️ Nível de stealth:", f"[yellow]{stealth}")
723
- info_table.add_row("🔍 Detecção de serviços:", "[green]Ativada[/green]")
724
- info_table.add_row("⚠️ Verificação de vulnerabilidades:", "[green]Ativada[/green]")
725
-
726
- console.print(Panel(info_table, title="[bold]Configuração da Varredura[/bold]", border_style="red"))
727
- console.print()
728
-
729
- # Executa a varredura com barra de progresso
730
- with Progress(*progress_columns) as progress:
731
- task = progress.add_task("[red]Escaneando portas...", total=100)
732
-
733
- try:
734
- # Cria o scanner Nmap
735
- scanner = PortScanner(
736
- target=target,
737
- ports=ports,
738
- scan_type="syn" if stealth < 2 else "tcp",
739
- stealth_level=stealth,
740
- resolve_services=True,
741
- check_vulns=True,
742
- )
743
-
744
- # Executa o scan de forma assíncrona
745
- results = await scanner.scan()
746
- progress.update(task, completed=100)
747
-
748
- if not results:
749
- console.print("\n[yellow]⚠️ Nenhuma porta aberta detectada[/yellow]")
750
- return
751
-
752
- # Formata a saída
753
- output_format = format.lower()
754
-
755
- if output_format == "json":
756
- output_text = json.dumps([r.to_dict() for r in results], indent=2, ensure_ascii=False)
757
- if not output_path:
758
- console.print_json(data=json.loads(output_text))
759
- elif output_format == "markdown":
760
- output_lines = [
761
- f"# Resultados da varredura de portas em {target}\n",
762
- "| Porta | Protocolo | Status | Serviço | Versão |",
763
- "|-------|-----------|--------|---------|---------|"
764
- ]
765
-
766
- for result in results:
767
- service = getattr(result, 'service', None)
768
- status = "🟢 ABERTA" if getattr(result, 'status', '') == "open" else "🔴 FECHADA"
769
- service_name = getattr(service, 'name', 'desconhecido') if service else "desconhecido"
770
- version = getattr(service, 'version', '') if service else ""
771
-
772
- output_lines.append(
773
- f"| {getattr(result, 'port', '')} | "
774
- f"{getattr(result, 'protocol', 'tcp').upper()} | "
775
- f"{status} | "
776
- f"{service_name} | "
777
- f"{version} |"
778
- )
779
-
780
- # Adiciona resumo
781
- open_ports = [r for r in results if getattr(r, 'status', '') == "open"]
782
- output_text = "\n".join(output_lines)
783
- output_text += f"\n\n✅ **Varredura concluída!** {len(open_ports)} porta(s) aberta(s) encontrada(s)."
784
-
785
- if not output_path:
786
- console.print(output_text)
787
- else: # Formato de texto simples
788
- # Cria tabela de resultados
789
- table = Table(
790
- title=f"🔍 Resultados da varredura em {target}",
791
- box=box.ROUNDED,
792
- header_style="bold magenta",
793
- expand=True
794
- )
795
-
796
- # Adiciona colunas
797
- table.add_column("Porta", style="red", justify="right")
798
- table.add_column("Protocolo", style="magenta")
799
- table.add_column("Status", style="green")
800
- table.add_column("Serviço", style="yellow")
801
- table.add_column("Versão", style="blue")
802
-
803
- # Adiciona linhas com os resultados
804
- for result in results:
805
- service = getattr(result, 'service', None)
806
- status = "[green]ABERTA[/green]" if getattr(result, 'status', '') == "open" else "[red]FECHADA[/red]"
807
- service_name = getattr(service, 'name', 'desconhecido') if service else "desconhecido"
808
- version = getattr(service, 'version', '') if service else ""
809
-
810
- table.add_row(
811
- str(getattr(result, 'port', '')),
812
- getattr(result, 'protocol', 'tcp').upper(),
813
- status,
814
- service_name,
815
- version,
816
- )
817
-
818
- # Exibe a tabela
819
- console.print(table)
820
-
821
- # Resumo
822
- open_ports = [r for r in results if getattr(r, 'status', '') == "open"]
823
- console.print(f"\n✅ [bold green]Varredura concluída![/bold green] {len(open_ports)} porta(s) aberta(s) encontrada(s).")
824
-
825
- # Prepara o texto para salvar em arquivo
826
- output_text = str(table)
827
-
828
- # Salva em arquivo se especificado
829
- if output_path:
830
- try:
831
- output_path.parent.mkdir(parents=True, exist_ok=True)
832
- with open(output_path, "w", encoding="utf-8") as f:
833
- f.write(output_text)
834
- console.print(f"\n💾 [bold]Resultados salvos em:[/bold] [red]{output_path.absolute()}[/red]")
835
- except Exception as e:
836
- console.print(f"\n❌ [bold red]Erro ao salvar arquivo:[/bold red] {str(e)}")
837
-
838
- except KeyboardInterrupt:
839
- console.print("\n❌ [bold red]Varredura cancelada pelo usuário[/bold red]")
840
- raise typer.Exit(1)
841
- except Exception as e:
842
- console.print(f"\n❌ [bold red]Erro durante a varredura:[/bold red] {str(e)}")
843
- if "Errno 8" in str(e) or "Name or service not known" in str(e):
844
- console.print("💡 [yellow]Dica:[/yellow] Não foi possível resolver o nome do host. Verifique a conexão com a internet ou o nome do domínio.")
845
- if "No module named 'nmap'" in str(e):
846
- console.print("💡 [yellow]Dica:[/yellow] O módulo 'python-nmap' não está instalado. Instale com: [red]pip install python-nmap[/red]")
847
- raise typer.Exit(1)
848
-
849
507
  @app.command("ports")
850
508
  def port_scan(
851
509
  target: str = typer.Argument(..., help="Domínio ou IP para escanear"),
@@ -903,7 +561,7 @@ def port_scan(
903
561
  ),
904
562
  ):
905
563
  """
906
- 🔍 Varredura avançada de portas com detecção de serviços e vulnerabilidades usando Nmap.
564
+ 🔍 Varredura avançada de portas com detecção de serviços e vulnerabilidades.
907
565
 
908
566
  Exemplos:
909
567
  moriarty domain ports example.com
@@ -912,218 +570,84 @@ def port_scan(
912
570
  """
913
571
  import asyncio
914
572
  import json
915
- import os
916
- import sys
917
573
  from pathlib import Path
918
- from typing import List, Dict, Any, Optional
919
-
920
- from rich.progress import (
921
- Progress,
922
- SpinnerColumn,
923
- BarColumn,
924
- TextColumn,
925
- TimeElapsedColumn,
926
- TaskProgressColumn,
927
- )
928
- from rich.panel import Panel
929
- from rich.table import Table, box
930
- from rich.console import Console
931
-
932
- from moriarty.modules.port_scanner_nmap import PortScanner, PortScanResult, ServiceInfo
933
-
934
- # Mapeia os perfis para o formato do Nmap
935
- port_profiles = {
936
- "quick": "21-23,25,53,80,110,111,135,139,143,389,443,445,465,587,993,995,1433,1521,2049,3306,3389,5432,5900,6379,8080,8443,9000,10000,27017",
937
- "web": "80,443,8080,8443,8000,8888,10443,4443",
938
- "mail": "25,110,143,465,587,993,995",
939
- "db": "1433,1521,27017-27019,28017,3306,5000,5432,5984,6379,8081",
940
- "full": "1-1024",
941
- "all": "1-65535",
942
- }
943
-
944
- # Verifica se o Nmap está instalado
945
- def check_nmap_installed() -> bool:
946
- """Verifica se o Nmap está instalado no sistema."""
947
- try:
948
- import nmap
949
- return True
950
- except ImportError:
951
- return False
952
-
953
- # Valida os parâmetros de entrada
954
- def validate_parameters() -> None:
955
- """Valida os parâmetros de entrada."""
956
- if not check_nmap_installed():
957
- console.print("❌ [bold red]Erro:[/bold red] O módulo 'python-nmap' não está instalado.")
958
- console.print("💡 [yellow]Dica:[/yellow] Instale com: pip install python-nmap")
959
- raise typer.Exit(1)
960
-
961
- if stealth < 0 or stealth > 5:
962
- console.print("❌ [bold red]Erro:[/bold red] O nível de stealth deve estar entre 0 e 5.")
963
- raise typer.Exit(1)
964
-
965
- # Inicializa o console do Rich
966
- console = Console()
967
-
968
- # Valida os parâmetros
969
- validate_parameters()
970
-
971
- # Usa o perfil especificado ou o padrão 'quick'
972
- ports = port_profiles.get(profile.lower(), port_profiles["quick"])
973
-
974
- # Configura o arquivo de saída
975
- output_path = Path(output) if output else None
976
- if output_path:
977
- if output_path.suffix not in (".json", ".txt", ".md"):
574
+ from moriarty.modules.port_scanner import format_scan_results
575
+
576
+ # Validações
577
+ if profile not in PROFILES:
578
+ console.print(f"[red]Erro:[/red] Perfil inválido. Use um destes: {', '.join(PROFILES.keys())}")
579
+ raise typer.Exit(1)
580
+
581
+ if output:
582
+ output = Path(output)
583
+ if output.suffix not in (".json", ".txt", ".md"):
978
584
  console.print("[yellow]Aviso:[/yellow] Extensão de arquivo não suportada. Usando .json")
979
- output_path = output_path.with_suffix(".json")
980
-
981
- # Configura a barra de progresso
982
- progress_columns = [
983
- SpinnerColumn(),
984
- TextColumn("[progress.description]{task.description}"),
985
- BarColumn(bar_width=None),
986
- TaskProgressColumn(),
987
- TimeElapsedColumn(),
988
- ]
989
-
990
- # Cabeçalho da varredura
991
- console.rule(f"🔍 [bold red]Varredura de Portas[/bold red]")
992
-
993
- # Tabela de informações
994
- info_table = Table.grid(padding=(0, 1))
995
- info_table.add_row("🌐 Alvo:", f"[bold]{target}")
996
- info_table.add_row("🔢 Portas:", f"[red]{ports}")
997
- info_table.add_row("🛡️ Nível de stealth:", f"[yellow]{stealth}")
998
- info_table.add_row("🔍 Detecção de serviços:", "[green]Ativada[/green]" if resolve_services else "[red]Desativada[/red]")
999
- info_table.add_row("⚠️ Verificação de vulnerabilidades:", "[green]Ativada[/green]" if check_vulns else "[red]Desativada[/red]")
1000
-
1001
- console.print(Panel(info_table, title="[bold]Configuração da Varredura[/bold]", border_style="red"))
1002
- console.print()
1003
-
1004
- async def run_scan() -> List[PortScanResult]:
1005
- """Executa a varredura de portas."""
1006
- # Usa sempre TCP Connect que não requer privilégios de root
1007
- console.print("[yellow]Aviso:[/yellow] Usando varredura TCP Connect (não requer privilégios de root).")
1008
- scan_type = "tcp"
1009
-
1010
- # Cria o scanner Nmap
585
+ output = output.with_suffix(".json")
586
+
587
+ async def _run():
1011
588
  scanner = PortScanner(
1012
589
  target=target,
1013
- ports=ports,
1014
- scan_type=scan_type,
590
+ profile=profile,
591
+ concurrency=concurrency,
592
+ timeout=timeout,
1015
593
  stealth_level=stealth,
1016
594
  resolve_services=resolve_services,
1017
595
  check_vulns=check_vulns,
1018
596
  )
1019
-
1020
- # Executa o scan e retorna os resultados
1021
597
  return await scanner.scan()
1022
598
 
1023
- console.print("🚀 Iniciando varredura de portas...\n")
599
+ # Executa o scanner
600
+ console.print(f"[bold]🔍 Iniciando varredura em:[/bold] {target}")
601
+ console.print(f"📊 Perfil: {profile} (portas: {len(PROFILES[profile])})")
602
+ console.print(f"🕵️ Nível de stealth: {stealth}")
1024
603
 
1025
- # Função para salvar resultados em diferentes formatos
1026
- def save_results(results, output_path, format):
1027
- output_format = format.lower()
604
+ try:
605
+ results = asyncio.run(_run())
1028
606
 
1029
- try:
1030
- if output_format == "json":
1031
- output_text = json.dumps([r.to_dict() for r in results], indent=2, ensure_ascii=False)
1032
- with open(output_path, 'w', encoding='utf-8') as f:
607
+ if not results:
608
+ console.print("[yellow]⚠️ Nenhuma porta aberta detectada[/yellow]")
609
+ return
610
+
611
+ # Formata a saída
612
+ if format.lower() == "json":
613
+ output_text = json.dumps([r.to_dict() for r in results], indent=2)
614
+ if not output:
615
+ console.print_json(data=json.loads(output_text))
616
+ else:
617
+ output_text = format_scan_results(results, output_format=format)
618
+ if not output:
619
+ console.print(output_text)
620
+
621
+ # Salva em arquivo se especificado
622
+ if output:
623
+ output.parent.mkdir(parents=True, exist_ok=True)
624
+ with open(output, "w", encoding="utf-8") as f:
625
+ if output.suffix == ".json":
626
+ json.dump([r.to_dict() for r in results], f, indent=2, ensure_ascii=False)
627
+ else:
1033
628
  f.write(output_text)
1034
- elif output_format == "markdown":
1035
- with open(output_path, 'w', encoding='utf-8') as f:
1036
- f.write("# Resultados da Varredura de Portas\n\n")
1037
- f.write("| Porta | Estado | Serviço | Versão |\n")
1038
- f.write("|-------|--------|---------|---------|\n")
1039
- for result in results:
1040
- if result.state == 'open':
1041
- service = result.service or "desconhecido"
1042
- version = result.version or ""
1043
- f.write(f"| {result.port} | ABERTA | {service} | {version} |\n")
1044
- else: # text
1045
- with open(output_path, 'w', encoding='utf-8') as f:
1046
- for result in results:
1047
- if result.state == 'open':
1048
- service = result.service or "desconhecido"
1049
- version = f" ({result.version})" if result.version else ""
1050
- f.write(f"Porta {result.port}/tcp: ABERTA - {service}{version}\n")
1051
- except Exception as e:
1052
- console.print(f"❌ [bold red]Erro ao salvar o arquivo:[/bold red] {str(e)}")
1053
- raise typer.Exit(1)
629
+ console.print(f"\n[green]✅ Resultados salvos em:[/green] {output.absolute()}")
1054
630
 
1055
- # Executa o scan de forma assíncrona
1056
- with Progress(*progress_columns) as progress:
1057
- task = progress.add_task("[red]Escaneando portas...", total=100)
1058
-
1059
- try:
1060
- # Executa a função assíncrona
1061
- results = asyncio.run(run_scan())
1062
- progress.update(task, completed=100)
1063
-
1064
- # Exibe os resultados
1065
- if results:
1066
- # Cria uma tabela para exibir os resultados
1067
- table = Table(title="[bold]Resultados da Varredura[/bold]")
1068
- table.add_column("Porta", style="red", no_wrap=True)
1069
- table.add_column("Estado", style="green")
1070
- table.add_column("Serviço", style="yellow")
1071
- table.add_column("Versão", style="magenta")
1072
-
1073
- for result in results:
1074
- if result.state == 'open':
1075
- service = result.service or "desconhecido"
1076
- version = result.version or ""
1077
- table.add_row(
1078
- str(result.port),
1079
- "[green]ABERTA[/green]",
1080
- service,
1081
- version
1082
- )
1083
-
1084
- console.print()
1085
- console.print(table)
1086
-
1087
- # Salva os resultados em um arquivo, se solicitado
1088
- if output_path:
1089
- save_results(results, output_path, format)
1090
- console.print(f"\n✅ Resultados salvos em: [bold red]{output_path}[/bold red]")
1091
-
1092
- # Exibe saída JSON no console se não houver arquivo de saída e o formato for JSON
1093
- if format.lower() == "json" and not output_path:
1094
- output_text = json.dumps([r.to_dict() for r in results], indent=2, ensure_ascii=False)
1095
- console.print_json(data=json.loads(output_text))
1096
-
1097
- else:
1098
- console.print("\n❌ Nenhuma porta aberta encontrada ou ocorreu um erro durante a varredura.")
1099
-
1100
- except Exception as e:
1101
- progress.update(task, completed=100)
1102
- console.print(f"\n❌ [bold red]Erro durante a varredura:[/bold red] {str(e)}")
1103
- if "Errno 8" in str(e) or "Name or service not known" in str(e):
1104
- console.print("💡 [yellow]Dica:[/yellow] Não foi possível resolver o nome do host. Verifique a conexão com a internet ou o nome do domínio.")
1105
- raise typer.Exit(1)
631
+ except Exception as e:
632
+ console.print(f"[red]❌ Erro durante a varredura:[/red] {str(e)}")
633
+ if "object NoneType can't be used in 'await' expression" in str(e):
634
+ console.print("[yellow]Dica:[/yellow] Tente aumentar o timeout com --timeout 5.0")
635
+ raise typer.Exit(1)
636
+ console.print(f"[green]✓ Resultado salvo em[/green] {output}")
1106
637
 
1107
638
 
1108
639
  @app.command("crawl")
1109
640
  def crawl(
1110
641
  domain: str = typer.Argument(..., help="Alvo base (ex: https://example.com)"),
642
+ max_pages: int = typer.Option(100, "--max-pages", help="Máximo de páginas"),
1111
643
  max_depth: int = typer.Option(2, "--max-depth", help="Profundidade máxima"),
1112
- max_pages: int = typer.Option(100, "--max-pages", help="Número máximo de páginas a serem rastreadas"),
1113
644
  concurrency: int = typer.Option(10, "--concurrency", help="Workers paralelos"),
1114
645
  follow_subdomains: bool = typer.Option(False, "--follow-subdomains", help="Seguir subdomínios"),
1115
646
  output: Optional[str] = typer.Option(None, "--output", "-o", help="Arquivo JSON de saída"),
1116
647
  ):
1117
- """Crawler leve para mapear formulários e rotas.
1118
-
1119
- Exemplos:
1120
- moriarty domain crawl https://example.com
1121
- moriarty domain crawl https://example.com --max-depth 3 --max-pages 50
1122
- """
648
+ """Crawler leve para mapear formulários e rotas."""
1123
649
  import asyncio
1124
650
  import json
1125
- from typing import Dict, Any
1126
- from pathlib import Path
1127
651
 
1128
652
  async def _run():
1129
653
  crawler = WebCrawler(
@@ -1138,46 +662,15 @@ def crawl(
1138
662
  finally:
1139
663
  await crawler.close()
1140
664
 
1141
- try:
1142
- console.print(f"🚀 Iniciando crawler em [red]{domain}[/red]...")
1143
- pages = asyncio.run(_run())
1144
-
1145
- # Cria um dicionário seguro para serialização
1146
- safe_pages = {}
1147
- for url, page in pages.items():
1148
- safe_pages[url] = {
1149
- 'url': url,
1150
- 'title': getattr(page, 'title', ''),
1151
- 'forms': [form.__dict__ for form in getattr(page, 'forms', [])],
1152
- 'links': getattr(page, 'links', []),
1153
- 'status_code': getattr(page, 'status_code', 0)
1154
- }
1155
-
1156
- # Exibe resumo
1157
- summary = {
1158
- "total_pages": len(pages),
1159
- "forms": sum(len(page.get('forms', [])) for page in safe_pages.values()),
1160
- "links_unicos": len({link for page in safe_pages.values() for link in page.get('links', [])})
1161
- }
1162
-
1163
- console.print("\n📊 [bold]Resumo do Crawling:[/bold]")
1164
- console.print(f"• Páginas processadas: {summary['total_pages']}")
1165
- console.print(f"• Formulários encontrados: {summary['forms']}")
1166
- console.print(f"• Links únicos: {summary['links_unicos']}")
665
+ pages = asyncio.run(_run())
666
+ summary = {
667
+ "total_pages": len(pages),
668
+ "forms": sum(len(page.forms) for page in pages.values()),
669
+ }
670
+ console.print_json(data=summary)
1167
671
 
1168
- # Salva em arquivo se especificado
1169
- if output:
1170
- output_path = Path(output)
1171
- output_path.parent.mkdir(parents=True, exist_ok=True)
1172
- with open(output_path, "w", encoding="utf-8") as f:
1173
- json.dump({
1174
- "summary": summary,
1175
- "pages": safe_pages
1176
- }, f, indent=2, ensure_ascii=False)
1177
- console.print(f"\n💾 [bold]Resultados salvos em:[/bold] [red]{output_path.absolute()}[/red]")
1178
-
1179
- except Exception as e:
1180
- console.print(f"\n❌ [bold red]Erro durante o crawling:[/bold red] {str(e)}")
1181
- if "Failed to establish a new connection" in str(e):
1182
- console.print("💡 [yellow]Dica:[/yellow] Verifique sua conexão com a internet ou a URL fornecida.")
1183
- raise typer.Exit(1)
672
+ if output:
673
+ payload = {url: page.__dict__ for url, page in pages.items()}
674
+ with open(output, "w", encoding="utf-8") as handle:
675
+ json.dump(payload, handle, indent=2, ensure_ascii=False)
676
+ console.print(f"[green]✓ Resultado salvo em[/green] {output}")