moriarty-project 0.1.24__py3-none-any.whl → 0.1.26__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.
@@ -0,0 +1,290 @@
1
+ """Port scanning avançado com detecção de serviços usando Nmap."""
2
+ from __future__ import annotations
3
+
4
+ import asyncio
5
+ import json
6
+ import re
7
+ import nmap
8
+ from dataclasses import dataclass, field
9
+ from datetime import datetime
10
+ from typing import Dict, List, Optional, Any, Set, Union
11
+ from pathlib import Path
12
+ import structlog
13
+ from rich.console import Console
14
+ from rich.table import Table, box
15
+
16
+ logger = structlog.get_logger(__name__)
17
+ console = Console()
18
+
19
+ # Perfis de varredura
20
+ PROFILES = {
21
+ "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",
22
+ "web": "80,443,8080,8443,8000,8888,10443,4443",
23
+ "db": "1433,1521,27017-27019,28017,3306,5000,5432,5984,6379,8081",
24
+ "full": "1-1024",
25
+ "all": "1-65535",
26
+ }
27
+
28
+ @dataclass
29
+ class ServiceInfo:
30
+ """Informações detalhadas sobre um serviço."""
31
+ name: str = "unknown"
32
+ version: Optional[str] = None
33
+ ssl: bool = False
34
+ ssl_info: Optional[Dict[str, Any]] = None
35
+ banner: Optional[str] = None
36
+ vulns: List[str] = field(default_factory=list)
37
+ cpe: Optional[str] = None
38
+ extra: Dict[str, Any] = field(default_factory=dict)
39
+ confidence: float = 0.0
40
+ last_checked: Optional[datetime] = field(default_factory=datetime.utcnow)
41
+
42
+ def to_dict(self) -> Dict[str, Any]:
43
+ """Converte o objeto para dicionário."""
44
+ return {
45
+ "name": self.name,
46
+ "version": self.version,
47
+ "ssl": self.ssl,
48
+ "ssl_info": self.ssl_info,
49
+ "banner": self.banner,
50
+ "vulns": self.vulns,
51
+ "cpe": self.cpe,
52
+ "extra": self.extra,
53
+ "confidence": self.confidence,
54
+ "last_checked": self.last_checked.isoformat() if self.last_checked else None,
55
+ }
56
+
57
+ @dataclass
58
+ class PortScanResult:
59
+ """Resultado da varredura de uma porta."""
60
+ port: int
61
+ protocol: str = "tcp"
62
+ status: str = "closed"
63
+ target: Optional[str] = None
64
+ service: Optional[ServiceInfo] = None
65
+ banner: Optional[str] = None
66
+ timestamp: str = field(default_factory=lambda: datetime.utcnow().isoformat())
67
+
68
+ def to_dict(self) -> Dict[str, Any]:
69
+ """Converte o resultado para dicionário."""
70
+ return {
71
+ "port": self.port,
72
+ "protocol": self.protocol,
73
+ "status": self.status,
74
+ "target": self.target,
75
+ "service": self.service.to_dict() if self.service else None,
76
+ "banner": self.banner,
77
+ "timestamp": self.timestamp,
78
+ }
79
+
80
+ def to_json(self) -> str:
81
+ """Retorna uma representação JSON do resultado."""
82
+ return json.dumps(self.to_dict(), indent=2)
83
+
84
+ class PortScanner:
85
+ """Execução de port scanning com detecção avançada de serviços usando Nmap."""
86
+
87
+ def __init__(
88
+ self,
89
+ target: str,
90
+ ports: Union[str, List[int], None] = None,
91
+ scan_type: str = "syn",
92
+ stealth_level: int = 0,
93
+ resolve_services: bool = True,
94
+ check_vulns: bool = True,
95
+ ):
96
+ self.target = target
97
+ self.ports = self._parse_ports(ports) if ports else PROFILES["all"]
98
+ self.scan_type = scan_type if scan_type in ["syn", "tcp", "udp", "sS", "sT", "sU"] else "sS"
99
+ self.stealth_level = max(0, min(stealth_level, 5))
100
+ self.resolve_services = resolve_services
101
+ self.check_vulns = check_vulns
102
+ self.nm = nmap.PortScanner()
103
+
104
+ # Configurações baseadas no nível de stealth
105
+ self.scan_arguments = self._get_scan_arguments()
106
+
107
+ def _parse_ports(self, ports: Union[str, List[int]]) -> str:
108
+ """Converte diferentes formatos de portas para o formato do Nmap."""
109
+ if isinstance(ports, list):
110
+ return ",".join(str(p) for p in ports)
111
+ elif ports in PROFILES:
112
+ return PROFILES[ports]
113
+ return ports
114
+
115
+ def _get_scan_arguments(self) -> str:
116
+ """Gera os argumentos do Nmap baseados nas configurações."""
117
+ # Argumentos base - removendo -O (OS detection) e usando -T4 para velocidade
118
+ args = "-T4 -sT" # Usando TCP Connect por padrão sem necessidade de root
119
+
120
+ # Adiciona detecção de versão se necessário
121
+ if self.resolve_services:
122
+ args += " -sV"
123
+
124
+ # Adiciona scripts padrão se não for muito furtivo
125
+ if self.stealth_level < 3:
126
+ args += " -sC"
127
+
128
+ # Adiciona argumentos de furtividade baseado no nível
129
+ if self.stealth_level > 0:
130
+ args += f" --max-rtt-timeout {1000 - (self.stealth_level * 100)}ms"
131
+ args += f" --scan-delay {self.stealth_level * 2}s"
132
+
133
+ return args.strip()
134
+
135
+ @staticmethod
136
+ def render_pipe_summary(results: List[PortScanResult]) -> str:
137
+ if not results:
138
+ return "Nenhuma porta aberta encontrada."
139
+ headers = ["PORTA", "STATUS", "SERVICO", "PROTOCOLO", "VERSAO", "SSL"]
140
+ rows = []
141
+ for r in sorted(results, key=lambda x: (x.protocol, x.port)):
142
+ service_name = r.service.name if r.service else "unknown"
143
+ version = (r.service.version or "").strip() if r.service else ""
144
+ ssl = "✅" if (r.service and r.service.ssl) else ""
145
+ rows.append([
146
+ str(r.port), r.status, service_name or "unknown",
147
+ r.protocol.upper(), version, ssl
148
+ ])
149
+
150
+ col_widths = [len(h) for h in headers]
151
+ for row in rows:
152
+ for i, cell in enumerate(row):
153
+ col_widths[i] = max(col_widths[i], len(cell))
154
+
155
+ def fmt_line(cells):
156
+ return " | ".join(cell.ljust(col_widths[i]) for i, cell in enumerate(cells))
157
+
158
+ out = [fmt_line(headers), " | ".join("-" * w for w in col_widths)]
159
+ out.extend(fmt_line(row) for row in rows)
160
+ return "\n".join(out)
161
+
162
+ async def scan(self) -> List[PortScanResult]:
163
+ """Executa a varredura de portas usando Nmap."""
164
+ console.print(f"[bold]🔍 Iniciando varredura Nmap em {self.target}[/bold]")
165
+ console.print(f"📊 Portas: [bold]{self.ports}[/bold]")
166
+ console.print(f"⚙️ Argumentos: [bold]{self.scan_arguments}[/bold]")
167
+
168
+ if self.stealth_level > 0:
169
+ console.print(f"🔒 Modo furtivo: nível {self.stealth_level}")
170
+
171
+ try:
172
+ # Executa o scan Nmap
173
+ self.nm.scan(
174
+ hosts=self.target,
175
+ ports=self.ports,
176
+ arguments=self.scan_arguments,
177
+ sudo=False # Necessário para SYN scan
178
+ )
179
+
180
+ # Processa os resultados
181
+ results = []
182
+ for host in self.nm.all_hosts():
183
+ for proto in self.nm[host].all_protocols():
184
+ ports = self.nm[host][proto].keys()
185
+
186
+ for port in ports:
187
+ port_info = self.nm[host][proto][port]
188
+
189
+ # Cria o objeto ServiceInfo
190
+ service_info = ServiceInfo(
191
+ name=port_info.get('name', 'unknown'),
192
+ version=port_info.get('version', ''),
193
+ banner=port_info.get('product', ''),
194
+ ssl=port_info.get('tunnel') == 'ssl' or 'ssl' in port_info.get('name', '').lower()
195
+ )
196
+
197
+ # Adiciona CPE se disponível
198
+ if 'cpe' in port_info:
199
+ service_info.cpe = port_info['cpe']
200
+
201
+ state = port_info.get('state', 'closed')
202
+ if state != 'open':
203
+ continue
204
+
205
+ # Cria o resultado da porta
206
+ result = PortScanResult(
207
+ port=port,
208
+ protocol=proto,
209
+ status=state,
210
+ target=host,
211
+ service=service_info,
212
+ banner=port_info.get('product', '') + ' ' + port_info.get('version', '')
213
+ )
214
+
215
+ results.append(result)
216
+
217
+ # Ordena os resultados por número de porta
218
+ results.sort(key=lambda x: x.port)
219
+
220
+ # Exibe resumo
221
+ open_ports = len(results)
222
+ console.print(f"\n✅ [bold green]Varredura concluída![/bold green] {open_ports} portas abertas encontradas.")
223
+
224
+ # Mostra a visão tipo "PORTA | STATUS | SERVICO | ..."
225
+ console.print("\n[bold]Resumo das portas abertas[/bold]")
226
+ console.print(self.render_pipe_summary(results))
227
+
228
+ return results
229
+
230
+ except Exception as e:
231
+ console.print(f"[bold red]❌ Erro ao executar Nmap: {str(e)}[/bold red]")
232
+ raise
233
+
234
+ def format_scan_results(results: List[PortScanResult], output_format: str = "text", total_ports: Optional[int] = None) -> str:
235
+ """Formata os resultados da varredura no formato solicitado.
236
+
237
+ Args:
238
+ results: Lista de resultados da varredura
239
+ output_format: Formato de saída ('text' ou 'json')
240
+ total_ports: Número total de portas verificadas (opcional)
241
+ """
242
+ if output_format == "json":
243
+ return json.dumps([r.to_dict() for r in results], indent=2)
244
+
245
+ if output_format == "pipe":
246
+ base = PortScanner.render_pipe_summary(results)
247
+ suffix = f"\n\n🔍 Total de portas abertas: {len(results)}"
248
+ if total_ports:
249
+ suffix = f"\n\n🔍 Resumo: {len(results)} portas abertas de {total_ports} verificadas"
250
+ return base + suffix
251
+
252
+
253
+ # Formato de texto
254
+ output = []
255
+
256
+ if not results:
257
+ return "Nenhuma porta aberta encontrada."
258
+
259
+ # Cria tabela
260
+ table = Table(show_header=True, header_style="bold magenta", box=box.ROUNDED)
261
+ table.add_column("Porta", style="cyan", width=10)
262
+ table.add_column("Protocolo", style="blue")
263
+ table.add_column("Serviço", style="green")
264
+ table.add_column("Versão", style="yellow")
265
+ table.add_column("SSL/TLS", style="magenta")
266
+
267
+ for result in results:
268
+ service = result.service.name if result.service and hasattr(result.service, 'name') else "desconhecido"
269
+ version = result.service.version if result.service and hasattr(result.service, 'version') else ""
270
+ ssl = "✅" if result.service and result.service.ssl else ""
271
+
272
+ table.add_row(
273
+ f"{result.port}",
274
+ result.protocol.upper(),
275
+ service,
276
+ version,
277
+ ssl
278
+ )
279
+
280
+ output.append(str(table))
281
+
282
+ # Adiciona resumo
283
+ if total_ports:
284
+ output.append(f"\n🔍 [bold]Resumo:[/bold] {len(results)} portas abertas de {total_ports} verificadas")
285
+ else:
286
+ output.append(f"\n🔍 [bold]Total de portas abertas:[/bold] {len(results)}")
287
+
288
+ return "\n".join(output)
289
+
290
+ __all__ = ["PortScanner", "PortScanResult", "format_scan_results"]