moriarty-project 0.1.25__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.
- moriarty/__init__.py +1 -1
- moriarty/cli/domain_cmd.py +629 -119
- moriarty/modules/domain_scanner.py +182 -59
- moriarty/modules/port_scanner.py +266 -158
- moriarty/modules/port_scanner_nmap.py +290 -0
- moriarty/modules/web_crawler.py +774 -152
- {moriarty_project-0.1.25.dist-info → moriarty_project-0.1.26.dist-info}/METADATA +5 -3
- {moriarty_project-0.1.25.dist-info → moriarty_project-0.1.26.dist-info}/RECORD +10 -9
- {moriarty_project-0.1.25.dist-info → moriarty_project-0.1.26.dist-info}/WHEEL +0 -0
- {moriarty_project-0.1.25.dist-info → moriarty_project-0.1.26.dist-info}/entry_points.txt +0 -0
moriarty/modules/port_scanner.py
CHANGED
@@ -2,31 +2,19 @@
|
|
2
2
|
from __future__ import annotations
|
3
3
|
|
4
4
|
import asyncio
|
5
|
-
import contextlib
|
6
5
|
import json
|
7
6
|
import re
|
8
|
-
import socket
|
9
7
|
import ssl
|
8
|
+
import contextlib
|
10
9
|
from dataclasses import dataclass, field
|
11
|
-
from datetime import datetime
|
10
|
+
from datetime import datetime
|
11
|
+
from typing import Dict, List, Optional, Any, Set
|
12
12
|
from pathlib import Path
|
13
|
-
from typing import Dict, List, Optional, Tuple, Any, Set, Union
|
14
|
-
from urllib.parse import urlparse
|
15
|
-
import aiofiles
|
16
|
-
import yaml
|
17
|
-
import requests
|
18
|
-
from packaging import version
|
19
|
-
|
20
|
-
# Importa a classe ServiceInfo para uso no código
|
21
|
-
|
22
|
-
import aiohttp
|
23
|
-
import dns.resolver
|
24
13
|
import dns.asyncresolver
|
25
|
-
import OpenSSL.crypto
|
26
14
|
import structlog
|
27
15
|
from rich.console import Console
|
28
|
-
from rich.
|
29
|
-
from rich.table import Table
|
16
|
+
from rich.progress import track
|
17
|
+
from rich.table import Table, box
|
30
18
|
|
31
19
|
logger = structlog.get_logger(__name__)
|
32
20
|
console = Console()
|
@@ -182,7 +170,7 @@ class ServiceInfo:
|
|
182
170
|
ssl: bool = False
|
183
171
|
ssl_info: Optional[Dict[str, Any]] = None
|
184
172
|
banner: Optional[str] = None
|
185
|
-
vulns: List[
|
173
|
+
vulns: List[str] = field(default_factory=list) # Alterado para List[str]
|
186
174
|
cpe: Optional[str] = None
|
187
175
|
extra: Dict[str, Any] = field(default_factory=dict)
|
188
176
|
confidence: float = 0.0 # Nível de confiança na identificação (0.0 a 1.0)
|
@@ -206,7 +194,7 @@ class ServiceInfo:
|
|
206
194
|
class PortScanResult:
|
207
195
|
port: int
|
208
196
|
protocol: str = "tcp"
|
209
|
-
status: str = "
|
197
|
+
status: str = "closed"
|
210
198
|
target: Optional[str] = None
|
211
199
|
service: Optional[ServiceInfo] = None
|
212
200
|
banner: Optional[str] = None
|
@@ -269,112 +257,197 @@ class PortScanner:
|
|
269
257
|
# Ajusta timeout baseado no nível de stealth
|
270
258
|
self.timeout = timeout * (1 + (self.stealth_level * 0.5))
|
271
259
|
|
272
|
-
# Cache para serviços já identificados
|
273
|
-
self.service_cache: Dict[int, ServiceInfo] = {}
|
274
|
-
|
275
260
|
# Resolvedor DNS assíncrono
|
276
261
|
self.resolver = dns.asyncresolver.Resolver()
|
277
262
|
self.resolver.timeout = 2.0
|
278
263
|
self.resolver.lifetime = 2.0
|
279
264
|
|
280
265
|
async def scan(self) -> List[PortScanResult]:
|
281
|
-
"""Executa a varredura de portas."""
|
282
|
-
console.print(f"[bold]Iniciando varredura em {self.target}[/bold]")
|
283
|
-
|
266
|
+
"""Executa a varredura de portas de forma assíncrona."""
|
267
|
+
console.print(f"[bold]🔍 Iniciando varredura em {self.target}[/bold]")
|
268
|
+
ports = sorted(PROFILES[self.profile])
|
269
|
+
total_ports = len(ports)
|
270
|
+
console.print(f"📊 Perfil: [bold]{self.profile}[/bold], Portas a verificar: [bold]{total_ports}[/bold]")
|
284
271
|
|
285
|
-
ports = PROFILES[self.profile]
|
286
272
|
if self.stealth_level > 0:
|
287
|
-
|
288
|
-
random.shuffle(ports)
|
289
|
-
|
290
|
-
sem = asyncio.Semaphore(self.concurrency)
|
291
|
-
results: List[PortScanResult] = []
|
273
|
+
console.print(f"🔒 Modo furtivo: nível {self.stealth_level}")
|
292
274
|
|
293
|
-
|
294
|
-
|
295
|
-
result = await self._probe_port(port)
|
296
|
-
if result:
|
297
|
-
results.append(result)
|
298
|
-
self._print_result(result)
|
275
|
+
results: List[PortScanResult] = []
|
276
|
+
sem = asyncio.Semaphore(self.concurrency)
|
299
277
|
|
300
|
-
#
|
301
|
-
|
302
|
-
|
278
|
+
# Barra de progresso
|
279
|
+
with console.status("[bold]Verificando portas...") as status:
|
280
|
+
async def scan_port(port: int) -> PortScanResult:
|
281
|
+
async with sem:
|
282
|
+
try:
|
283
|
+
return await self._probe_port(port)
|
284
|
+
except Exception as e:
|
285
|
+
logger.debug(f"Erro ao escanear porta {port}: {str(e)}")
|
286
|
+
return PortScanResult(port=port, status="error", target=self.target)
|
287
|
+
|
288
|
+
# Executa as tarefas em lote para evitar sobrecarga de memória
|
289
|
+
batch_size = min(100, max(10, self.concurrency * 2))
|
290
|
+
for i in range(0, len(ports), batch_size):
|
291
|
+
batch = ports[i:i + batch_size]
|
292
|
+
tasks = [scan_port(port) for port in batch]
|
293
|
+
batch_results = await asyncio.gather(*tasks)
|
294
|
+
results.extend(batch_results)
|
295
|
+
|
296
|
+
# Atualiza status
|
297
|
+
open_ports = len([r for r in results if r.status == "open"])
|
298
|
+
status.update(f"[bold]Verificando portas... {min(i + len(batch), total_ports)}/{total_ports} (abertas: {open_ports})")
|
303
299
|
|
304
300
|
# Ordena os resultados por número de porta
|
305
301
|
results.sort(key=lambda r: r.port)
|
306
302
|
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
port_info = f"[bold blue]{result.port:>5}/tcp[/bold blue]"
|
312
|
-
status = "[green]open[/green]" if result.status == "open" else "[yellow]filtered[/yellow]"
|
303
|
+
# Filtra e conta portas por status
|
304
|
+
open_ports = [r for r in results if r.status == "open"]
|
305
|
+
closed_ports = [r for r in results if r.status == "closed"]
|
306
|
+
error_ports = [r for r in results if r.status == "error"]
|
313
307
|
|
314
|
-
|
315
|
-
|
308
|
+
# Exibe resultados
|
309
|
+
console.print("\n[bold]📊 Resultados da varredura:[/bold]")
|
316
310
|
|
317
|
-
|
318
|
-
|
311
|
+
# Tabela de portas abertas
|
312
|
+
if open_ports:
|
313
|
+
table = Table(show_header=True, header_style="bold magenta", box=box.ROUNDED)
|
314
|
+
table.add_column("Porta", style="red", width=10)
|
315
|
+
table.add_column("Status", style="green")
|
316
|
+
table.add_column("Serviço", style="blue")
|
317
|
+
table.add_column("Detalhes", style="yellow")
|
319
318
|
|
320
|
-
|
321
|
-
|
319
|
+
for result in open_ports:
|
320
|
+
service = result.service.name if result.service else "desconhecido"
|
321
|
+
version = f" {result.service.version}" if result.service and result.service.version else ""
|
322
|
+
ssl = "🔒" if result.service and result.service.ssl else ""
|
323
|
+
vulns = f" [red]({len(result.service.vulns)} vulns)" if result.service and result.service.vulns else ""
|
324
|
+
|
325
|
+
table.add_row(
|
326
|
+
f"{result.port}/tcp",
|
327
|
+
"[green]ABERTA[/green]",
|
328
|
+
f"[red]{service}{version}[/red] {ssl}",
|
329
|
+
vulns
|
330
|
+
)
|
331
|
+
|
332
|
+
# Adiciona banner se disponível
|
333
|
+
if result.banner:
|
334
|
+
banner = result.banner.split('\n')[0][:80] # Pega apenas a primeira linha e limita o tamanho
|
335
|
+
table.add_row("", "", f"[dim]↳ {banner}...[/dim]", "")
|
322
336
|
|
323
|
-
|
324
|
-
|
325
|
-
service_info += f" [red]({vuln_count} vulns)[/red]"
|
337
|
+
console.print("\n[bold]🚪 Portas abertas:[/bold]")
|
338
|
+
console.print(table)
|
326
339
|
|
327
|
-
|
340
|
+
# Resumo
|
341
|
+
console.print("\n[bold]📋 Resumo:[/bold]")
|
342
|
+
console.print(f" • [green]Portas abertas:[/green] {len(open_ports)}")
|
343
|
+
console.print(f" • [red]Portas fechadas:[/red] {len(closed_ports)}")
|
344
|
+
if error_ports:
|
345
|
+
console.print(f" • [yellow]Erros:[/yellow] {len(error_ports)} porta(s) com erro")
|
328
346
|
|
329
|
-
|
330
|
-
|
347
|
+
# Lista de portas abertas resumida
|
348
|
+
if open_ports:
|
349
|
+
open_ports_str = ", ".join(str(r.port) for r in open_ports)
|
350
|
+
if len(open_ports_str) > 80:
|
351
|
+
open_ports_str = open_ports_str[:77] + "..."
|
352
|
+
console.print(f"\n🔍 Portas abertas: {open_ports_str}")
|
353
|
+
|
354
|
+
return results
|
331
355
|
|
332
|
-
|
356
|
+
def _print_result(self, result: PortScanResult):
|
357
|
+
"""Método mantido para compatibilidade, mas não é mais usado internamente."""
|
358
|
+
pass
|
359
|
+
async def _probe_port(self, port: int) -> PortScanResult:
|
333
360
|
"""Verifica se uma porta está aberta e coleta informações do serviço."""
|
361
|
+
result = PortScanResult(port=port, status="closed", target=self.target)
|
362
|
+
|
334
363
|
# Atraso aleatório para evitar detecção
|
335
364
|
if self.stealth_level > 0:
|
365
|
+
import random
|
336
366
|
await asyncio.sleep(random.uniform(0.01, 0.5) * self.stealth_level)
|
337
367
|
|
338
|
-
#
|
368
|
+
# Portas que devem ser verificadas com TLS primeiro
|
369
|
+
ssl_ports = {
|
370
|
+
# HTTPS
|
371
|
+
443, # HTTPS padrão
|
372
|
+
8443, # HTTPS alternativo
|
373
|
+
10443, # HTTPS alternativo
|
374
|
+
4443, # HTTPS alternativo
|
375
|
+
1443, # HTTPS alternativo
|
376
|
+
9443, # Nginx Admin / HTTPS alternativo
|
377
|
+
# Email seguro
|
378
|
+
465, # SMTPS
|
379
|
+
993, # IMAPS
|
380
|
+
995, # POP3S
|
381
|
+
# Outros serviços seguros
|
382
|
+
636, # LDAPS
|
383
|
+
853, # DNS sobre TLS
|
384
|
+
989, # FTPS Data
|
385
|
+
990, # FTPS Control
|
386
|
+
992, # Telnet sobre TLS
|
387
|
+
994, # IRCS
|
388
|
+
# Adicionais comuns
|
389
|
+
5061, # SIP-TLS
|
390
|
+
5062, # SIP-TLS
|
391
|
+
5989, # CIM XML sobre HTTPS
|
392
|
+
832, # NETCONF sobre TLS
|
393
|
+
6514, # Syslog sobre TLS
|
394
|
+
5684, # CoAP sobre DTLS
|
395
|
+
8883, # MQTT sobre SSL
|
396
|
+
8884, # MQTT sobre WebSocket
|
397
|
+
8888, # HTTP alternativo com SSL
|
398
|
+
10000, # Webmin
|
399
|
+
10001, # Webmin alternativo
|
400
|
+
20000 # Usermin (Webmin para usuários)
|
401
|
+
}
|
402
|
+
|
403
|
+
# Portas que devem tentar obter banner via TLS
|
404
|
+
http_ports_with_tls = {443, 8443, 10443, 4443, 9443, 10000, 10001, 20000}
|
405
|
+
|
406
|
+
|
339
407
|
try:
|
340
|
-
#
|
341
|
-
|
408
|
+
# Para portas SSL conhecidas, tenta TLS primeiro
|
409
|
+
if port in ssl_ports:
|
410
|
+
ssl_info = await self._check_ssl(port)
|
411
|
+
if ssl_info:
|
412
|
+
result.status = "open"
|
413
|
+
service_name = self._identify_ssl_service(port, ssl_info)
|
414
|
+
result.service = ServiceInfo(name=service_name, ssl=True, ssl_info=ssl_info)
|
415
|
+
|
416
|
+
# Tenta obter banner apenas para portas HTTP/HTTPS conhecidas
|
417
|
+
if port in http_ports_with_tls:
|
418
|
+
try:
|
419
|
+
banner = await self._get_ssl_banner(port)
|
420
|
+
if banner:
|
421
|
+
result.banner = banner
|
422
|
+
# Atualiza informações do serviço com base no banner
|
423
|
+
service_info = await self._identify_service(port, banner)
|
424
|
+
if service_info:
|
425
|
+
result.service.name = service_info.name
|
426
|
+
result.service.version = service_info.version
|
427
|
+
result.service.cpe = service_info.cpe
|
428
|
+
except Exception as e:
|
429
|
+
logger.debug(f"Erro ao obter banner SSL da porta {port}: {str(e)}")
|
430
|
+
|
431
|
+
return result
|
432
|
+
|
433
|
+
|
434
|
+
# Se não for porta SSL ou falhar, tenta conexão TCP normal
|
342
435
|
reader, writer = await asyncio.wait_for(
|
343
436
|
asyncio.open_connection(self.target, port),
|
344
|
-
timeout=
|
437
|
+
timeout=self.timeout
|
345
438
|
)
|
346
439
|
|
347
|
-
# Se chegou aqui, a porta está aberta
|
348
|
-
|
349
|
-
|
350
|
-
# Tenta obter o banner do serviço
|
440
|
+
# Se chegou aqui, a conexão TCP foi estabelecida, mas ainda não sabemos se a porta está realmente aberta
|
441
|
+
# Vamos tentar ler o banner para confirmar
|
351
442
|
try:
|
352
|
-
#
|
353
|
-
|
443
|
+
# Lê o banner inicial (até 1024 bytes)
|
444
|
+
banner_bytes = await asyncio.wait_for(
|
445
|
+
reader.read(1024),
|
446
|
+
timeout=self.timeout
|
447
|
+
)
|
354
448
|
|
355
|
-
#
|
356
|
-
|
357
|
-
if port in [80, 8080, 8000, 8008, 8081, 8443, 443]:
|
358
|
-
# HTTP/HTTPS - envia um HEAD / HTTP/1.0
|
359
|
-
writer.write(b"HEAD / HTTP/1.0\r\nHost: " + self.target.encode() + b"\r\n\r\n")
|
360
|
-
elif port == 21:
|
361
|
-
# FTP - envia um comando USER anonymous
|
362
|
-
writer.write(b"USER anonymous\r\n")
|
363
|
-
elif port == 22:
|
364
|
-
# SSH - apenas lê a versão do servidor
|
365
|
-
pass
|
366
|
-
elif port == 25 or port == 587 or port == 465:
|
367
|
-
# SMTP - envia EHLO
|
368
|
-
writer.write(b"EHLO moriarty-scanner\r\n")
|
369
|
-
elif port == 53:
|
370
|
-
# DNS - envia uma query DNS padrão
|
371
|
-
query = b'\x00\x01\x01\x00\x00\x01\x00\x00\x00\x00\x00\x01\x07version\x04bind\x00\x00\x10\x00\x03'
|
372
|
-
writer.write(query)
|
373
|
-
|
374
|
-
await writer.drain()
|
375
|
-
|
376
|
-
# Lê até 1024 bytes
|
377
|
-
banner_bytes = await asyncio.wait_for(reader.read(1024), timeout=read_timeout)
|
449
|
+
# Se chegou aqui sem exceção, a porta está realmente aberta
|
450
|
+
result.status = "open"
|
378
451
|
|
379
452
|
if banner_bytes:
|
380
453
|
# Tenta decodificar como texto
|
@@ -382,70 +455,74 @@ class PortScanner:
|
|
382
455
|
banner = banner_bytes.decode('utf-8', errors='replace').strip()
|
383
456
|
result.banner = banner
|
384
457
|
|
385
|
-
#
|
458
|
+
# Identifica o serviço baseado no banner
|
386
459
|
service_info = await self._identify_service(port, banner)
|
387
460
|
if service_info:
|
388
461
|
result.service = service_info
|
462
|
+
|
389
463
|
except UnicodeDecodeError:
|
390
|
-
# Se não for texto,
|
391
|
-
result.banner = banner_bytes.hex(
|
392
|
-
else:
|
393
|
-
# Se não recebeu banner, verifica se a porta é conhecida
|
394
|
-
if port in SERVICE_MAP:
|
395
|
-
result.service = ServiceInfo(name=SERVICE_MAP[port])
|
396
|
-
else:
|
397
|
-
# Se não recebeu resposta, marca como filtrada
|
398
|
-
result.status = "filtered"
|
399
|
-
return result
|
400
|
-
except (asyncio.TimeoutError, ConnectionResetError, OSError):
|
401
|
-
# Ignora erros de leitura do banner
|
402
|
-
pass
|
403
|
-
|
404
|
-
# Verifica se é um serviço SSL/TLS
|
405
|
-
if port in [443, 465, 636, 993, 995, 8443] or (result.service and result.service.ssl):
|
406
|
-
ssl_info = await self._check_ssl(port)
|
407
|
-
if ssl_info:
|
408
|
-
if not result.service:
|
409
|
-
result.service = ServiceInfo(name="ssl")
|
410
|
-
result.service.ssl = True
|
411
|
-
result.service.ssl_info = ssl_info
|
412
|
-
|
413
|
-
# Tenta identificar o serviço SSL
|
414
|
-
if not result.service.name or result.service.name == "ssl":
|
415
|
-
service_name = self._identify_ssl_service(port, ssl_info)
|
416
|
-
result.service.name = service_name
|
417
|
-
|
418
|
-
# Se não identificou o serviço, tenta pelo número da porta
|
419
|
-
if not result.service and port in SERVICE_MAP:
|
420
|
-
result.service = ServiceInfo(name=SERVICE_MAP[port])
|
421
|
-
|
422
|
-
# Se ainda não identificou o serviço e não recebeu banner, marca como filtrada
|
423
|
-
if not result.service and not result.banner:
|
424
|
-
result.status = "filtered"
|
425
|
-
return result
|
464
|
+
# Se não for texto, exibe como hex
|
465
|
+
result.banner = f"[binary data] {banner_bytes.hex()[:100]}..."
|
426
466
|
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
467
|
+
# Se a porta está aberta, verifica SSL/TLS em portas comuns (apenas se ainda não verificou)
|
468
|
+
if result.status == "open" and port in ssl_ports and (result.service is None or not result.service.ssl):
|
469
|
+
try:
|
470
|
+
ssl_info = await self._check_ssl(port)
|
471
|
+
if ssl_info:
|
472
|
+
if result.service is None:
|
473
|
+
result.service = ServiceInfo(name=SERVICE_MAP.get(port, "unknown"))
|
474
|
+
result.service.ssl = True
|
475
|
+
result.service.ssl_info = ssl_info
|
476
|
+
# Atualiza o nome do serviço com base nas informações SSL
|
477
|
+
ssl_service = self._identify_ssl_service(port, ssl_info)
|
478
|
+
if ssl_service:
|
479
|
+
result.service.name = ssl_service
|
480
|
+
except Exception as e:
|
481
|
+
logger.debug(f"Erro ao verificar SSL na porta {port}: {str(e)}")
|
482
|
+
# Se falhar ao verificar SSL, mantém a porta como aberta mas sem informações SSL
|
432
483
|
|
484
|
+
except (asyncio.TimeoutError, ConnectionResetError, OSError) as e:
|
485
|
+
logger.debug(f"Erro ao ler banner da porta {port}: {str(e)}")
|
486
|
+
|
487
|
+
finally:
|
488
|
+
# Fecha a conexão
|
489
|
+
writer.close()
|
490
|
+
try:
|
491
|
+
await writer.wait_closed()
|
492
|
+
except Exception as e:
|
493
|
+
logger.debug(f"Erro ao fechar conexão: {str(e)}")
|
494
|
+
|
433
495
|
except (asyncio.TimeoutError, ConnectionRefusedError, OSError):
|
434
496
|
# Porta fechada ou inacessível
|
435
|
-
|
497
|
+
result.status = "closed"
|
436
498
|
|
437
499
|
except Exception as e:
|
438
|
-
logger.error(f"Erro ao verificar porta {port}: {str(e)}")
|
439
|
-
|
500
|
+
logger.error(f"Erro inesperado ao verificar porta {port}: {str(e)}")
|
501
|
+
result.status = "error"
|
440
502
|
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
503
|
+
# Se a porta está aberta, tenta identificar o serviço se ainda não identificado
|
504
|
+
if result.status == "open":
|
505
|
+
try:
|
506
|
+
# Se tem serviço SSL mas não foi identificado corretamente
|
507
|
+
if result.service and (not result.service.name or result.service.name == "ssl"):
|
508
|
+
if 'ssl_info' in locals() and ssl_info:
|
509
|
+
service_name = self._identify_ssl_service(port, ssl_info)
|
510
|
+
if service_name:
|
511
|
+
result.service.name = service_name
|
512
|
+
|
513
|
+
# Se ainda não identificou o serviço, tenta pelo número da porta
|
514
|
+
if not result.service and port in SERVICE_MAP:
|
515
|
+
result.service = ServiceInfo(name=SERVICE_MAP[port])
|
516
|
+
|
517
|
+
# Verifica vulnerabilidades conhecidas para portas abertas
|
518
|
+
if self.check_vulns and result.service:
|
519
|
+
result.service.vulns = self._check_known_vulns(port, result.service.name)
|
520
|
+
except Exception as e:
|
521
|
+
logger.error(f"Erro ao processar informações da porta {port}: {str(e)}")
|
522
|
+
# Em caso de erro, mantém a porta como aberta mas sem informações adicionais
|
523
|
+
|
524
|
+
|
525
|
+
return result
|
449
526
|
|
450
527
|
async def _identify_service(self, port: int, banner: str) -> Optional[ServiceInfo]:
|
451
528
|
"""Tenta identificar o serviço rodando na porta com base no banner."""
|
@@ -834,6 +911,27 @@ class PortScanner:
|
|
834
911
|
await writer.wait_closed()
|
835
912
|
except:
|
836
913
|
pass
|
914
|
+
|
915
|
+
async def _get_ssl_banner(self, port: int) -> Optional[str]:
|
916
|
+
try:
|
917
|
+
ssl_context = ssl.create_default_context()
|
918
|
+
ssl_context.check_hostname = False
|
919
|
+
ssl_context.verify_mode = ssl.CERT_NONE
|
920
|
+
reader, writer = await asyncio.wait_for(
|
921
|
+
asyncio.open_connection(self.target, port, ssl=ssl_context, server_hostname=self.target),
|
922
|
+
timeout=self.timeout
|
923
|
+
)
|
924
|
+
req = f"HEAD / HTTP/1.0\r\nHost: {self.target}\r\nUser-Agent: moriarty/1.0\r\nConnection: close\r\n\r\n"
|
925
|
+
writer.write(req.encode("ascii"))
|
926
|
+
await writer.drain()
|
927
|
+
data = await asyncio.wait_for(reader.read(2048), timeout=self.timeout)
|
928
|
+
writer.close()
|
929
|
+
with contextlib.suppress(Exception):
|
930
|
+
await writer.wait_closed()
|
931
|
+
return data.decode("utf-8", errors="replace").strip()
|
932
|
+
except Exception:
|
933
|
+
return None
|
934
|
+
|
837
935
|
|
838
936
|
def _check_known_vulns(self, port: int, service_name: str) -> List[str]:
|
839
937
|
"""Verifica vulnerabilidades conhecidas para o serviço na porta."""
|
@@ -844,29 +942,35 @@ class PortScanner:
|
|
844
942
|
if service.lower() in service_name.lower():
|
845
943
|
vulns.extend(cves)
|
846
944
|
|
847
|
-
# Verifica vulnerabilidades específicas da porta
|
945
|
+
# Verifica vulnerabilidades específicas da porta (apenas como informação, não como confirmação)
|
848
946
|
if port == 22: # SSH
|
849
|
-
vulns.extend(["CVE-2016-0777", "CVE-2016-0778", "CVE-2018-15473"])
|
947
|
+
vulns.extend(["CVE-2016-0777 (verificar versão)", "CVE-2016-0778 (verificar versão)", "CVE-2018-15473 (verificar versão)"])
|
850
948
|
elif port == 445: # SMB
|
851
|
-
vulns.extend(["
|
949
|
+
vulns.extend(["Possível vulnerabilidade SMB - requer verificação de versão"])
|
852
950
|
elif port == 3389: # RDP
|
853
|
-
vulns.extend(["
|
951
|
+
vulns.extend(["Possível vulnerabilidade RDP - requer verificação de versão"])
|
854
952
|
elif port == 27017: # MongoDB
|
855
|
-
vulns.extend(["
|
953
|
+
vulns.extend(["Acesso não autenticado (se sem senha)"])
|
856
954
|
elif port == 9200: # Elasticsearch
|
857
|
-
vulns.extend(["
|
955
|
+
vulns.extend(["Possível exposição indevida - verificar configurações"])
|
858
956
|
elif port == 11211: # Memcached
|
859
|
-
vulns.extend(["DRDoS
|
957
|
+
vulns.extend(["Possível amplificação DRDoS - verificar configuração"])
|
860
958
|
elif port == 2375: # Docker
|
861
|
-
vulns.extend(["
|
959
|
+
vulns.extend(["API Docker exposta - verificar autenticação"])
|
862
960
|
elif port == 10250: # Kubelet
|
863
|
-
vulns.extend(["
|
961
|
+
vulns.extend(["Kubelet exposto - verificar autenticação"])
|
864
962
|
|
865
963
|
return list(set(vulns)) # Remove duplicatas
|
866
964
|
|
867
965
|
|
868
|
-
def format_scan_results(results: List[PortScanResult], output_format: str = "text") -> str:
|
869
|
-
"""Formata os resultados da varredura no formato solicitado.
|
966
|
+
def format_scan_results(results: List[PortScanResult], output_format: str = "text", total_ports: Optional[int] = None) -> str:
|
967
|
+
"""Formata os resultados da varredura no formato solicitado.
|
968
|
+
|
969
|
+
Args:
|
970
|
+
results: Lista de resultados da varredura
|
971
|
+
output_format: Formato de saída ('text' ou 'json')
|
972
|
+
total_ports: Número total de portas verificadas (opcional)
|
973
|
+
"""
|
870
974
|
if output_format.lower() == "json":
|
871
975
|
return json.dumps([r.to_dict() for r in results], indent=2)
|
872
976
|
|
@@ -875,7 +979,7 @@ def format_scan_results(results: List[PortScanResult], output_format: str = "tex
|
|
875
979
|
output.append("")
|
876
980
|
output.append(f"[bold]Resultado da varredura de portas[/bold]")
|
877
981
|
output.append(f"Alvo: {results[0].target if results else 'N/A'}")
|
878
|
-
output.append(f"Portas verificadas: {len(results)}")
|
982
|
+
output.append(f"Portas verificadas: {total_ports if total_ports is not None else len([r for r in results if r.status != 'error'])}")
|
879
983
|
output.append("-" * 80)
|
880
984
|
|
881
985
|
# Cabeçalho da tabela
|
@@ -889,9 +993,13 @@ def format_scan_results(results: List[PortScanResult], output_format: str = "tex
|
|
889
993
|
if result.status == "open":
|
890
994
|
port = f"[green]{result.port}[/green]"
|
891
995
|
status = "[green]ABERTA[/green]"
|
996
|
+
elif result.status == "closed":
|
997
|
+
port = f"[red]{result.port}[/red]"
|
998
|
+
status = "[red]FECHADA[/red]"
|
892
999
|
else:
|
893
1000
|
port = f"[yellow]{result.port}[/yellow]"
|
894
|
-
status = "[yellow]
|
1001
|
+
status = "[yellow]ERRO[/yellow]"
|
1002
|
+
|
895
1003
|
|
896
1004
|
service = result.service.name if result.service else "desconhecido"
|
897
1005
|
version = f" {result.service.version}" if result.service and result.service.version else ""
|