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.
- moriarty/__init__.py +1 -1
- moriarty/cli/app.py +2 -2
- moriarty/cli/domain_cmd.py +127 -634
- moriarty/modules/domain_scanner.py +21 -16
- moriarty/modules/port_scanner_nmap.py +369 -102
- {moriarty_project-0.1.26.dist-info → moriarty_project-0.1.27.dist-info}/METADATA +5 -5
- {moriarty_project-0.1.26.dist-info → moriarty_project-0.1.27.dist-info}/RECORD +9 -11
- moriarty/cli/wifippler.py +0 -124
- moriarty/modules/port_scanner.py +0 -1050
- {moriarty_project-0.1.26.dist-info → moriarty_project-0.1.27.dist-info}/WHEEL +0 -0
- {moriarty_project-0.1.26.dist-info → moriarty_project-0.1.27.dist-info}/entry_points.txt +0 -0
@@ -4,7 +4,7 @@ from __future__ import annotations
|
|
4
4
|
import asyncio
|
5
5
|
import json
|
6
6
|
import re
|
7
|
-
import
|
7
|
+
import nmap3
|
8
8
|
from dataclasses import dataclass, field
|
9
9
|
from datetime import datetime
|
10
10
|
from typing import Dict, List, Optional, Any, Set, Union
|
@@ -12,6 +12,8 @@ from pathlib import Path
|
|
12
12
|
import structlog
|
13
13
|
from rich.console import Console
|
14
14
|
from rich.table import Table, box
|
15
|
+
from rich.live import Live
|
16
|
+
from rich.spinner import Spinner
|
15
17
|
|
16
18
|
logger = structlog.get_logger(__name__)
|
17
19
|
console = Console()
|
@@ -88,171 +90,434 @@ class PortScanner:
|
|
88
90
|
self,
|
89
91
|
target: str,
|
90
92
|
ports: Union[str, List[int], None] = None,
|
91
|
-
scan_type: str = "
|
93
|
+
scan_type: str = "tcp", # CORREÇÃO: Padrão mudado para TCP (não requer root)
|
92
94
|
stealth_level: int = 0,
|
93
95
|
resolve_services: bool = True,
|
94
96
|
check_vulns: bool = True,
|
97
|
+
debug: bool = False, # NOVO: Opção de debug
|
95
98
|
):
|
96
99
|
self.target = target
|
100
|
+
# CORREÇÃO: Armazena o range real, não o apelido
|
97
101
|
self.ports = self._parse_ports(ports) if ports else PROFILES["all"]
|
98
|
-
|
102
|
+
# CORREÇÃO: Default para TCP se não especificado
|
103
|
+
self.scan_type = scan_type if scan_type in ["syn", "tcp", "udp", "sS", "sT", "sU"] else "sT"
|
99
104
|
self.stealth_level = max(0, min(stealth_level, 5))
|
100
105
|
self.resolve_services = resolve_services
|
101
106
|
self.check_vulns = check_vulns
|
102
|
-
self.
|
107
|
+
self.debug = debug # NOVO
|
108
|
+
self.stealth_level = max(0, min(stealth_level, 5))
|
109
|
+
self.resolve_services = resolve_services
|
110
|
+
self.check_vulns = check_vulns
|
111
|
+
|
112
|
+
self.nm = nmap3.Nmap()
|
113
|
+
self.scan_tech = nmap3.NmapScanTechniques()
|
103
114
|
|
104
|
-
#
|
115
|
+
# CORREÇÃO: Remove -sS dos args base (será adicionado pela técnica)
|
105
116
|
self.scan_arguments = self._get_scan_arguments()
|
106
117
|
|
107
118
|
def _parse_ports(self, ports: Union[str, List[int]]) -> str:
|
108
119
|
"""Converte diferentes formatos de portas para o formato do Nmap."""
|
109
120
|
if isinstance(ports, list):
|
110
121
|
return ",".join(str(p) for p in ports)
|
111
|
-
|
112
|
-
|
113
|
-
|
122
|
+
if isinstance(ports, str):
|
123
|
+
# CORREÇÃO: Sempre retorna o range real, não o apelido
|
124
|
+
if ports in PROFILES:
|
125
|
+
return PROFILES[ports]
|
126
|
+
return ports
|
127
|
+
return PROFILES["all"]
|
114
128
|
|
115
129
|
def _get_scan_arguments(self) -> str:
|
116
130
|
"""Gera os argumentos do Nmap baseados nas configurações."""
|
117
|
-
#
|
118
|
-
args = "-T4
|
119
|
-
|
120
|
-
# Adiciona detecção de versão se necessário
|
121
|
-
if self.resolve_services:
|
122
|
-
args += " -sV"
|
131
|
+
# CORREÇÃO: Remove -sV e -sC daqui pois já são adicionados pela técnica de scan
|
132
|
+
args = "-Pn -T4 --open"
|
123
133
|
|
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
134
|
if self.stealth_level > 0:
|
130
135
|
args += f" --max-rtt-timeout {1000 - (self.stealth_level * 100)}ms"
|
131
136
|
args += f" --scan-delay {self.stealth_level * 2}s"
|
132
|
-
|
137
|
+
|
133
138
|
return args.strip()
|
134
139
|
|
135
140
|
@staticmethod
|
136
141
|
def render_pipe_summary(results: List[PortScanResult]) -> str:
|
142
|
+
"""Renderiza tabela em formato pipe (ASCII) com larguras automáticas."""
|
137
143
|
if not results:
|
138
144
|
return "Nenhuma porta aberta encontrada."
|
139
|
-
|
145
|
+
|
146
|
+
# Prepara dados
|
147
|
+
headers = [" PORTA", "STATUS", "SERVICO", "PROTOCOLO", "VERSAO", "SSL"]
|
140
148
|
rows = []
|
149
|
+
|
141
150
|
for r in sorted(results, key=lambda x: (x.protocol, x.port)):
|
142
151
|
service_name = r.service.name if r.service else "unknown"
|
143
152
|
version = (r.service.version or "").strip() if r.service else ""
|
144
153
|
ssl = "✅" if (r.service and r.service.ssl) else ""
|
154
|
+
|
145
155
|
rows.append([
|
146
|
-
str(r.port),
|
147
|
-
r.
|
156
|
+
str(r.port),
|
157
|
+
r.status,
|
158
|
+
service_name or "unknown",
|
159
|
+
r.protocol.upper(),
|
160
|
+
version or "-",
|
161
|
+
ssl
|
148
162
|
])
|
149
|
-
|
163
|
+
|
164
|
+
# Calcula larguras automáticas
|
150
165
|
col_widths = [len(h) for h in headers]
|
151
166
|
for row in rows:
|
152
167
|
for i, cell in enumerate(row):
|
153
|
-
col_widths[i] = max(col_widths[i], len(cell))
|
154
|
-
|
168
|
+
col_widths[i] = max(col_widths[i], len(str(cell)))
|
169
|
+
|
170
|
+
# Formata linhas
|
155
171
|
def fmt_line(cells):
|
156
|
-
return " | ".join(cell.ljust(col_widths[i]) for i, cell in enumerate(cells))
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
172
|
+
return " | ".join(str(cell).ljust(col_widths[i]) for i, cell in enumerate(cells))
|
173
|
+
|
174
|
+
separator = "-+-".join("-" * w for w in col_widths)
|
175
|
+
|
176
|
+
output = [
|
177
|
+
fmt_line(headers),
|
178
|
+
separator
|
179
|
+
]
|
180
|
+
output.extend(fmt_line(row) for row in rows)
|
181
|
+
|
182
|
+
return "\n".join(output)
|
161
183
|
|
162
184
|
async def scan(self) -> List[PortScanResult]:
|
163
|
-
"""Executa a varredura de portas usando Nmap."""
|
164
|
-
|
165
|
-
|
166
|
-
console.print(f"
|
167
|
-
|
185
|
+
"""Executa a varredura de portas usando Nmap (python3-nmap)."""
|
186
|
+
# CORREÇÃO: Exibe o range real
|
187
|
+
logger.info(f"Iniciando varredura Nmap", target=self.target, ports=self.ports, arguments=self.scan_arguments)
|
188
|
+
console.print(f"[bold] 🔍 Iniciando varredura Nmap em {self.target}[/bold]")
|
189
|
+
console.print(f" 📊 Portas: [bold]{self.ports}[/bold]")
|
190
|
+
console.print(f" ⚙️ Argumentos: [bold]{self.scan_arguments}[/bold]")
|
191
|
+
|
168
192
|
if self.stealth_level > 0:
|
169
|
-
console.print(f"🔒 Modo furtivo: nível {self.stealth_level}")
|
193
|
+
console.print(f" 🔒 Modo furtivo: nível {self.stealth_level}")
|
194
|
+
|
195
|
+
# Cria spinner animado
|
196
|
+
spinner = Spinner("dots", text="[cyan]Executando scan Nmap...[/cyan]", style="cyan")
|
170
197
|
|
171
198
|
try:
|
172
|
-
#
|
173
|
-
self.
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
)
|
199
|
+
# CORREÇÃO: self.ports já é o range correto
|
200
|
+
ports_str = self.ports
|
201
|
+
args = f"{self.scan_arguments} -p {ports_str}"
|
202
|
+
logger.debug("Argumentos completos do Nmap", nmap_args=args)
|
203
|
+
|
204
|
+
st = self.scan_type
|
205
|
+
logger.info(f"Técnica de varredura selecionada: {st}")
|
206
|
+
|
207
|
+
# Inicia o spinner em Live context
|
208
|
+
with Live(spinner, console=console, refresh_per_second=10):
|
209
|
+
try:
|
210
|
+
# Detecta se precisa de root e ajusta automaticamente
|
211
|
+
needs_root = st in ("syn", "sS")
|
212
|
+
if needs_root:
|
213
|
+
spinner.update(text="[yellow]⚠️ Alternando para TCP Connect (-sT)...[/yellow]")
|
214
|
+
await asyncio.sleep(0.5) # Pequena pausa para o usuário ver a mensagem
|
215
|
+
st = "sT"
|
216
|
+
self.scan_type = "sT"
|
217
|
+
|
218
|
+
if self.debug:
|
219
|
+
console.print(f"[dim]🐛 Executando: nmap -{st} {args} {self.target}[/dim]")
|
220
|
+
|
221
|
+
spinner.update(text=f"[cyan]Scaneando portas {self.ports}...[/cyan]")
|
222
|
+
|
223
|
+
# Executa o scan baseado na técnica
|
224
|
+
if st in ("syn", "sS"):
|
225
|
+
logger.debug("Executando varredura SYN (sS)")
|
226
|
+
scan_results = await asyncio.to_thread(
|
227
|
+
self.scan_tech.nmap_syn_scan, self.target, args=args
|
228
|
+
)
|
229
|
+
elif st in ("tcp", "sT"):
|
230
|
+
logger.debug("Executando varredura TCP (sT)")
|
231
|
+
scan_results = await asyncio.to_thread(
|
232
|
+
self.scan_tech.nmap_tcp_scan, self.target, args=args
|
233
|
+
)
|
234
|
+
elif st in ("udp", "sU"):
|
235
|
+
if hasattr(self.scan_tech, "nmap_udp_scan"):
|
236
|
+
logger.debug("Executando varredura UDP (sU)")
|
237
|
+
scan_results = await asyncio.to_thread(
|
238
|
+
self.scan_tech.nmap_udp_scan, self.target, args=args
|
239
|
+
)
|
240
|
+
else:
|
241
|
+
logger.debug("Executando varredura UDP via fallback")
|
242
|
+
scan_results = await asyncio.to_thread(
|
243
|
+
self.nm.nmap_version_detection, self.target, args=f"-sU {args}"
|
244
|
+
)
|
245
|
+
else:
|
246
|
+
logger.debug(f"Técnica não reconhecida '{st}', usando fallback")
|
247
|
+
scan_results = await asyncio.to_thread(
|
248
|
+
self.nm.nmap_version_detection, self.target, args=args
|
249
|
+
)
|
250
|
+
|
251
|
+
spinner.update(text="[cyan]Processando resultados...[/cyan]")
|
252
|
+
|
253
|
+
if self.debug:
|
254
|
+
console.print(f"[dim]🐛 Tipo de resultado: {type(scan_results)}[/dim]")
|
255
|
+
|
256
|
+
# Detecta erro de permissão e tenta novamente com TCP
|
257
|
+
if isinstance(scan_results, dict) and scan_results.get("error") and "root" in str(scan_results.get("msg", "")).lower():
|
258
|
+
spinner.update(text="[yellow]⚠️ Erro de permissão. Tentando TCP...[/yellow]")
|
259
|
+
await asyncio.sleep(0.5)
|
260
|
+
st = "sT"
|
261
|
+
self.scan_type = "sT"
|
262
|
+
scan_results = await asyncio.to_thread(
|
263
|
+
self.scan_tech.nmap_tcp_scan, self.target, args=args
|
264
|
+
)
|
265
|
+
if self.debug:
|
266
|
+
console.print(f"[dim]🐛 Tipo de resultado (2ª tentativa): {type(scan_results)}[/dim]")
|
267
|
+
|
268
|
+
# Se ports está vazio, tenta scan com subprocess direto
|
269
|
+
if isinstance(scan_results, dict):
|
270
|
+
has_ports = False
|
271
|
+
for key, val in scan_results.items():
|
272
|
+
if isinstance(val, dict) and isinstance(val.get("ports"), list) and val.get("ports"):
|
273
|
+
has_ports = True
|
274
|
+
break
|
275
|
+
|
276
|
+
if not has_ports and self.debug:
|
277
|
+
spinner.update(text="[yellow]⚠️ Tentando método alternativo...[/yellow]")
|
278
|
+
await asyncio.sleep(0.3)
|
279
|
+
try:
|
280
|
+
clean_args = args.replace("-sV", "").replace("-sC", "").strip()
|
281
|
+
scan_results = await asyncio.to_thread(
|
282
|
+
self.scan_tech.nmap_tcp_scan,
|
283
|
+
self.target,
|
284
|
+
args=f"{clean_args} -p {ports_str}"
|
285
|
+
)
|
286
|
+
console.print(f"[dim]🐛 Tipo de resultado (scan limpo): {type(scan_results)}[/dim]")
|
287
|
+
except Exception as e:
|
288
|
+
console.print(f"[red]❌ Erro no scan alternativo: {e}[/red]")
|
289
|
+
|
290
|
+
if self.debug:
|
291
|
+
console.print(f"[dim]🐛 Resultado RAW:[/dim]")
|
292
|
+
try:
|
293
|
+
import pprint
|
294
|
+
console.print(f"[dim]{pprint.pformat(scan_results, width=120)}[/dim]")
|
295
|
+
except:
|
296
|
+
console.print(f"[dim]{str(scan_results)[:2000]}[/dim]")
|
297
|
+
|
298
|
+
logger.debug("Varredura Nmap concluída", scan_results=scan_results)
|
299
|
+
|
300
|
+
except Exception as e:
|
301
|
+
logger.error("Erro durante a execução do Nmap", error=str(e), exc_info=True)
|
302
|
+
raise
|
303
|
+
|
304
|
+
# CORREÇÃO: Parser melhorado para lidar com diferentes formatos
|
305
|
+
logger.debug("Iniciando processamento dos resultados do Nmap")
|
179
306
|
|
180
|
-
# Processa os resultados
|
181
307
|
results = []
|
182
|
-
|
183
|
-
|
184
|
-
|
308
|
+
|
309
|
+
if self.debug:
|
310
|
+
if isinstance(scan_results, dict):
|
311
|
+
console.print(f"[dim]🐛 Chaves do resultado: {list(scan_results.keys())}[/dim]")
|
312
|
+
|
313
|
+
# CORREÇÃO: Trata múltiplos formatos de retorno
|
314
|
+
if isinstance(scan_results, dict):
|
315
|
+
for host_key, host_data in scan_results.items():
|
316
|
+
# Ignora chaves de metadados
|
317
|
+
if host_key in ("stats", "runtime", "task_results"):
|
318
|
+
if self.debug:
|
319
|
+
console.print(f"[dim]🐛 Ignorando chave de metadados: {host_key}[/dim]")
|
320
|
+
continue
|
185
321
|
|
186
|
-
|
187
|
-
|
322
|
+
if self.debug:
|
323
|
+
console.print(f"[dim]🐛 Processando host: {host_key}[/dim]")
|
324
|
+
console.print(f"[dim]🐛 Tipo de host_data: {type(host_data)}[/dim]")
|
325
|
+
|
326
|
+
# Formato: { "host": { "ports": [...] } }
|
327
|
+
if isinstance(host_data, dict):
|
328
|
+
if self.debug:
|
329
|
+
console.print(f"[dim]🐛 Chaves de host_data: {list(host_data.keys())}[/dim]")
|
188
330
|
|
189
|
-
|
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
|
-
)
|
331
|
+
ports_list = host_data.get("ports", [])
|
196
332
|
|
197
|
-
|
198
|
-
|
199
|
-
service_info.cpe = port_info['cpe']
|
333
|
+
if self.debug:
|
334
|
+
console.print(f"[dim]🐛 Portas no formato 'ports': {len(ports_list)}[/dim]")
|
200
335
|
|
201
|
-
|
202
|
-
if
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
336
|
+
# CORREÇÃO: Também processa ports no formato alternativo
|
337
|
+
if not ports_list and "tcp" in host_data:
|
338
|
+
if self.debug:
|
339
|
+
console.print(f"[dim]🐛 Tentando formato alternativo 'tcp'...[/dim]")
|
340
|
+
tcp_ports = host_data.get("tcp", {})
|
341
|
+
if self.debug:
|
342
|
+
console.print(f"[dim]🐛 Portas TCP encontradas: {list(tcp_ports.keys())}[/dim]")
|
343
|
+
|
344
|
+
for port_num, port_data in tcp_ports.items():
|
345
|
+
ports_list.append({
|
346
|
+
"portid": str(port_num),
|
347
|
+
"protocol": "tcp",
|
348
|
+
"state": port_data,
|
349
|
+
"service": port_data.get("service", {}) if isinstance(port_data, dict) else {}
|
350
|
+
})
|
351
|
+
|
352
|
+
# CORREÇÃO: Também processa formato direto de portas
|
353
|
+
if not ports_list and self.debug:
|
354
|
+
console.print(f"[dim]🐛 Tentando outros formatos de porta...[/dim]")
|
355
|
+
for key, value in host_data.items():
|
356
|
+
if key.isdigit() or (isinstance(value, dict) and "portid" in value):
|
357
|
+
if self.debug:
|
358
|
+
console.print(f"[dim]🐛 Encontrada porta: {key}[/dim]")
|
359
|
+
ports_list.append(value if isinstance(value, dict) else {"portid": key, "state": value})
|
214
360
|
|
361
|
+
if self.debug and ports_list:
|
362
|
+
console.print(f"[dim]🐛 Total de portas a processar: {len(ports_list)}[/dim]")
|
363
|
+
|
364
|
+
for i, port_info in enumerate(ports_list):
|
365
|
+
if self.debug:
|
366
|
+
console.print(f"[dim]🐛 Processando porta {i+1}/{len(ports_list)}: {port_info}[/dim]")
|
367
|
+
result = self._process_port_info(port_info, host_key)
|
368
|
+
if result:
|
369
|
+
if self.debug:
|
370
|
+
console.print(f"[dim]✅ Porta {result.port} processada com sucesso![/dim]")
|
371
|
+
results.append(result)
|
372
|
+
elif self.debug:
|
373
|
+
console.print(f"[dim]❌ Porta não passou na validação[/dim]")
|
374
|
+
|
375
|
+
elif isinstance(scan_results, list):
|
376
|
+
if self.debug:
|
377
|
+
console.print(f"[dim]🐛 Resultado é uma lista com {len(scan_results)} itens[/dim]")
|
378
|
+
for port_info in scan_results:
|
379
|
+
result = self._process_port_info(port_info, self.target)
|
380
|
+
if result:
|
215
381
|
results.append(result)
|
216
|
-
|
217
|
-
|
382
|
+
else:
|
383
|
+
if self.debug:
|
384
|
+
console.print(f"[red]⚠️ Formato de resultado desconhecido: {type(scan_results)}[/red]")
|
385
|
+
|
218
386
|
results.sort(key=lambda x: x.port)
|
219
387
|
|
220
|
-
|
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
|
388
|
+
console.print("\n[bold green] ✅ Varredura concluída![/]")
|
229
389
|
|
390
|
+
if not results:
|
391
|
+
logger.warning(
|
392
|
+
"Nenhuma porta aberta encontrada",
|
393
|
+
target=self.target,
|
394
|
+
ports=self.ports,
|
395
|
+
arguments=args
|
396
|
+
)
|
397
|
+
console.print("[yellow] ℹ️ Nenhuma porta aberta encontrada.[/]")
|
398
|
+
else:
|
399
|
+
logger.info(
|
400
|
+
"Portas abertas encontradas",
|
401
|
+
count=len(results),
|
402
|
+
ports=[r.port for r in results]
|
403
|
+
)
|
404
|
+
|
405
|
+
# Exibe tabela formatada
|
406
|
+
console.print("\n[bold cyan] 🚪 Portas Abertas:[/bold cyan]")
|
407
|
+
table_output = self.render_pipe_summary(results)
|
408
|
+
console.print(table_output)
|
409
|
+
console.print(f"\n[bold] Total: {len(results)} porta(s) aberta(s)[/bold]")
|
410
|
+
|
411
|
+
return results
|
412
|
+
|
230
413
|
except Exception as e:
|
231
|
-
|
414
|
+
error_msg = f"Erro durante a varredura Nmap: {str(e)}"
|
415
|
+
console.print(f"[bold red]❌ {error_msg}[/]")
|
416
|
+
logger.error(
|
417
|
+
error_msg,
|
418
|
+
target=self.target,
|
419
|
+
ports=self.ports,
|
420
|
+
error_type=type(e).__name__,
|
421
|
+
exc_info=True
|
422
|
+
)
|
232
423
|
raise
|
233
424
|
|
425
|
+
def _process_port_info(self, port_info: Dict, host: str) -> Optional[PortScanResult]:
|
426
|
+
"""Processa informações de uma porta individual."""
|
427
|
+
if not isinstance(port_info, dict):
|
428
|
+
if self.debug:
|
429
|
+
console.print(f"[dim]❌ port_info não é dict: {type(port_info)}[/dim]")
|
430
|
+
return None
|
431
|
+
|
432
|
+
try:
|
433
|
+
if self.debug:
|
434
|
+
console.print(f"[dim]🐛 Processando: {port_info}[/dim]")
|
435
|
+
|
436
|
+
# CORREÇÃO: Aceita diferentes formatos de estado
|
437
|
+
port_state = port_info.get("state", {})
|
438
|
+
|
439
|
+
if self.debug:
|
440
|
+
console.print(f"[dim]🐛 port_state: {port_state} (tipo: {type(port_state)})[/dim]")
|
441
|
+
|
442
|
+
# Formato 1: { "state": { "state": "open" } }
|
443
|
+
if isinstance(port_state, dict):
|
444
|
+
state_value = port_state.get("state", "unknown")
|
445
|
+
# Formato 2: { "state": "open" }
|
446
|
+
elif isinstance(port_state, str):
|
447
|
+
state_value = port_state
|
448
|
+
else:
|
449
|
+
state_value = "unknown"
|
450
|
+
|
451
|
+
if self.debug:
|
452
|
+
console.print(f"[dim]🐛 state_value: {state_value}[/dim]")
|
453
|
+
|
454
|
+
# CORREÇÃO: Aceita portas abertas E filtered (importantes)
|
455
|
+
if state_value not in ("open", "filtered"):
|
456
|
+
if self.debug:
|
457
|
+
console.print(f"[dim]❌ Estado '{state_value}' não aceito[/dim]")
|
458
|
+
return None
|
459
|
+
|
460
|
+
port_num = int(port_info.get("portid", 0))
|
461
|
+
if port_num == 0:
|
462
|
+
if self.debug:
|
463
|
+
console.print(f"[dim]❌ Porta número 0 inválida[/dim]")
|
464
|
+
return None
|
465
|
+
|
466
|
+
if self.debug:
|
467
|
+
console.print(f"[dim] ✅ Porta {port_num} válida, estado: {state_value}[/dim]")
|
468
|
+
|
469
|
+
# Extrai informações do serviço
|
470
|
+
service_data = port_info.get("service", {})
|
471
|
+
service_info = None
|
472
|
+
|
473
|
+
if isinstance(service_data, dict) and service_data:
|
474
|
+
service_info = ServiceInfo(
|
475
|
+
name=service_data.get("name", "unknown"),
|
476
|
+
version=service_data.get("version", ""),
|
477
|
+
banner=service_data.get("product", ""),
|
478
|
+
)
|
479
|
+
|
480
|
+
result = PortScanResult(
|
481
|
+
port=port_num,
|
482
|
+
protocol=port_info.get("protocol", "tcp"),
|
483
|
+
status=state_value,
|
484
|
+
target=host,
|
485
|
+
service=service_info,
|
486
|
+
banner=service_data.get("product", "") if isinstance(service_data, dict) else "",
|
487
|
+
)
|
488
|
+
|
489
|
+
logger.debug(
|
490
|
+
"Porta processada",
|
491
|
+
port=port_num,
|
492
|
+
status=state_value,
|
493
|
+
service=service_info.name if service_info else "unknown",
|
494
|
+
protocol=port_info.get("protocol")
|
495
|
+
)
|
496
|
+
|
497
|
+
return result
|
498
|
+
|
499
|
+
except (TypeError, ValueError, KeyError) as e:
|
500
|
+
if self.debug:
|
501
|
+
console.print(f"[red]❌ Erro ao processar porta: {e}[/red]")
|
502
|
+
logger.warning("Erro ao processar porta",
|
503
|
+
port_info=port_info,
|
504
|
+
error=str(e),
|
505
|
+
error_type=type(e).__name__)
|
506
|
+
return None
|
507
|
+
|
234
508
|
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
|
-
"""
|
509
|
+
"""Formata os resultados da varredura no formato solicitado."""
|
242
510
|
if output_format == "json":
|
243
511
|
return json.dumps([r.to_dict() for r in results], indent=2)
|
244
512
|
|
245
513
|
if output_format == "pipe":
|
246
514
|
base = PortScanner.render_pipe_summary(results)
|
247
|
-
suffix = f"\n\n🔍 Total de portas abertas: {len(results)}"
|
515
|
+
suffix = f"\n\n 🔍 Total de portas abertas: {len(results)}"
|
248
516
|
if total_ports:
|
249
|
-
suffix = f"\n\n🔍 Resumo: {len(results)} portas abertas de {total_ports} verificadas"
|
517
|
+
suffix = f"\n\n 🔍 Resumo: {len(results)} portas abertas de {total_ports} verificadas"
|
250
518
|
return base + suffix
|
251
|
-
|
252
519
|
|
253
520
|
# Formato de texto
|
254
|
-
output = []
|
255
|
-
|
256
521
|
if not results:
|
257
522
|
return "Nenhuma porta aberta encontrada."
|
258
523
|
|
@@ -260,6 +525,7 @@ def format_scan_results(results: List[PortScanResult], output_format: str = "tex
|
|
260
525
|
table = Table(show_header=True, header_style="bold magenta", box=box.ROUNDED)
|
261
526
|
table.add_column("Porta", style="cyan", width=10)
|
262
527
|
table.add_column("Protocolo", style="blue")
|
528
|
+
table.add_column("Status", style="yellow")
|
263
529
|
table.add_column("Serviço", style="green")
|
264
530
|
table.add_column("Versão", style="yellow")
|
265
531
|
table.add_column("SSL/TLS", style="magenta")
|
@@ -272,19 +538,20 @@ def format_scan_results(results: List[PortScanResult], output_format: str = "tex
|
|
272
538
|
table.add_row(
|
273
539
|
f"{result.port}",
|
274
540
|
result.protocol.upper(),
|
541
|
+
result.status,
|
275
542
|
service,
|
276
|
-
version,
|
543
|
+
version or "-",
|
277
544
|
ssl
|
278
545
|
)
|
279
546
|
|
280
|
-
output
|
547
|
+
output = [str(table)]
|
281
548
|
|
282
549
|
# Adiciona resumo
|
283
550
|
if total_ports:
|
284
|
-
output.append(f"\n🔍 [bold]Resumo:[/bold] {len(results)} portas abertas de {total_ports} verificadas")
|
551
|
+
output.append(f"\n 🔍 [bold]Resumo:[/bold] {len(results)} portas abertas de {total_ports} verificadas")
|
285
552
|
else:
|
286
|
-
output.append(f"\n🔍 [bold]Total de portas
|
553
|
+
output.append(f"\n 🔍 [bold]Total de portas:[/bold] {len(results)}")
|
287
554
|
|
288
555
|
return "\n".join(output)
|
289
556
|
|
290
|
-
__all__ = ["PortScanner", "PortScanResult", "format_scan_results"]
|
557
|
+
__all__ = ["PortScanner", "PortScanResult", "format_scan_results"]
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: moriarty-project
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.27
|
4
4
|
Summary: Client-side OSINT toolkit with forensic-grade evidence handling.
|
5
5
|
Project-URL: Homepage, https://github.com/DonatoReis/moriarty
|
6
6
|
Project-URL: Documentation, https://github.com/DonatoReis/moriarty#readme
|
@@ -30,7 +30,7 @@ Requires-Dist: httpx[http2]>=0.27.0
|
|
30
30
|
Requires-Dist: idna>=3.6
|
31
31
|
Requires-Dist: jsonpath-ng>=1.6.0
|
32
32
|
Requires-Dist: lxml>=5.3.0
|
33
|
-
Requires-Dist: netifaces>=0.
|
33
|
+
Requires-Dist: netifaces-plus>=0.12.4
|
34
34
|
Requires-Dist: networkx>=3.2.0
|
35
35
|
Requires-Dist: orjson>=3.9.0
|
36
36
|
Requires-Dist: packaging>=25.0
|
@@ -40,7 +40,7 @@ Requires-Dist: psutil>=5.9.0
|
|
40
40
|
Requires-Dist: pycryptodomex>=3.23.0
|
41
41
|
Requires-Dist: pydantic>=2.7.0
|
42
42
|
Requires-Dist: pyopenssl>=25.0.0
|
43
|
-
Requires-Dist:
|
43
|
+
Requires-Dist: python3-nmap>=1.9.1
|
44
44
|
Requires-Dist: pyyaml>=6.0
|
45
45
|
Requires-Dist: rapidfuzz>=3.0.0
|
46
46
|
Requires-Dist: requests>=2.32.0
|
@@ -100,7 +100,7 @@ Description-Content-Type: text/markdown
|
|
100
100
|
<!-- Badges -->
|
101
101
|
<p align="center">
|
102
102
|
<a href="https://pypi.org/project/moriarty-project/">
|
103
|
-
<img src="https://img.shields.io/badge/version-0.1.
|
103
|
+
<img src="https://img.shields.io/badge/version-0.1.27-blue" alt="Version 0.1.27">
|
104
104
|
</a>
|
105
105
|
<a href="https://www.python.org/downloads/">
|
106
106
|
<img src="https://img.shields.io/pypi/pyversions/moriarty-project?color=blue" alt="Python Versions">
|
@@ -154,7 +154,7 @@ Description-Content-Type: text/markdown
|
|
154
154
|
pipx install moriarty-project
|
155
155
|
|
156
156
|
# OU para instalar uma versão específica
|
157
|
-
# pipx install moriarty-project==0.1.
|
157
|
+
# pipx install moriarty-project==0.1.27
|
158
158
|
|
159
159
|
# Verificar a instalação
|
160
160
|
moriarty --help
|