moriarty-project 0.1.25__py3-none-any.whl → 0.1.27__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,942 +0,0 @@
1
- """Port scanning avançado com detecção de serviços e vulnerabilidades."""
2
- from __future__ import annotations
3
-
4
- import asyncio
5
- import contextlib
6
- import json
7
- import re
8
- import socket
9
- import ssl
10
- from dataclasses import dataclass, field
11
- from datetime import datetime, timedelta
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
- import dns.asyncresolver
25
- import OpenSSL.crypto
26
- import structlog
27
- from rich.console import Console
28
- from rich.json import JSON
29
- from rich.table import Table
30
-
31
- logger = structlog.get_logger(__name__)
32
- console = Console()
33
-
34
- # Perfis de varredura
35
- PROFILES = {
36
- "quick": [21, 22, 23, 25, 53, 80, 110, 111, 135, 139, 143, 389, 443, 445,
37
- 465, 587, 993, 995, 1433, 1521, 2049, 3306, 3389, 5432, 5900, 6379,
38
- 8080, 8443, 9000, 10000, 27017],
39
- "web": [80, 443, 8080, 8443, 8000, 8888, 10443, 4443],
40
- "mail": [25, 110, 143, 465, 587, 993, 995],
41
- "db": [1433, 1521, 27017, 27018, 27019, 28017, 3306, 5000, 5432, 5984, 6379, 8081],
42
- "full": list(range(1, 1025)),
43
- "all": list(range(1, 65536)),
44
- }
45
-
46
- # URL base para atualizações de serviços e vulnerabilidades
47
- SERVICES_DB_URL = "https://raw.githubusercontent.com/nmap/nmap/master/nmap-services"
48
- VULN_DB_URL = "https://cve.mitre.org/data/downloads/allitems.csv"
49
-
50
- # Diretório para armazenar dados locais
51
- DATA_DIR = Path.home() / ".moriarty" / "data"
52
- SERVICES_DB = DATA_DIR / "services.yml"
53
- VULN_DB = DATA_DIR / "vulnerabilities.yml"
54
-
55
- # Garante que o diretório de dados existe
56
- DATA_DIR.mkdir(parents=True, exist_ok=True)
57
-
58
- # Estrutura para armazenar assinaturas de serviços
59
- @dataclass
60
- class ServiceSignature:
61
- name: str
62
- port: int
63
- protocol: str = "tcp"
64
- banner_patterns: List[str] = field(default_factory=list)
65
- ssl_ports: Set[int] = field(default_factory=set)
66
- version_pattern: Optional[str] = None
67
- cpe: Optional[str] = None
68
- vulns: List[Dict[str, str]] = field(default_factory=list)
69
- last_updated: Optional[datetime] = None
70
-
71
- # Dicionário para armazenar assinaturas de serviços
72
- SERVICE_SIGNATURES: Dict[str, ServiceSignature] = {}
73
-
74
- # Mapeamento de portas para serviços comuns
75
- SERVICE_MAP = {
76
- 21: "FTP",
77
- 22: "SSH",
78
- 23: "Telnet",
79
- 25: "SMTP",
80
- 53: "DNS",
81
- 80: "HTTP",
82
- 110: "POP3",
83
- 111: "RPCbind",
84
- 135: "MSRPC",
85
- 139: "NetBIOS",
86
- 143: "IMAP",
87
- 389: "LDAP",
88
- 443: "HTTPS",
89
- 445: "SMB",
90
- 465: "SMTPS",
91
- 500: "IKE",
92
- 515: "LPD",
93
- 554: "RTSP",
94
- 587: "SMTP (Submission)",
95
- 631: "IPP",
96
- 636: "LDAPS",
97
- 993: "IMAPS",
98
- 995: "POP3S",
99
- 1080: "SOCKS",
100
- 1194: "OpenVPN",
101
- 1433: "MSSQL",
102
- 1521: "Oracle",
103
- 2049: "NFS",
104
- 2375: "Docker",
105
- 2376: "Docker TLS",
106
- 3000: "Node.js",
107
- 3306: "MySQL",
108
- 3389: "RDP",
109
- 5000: "UPnP",
110
- 5432: "PostgreSQL",
111
- 5601: "Kibana",
112
- 5672: "AMQP",
113
- 5900: "VNC",
114
- 5984: "CouchDB",
115
- 6379: "Redis",
116
- 8000: "HTTP-Alt",
117
- 8008: "HTTP-Alt",
118
- 8080: "HTTP-Proxy",
119
- 8081: "HTTP-Alt",
120
- 8088: "HTTP-Alt",
121
- 8089: "Splunk",
122
- 8090: "HTTP-Alt",
123
- 8091: "Couchbase",
124
- 8096: "Plex",
125
- 8125: "StatsD",
126
- 8140: "Puppet",
127
- 8200: "Vault",
128
- 8300: "Consul",
129
- 8333: "Bitcoin",
130
- 8443: "HTTPS-Alt",
131
- 8500: "Consul",
132
- 8545: "Ethereum",
133
- 8765: "Grafana",
134
- 8888: "Jupyter",
135
- 9000: "SonarQube",
136
- 9001: "Tor",
137
- 9042: "Cassandra",
138
- 9090: "Prometheus",
139
- 9092: "Kafka",
140
- 9100: "Node-Exporter",
141
- 9200: "Elasticsearch",
142
- 9300: "Elasticsearch",
143
- 9418: "Git",
144
- 9999: "JIRA",
145
- 10000: "Webmin",
146
- 10250: "Kubelet",
147
- 11211: "Memcached",
148
- 15672: "RabbitMQ",
149
- 16379: "Redis",
150
- 27017: "MongoDB",
151
- 27018: "MongoDB",
152
- 27019: "MongoDB",
153
- 28017: "MongoDB",
154
- 32608: "Kubernetes"
155
- }
156
-
157
-
158
- # Vulnerabilidades comuns por serviço
159
- VULNERABILITIES = {
160
- "SSH": ["CVE-2016-0777", "CVE-2016-0778", "CVE-2018-15473"],
161
- "SMB": ["EternalBlue", "SMBGhost", "EternalRomance", "SambaCry"],
162
- "RDP": ["BlueKeep", "CVE-2019-0708", "CVE-2019-1181", "CVE-2019-1182"],
163
- "Redis": ["Unauthenticated Access", "CVE-2015-4335", "CVE-2016-8339"],
164
- "MongoDB": ["Unauthenticated Access", "CVE-2016-6494"],
165
- "Elasticsearch": ["CVE-2015-1427", "CVE-2015-3337", "CVE-2015-5531"],
166
- "Memcached": ["DRDoS Amplification", "CVE-2016-8704", "CVE-2016-8705"],
167
- "Docker": ["CVE-2019-5736", "CVE-2019-13139", "CVE-2019-14271"],
168
- "Kubernetes": ["CVE-2018-1002105", "CVE-2019-11253", "CVE-2019-11255"],
169
- "VNC": ["CVE-2006-2369", "CVE-2015-5239", "CVE-2018-20019"],
170
- "Jenkins": ["CVE-2017-1000353", "CVE-2018-1000861", "CVE-2019-1003000"],
171
- "MySQL": ["CVE-2016-6662", "CVE-2016-6663", "CVE-2016-6664"],
172
- "PostgreSQL": ["CVE-2019-9193", "CVE-2018-1058", "CVE-2016-5423"],
173
- "Oracle": ["CVE-2012-1675", "CVE-2012-3137", "CVE-2018-3110"],
174
- "MSSQL": ["CVE-2019-1068", "CVE-2018-8273", "CVE-2018-8271"],
175
- }
176
-
177
- @dataclass
178
- class ServiceInfo:
179
- """Informações detalhadas sobre um serviço."""
180
- name: str
181
- version: Optional[str] = None
182
- ssl: bool = False
183
- ssl_info: Optional[Dict[str, Any]] = None
184
- banner: Optional[str] = None
185
- vulns: List[Dict[str, str]] = field(default_factory=list)
186
- cpe: Optional[str] = None
187
- extra: Dict[str, Any] = field(default_factory=dict)
188
- confidence: float = 0.0 # Nível de confiança na identificação (0.0 a 1.0)
189
- last_checked: Optional[datetime] = None
190
-
191
- def to_dict(self) -> Dict[str, Any]:
192
- """Converte o objeto para dicionário."""
193
- return {
194
- "name": self.name,
195
- "version": self.version,
196
- "ssl": self.ssl,
197
- "ssl_info": self.ssl_info,
198
- "banner": self.banner,
199
- "vulns": self.vulns,
200
- "cpe": self.cpe,
201
- "confidence": self.confidence,
202
- "last_checked": self.last_checked.isoformat() if self.last_checked else None
203
- }
204
-
205
- @dataclass
206
- class PortScanResult:
207
- port: int
208
- protocol: str = "tcp"
209
- status: str = "open"
210
- target: Optional[str] = None
211
- service: Optional[ServiceInfo] = None
212
- banner: Optional[str] = None
213
- timestamp: str = field(default_factory=lambda: datetime.utcnow().isoformat())
214
-
215
- def to_dict(self) -> Dict[str, Any]:
216
- """Converte o resultado para dicionário."""
217
- result = {
218
- "port": self.port,
219
- "protocol": self.protocol,
220
- "status": self.status,
221
- "banner": self.banner,
222
- "timestamp": self.timestamp,
223
- }
224
-
225
- if self.service:
226
- service_info = {
227
- "name": self.service.name,
228
- "version": self.service.version,
229
- "ssl": self.service.ssl,
230
- "vulnerabilities": self.service.vulns,
231
- "cpe": self.service.cpe,
232
- "extra": self.service.extra,
233
- }
234
- if self.service.ssl_info:
235
- service_info["ssl_info"] = self.service.ssl_info
236
-
237
- result["service"] = service_info
238
-
239
- return result
240
-
241
- def to_json(self) -> str:
242
- """Retorna uma representação JSON do resultado."""
243
- return json.dumps(self.to_dict(), indent=2)
244
-
245
- class PortScanner:
246
- """Execução assíncrona de port scanning com detecção avançada de serviços."""
247
-
248
- def __init__(
249
- self,
250
- target: str,
251
- profile: str = "quick",
252
- concurrency: int = 200,
253
- timeout: float = 2.0,
254
- stealth_level: int = 0,
255
- resolve_services: bool = True,
256
- check_vulns: bool = True,
257
- ):
258
- self.target = target
259
- self.profile = profile if profile in PROFILES else "quick"
260
- self.stealth_level = max(0, min(stealth_level, 5))
261
- self.resolve_services = resolve_services
262
- self.check_vulns = check_vulns
263
-
264
- # Ajusta concorrência baseado no nível de stealth
265
- stealth_factors = [1.0, 0.8, 0.6, 0.4, 0.2, 0.1]
266
- adjusted_concurrency = int(concurrency * stealth_factors[self.stealth_level])
267
- self.concurrency = max(10, min(adjusted_concurrency, 500))
268
-
269
- # Ajusta timeout baseado no nível de stealth
270
- self.timeout = timeout * (1 + (self.stealth_level * 0.5))
271
-
272
- # Cache para serviços já identificados
273
- self.service_cache: Dict[int, ServiceInfo] = {}
274
-
275
- # Resolvedor DNS assíncrono
276
- self.resolver = dns.asyncresolver.Resolver()
277
- self.resolver.timeout = 2.0
278
- self.resolver.lifetime = 2.0
279
-
280
- async def scan(self) -> List[PortScanResult]:
281
- """Executa a varredura de portas."""
282
- console.print(f"[bold]Iniciando varredura em {self.target}[/bold]")
283
- console.print(f"Perfil: {self.profile}, Portas: {len(PROFILES[self.profile])}")
284
-
285
- ports = PROFILES[self.profile]
286
- if self.stealth_level > 0:
287
- # Aleatoriza a ordem das portas para maior discrição
288
- random.shuffle(ports)
289
-
290
- sem = asyncio.Semaphore(self.concurrency)
291
- results: List[PortScanResult] = []
292
-
293
- async def worker(port: int):
294
- async with sem:
295
- result = await self._probe_port(port)
296
- if result:
297
- results.append(result)
298
- self._print_result(result)
299
-
300
- # Executa os workers em paralelo
301
- tasks = [worker(port) for port in ports]
302
- await asyncio.gather(*tasks)
303
-
304
- # Ordena os resultados por número de porta
305
- results.sort(key=lambda r: r.port)
306
-
307
- return results
308
-
309
- def _print_result(self, result: PortScanResult):
310
- """Exibe o resultado formatado no console."""
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]"
313
-
314
- service_name = result.service.name if result.service else "unknown"
315
- service_info = f"[cyan]{service_name}[/cyan]"
316
-
317
- if result.service and result.service.version:
318
- service_info += f" [yellow]{result.service.version}[/yellow]"
319
-
320
- if result.service and result.service.ssl:
321
- service_info += " [green]🔒[/green]"
322
-
323
- if result.service and result.service.vulns:
324
- vuln_count = len(result.service.vulns)
325
- service_info += f" [red]({vuln_count} vulns)[/red]"
326
-
327
- console.print(f"{port_info} {status} {service_info}")
328
-
329
- if result.banner:
330
- console.print(f" [dim]Banner: {result.banner[:100]}{'...' if len(result.banner) > 100 else ''}[/dim]")
331
-
332
- async def _probe_port(self, port: int) -> Optional[PortScanResult]:
333
- """Verifica se uma porta está aberta e coleta informações do serviço."""
334
- # Atraso aleatório para evitar detecção
335
- if self.stealth_level > 0:
336
- await asyncio.sleep(random.uniform(0.01, 0.5) * self.stealth_level)
337
-
338
- # Tenta conexão TCP
339
- try:
340
- # Usa um timeout menor para a conexão inicial
341
- conn_timeout = min(1.0, self.timeout)
342
- reader, writer = await asyncio.wait_for(
343
- asyncio.open_connection(self.target, port),
344
- timeout=conn_timeout
345
- )
346
-
347
- # Se chegou aqui, a porta está aberta
348
- result = PortScanResult(port=port, status="open", target=self.target)
349
-
350
- # Tenta obter o banner do serviço
351
- try:
352
- # Configura timeout para leitura
353
- read_timeout = max(1.0, self.timeout - 1.0)
354
-
355
- # Envia uma requisição específica baseada no serviço comum da porta
356
- banner_bytes = b""
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)
378
-
379
- if banner_bytes:
380
- # Tenta decodificar como texto
381
- try:
382
- banner = banner_bytes.decode('utf-8', errors='replace').strip()
383
- result.banner = banner
384
-
385
- # Tenta identificar o serviço pelo banner
386
- service_info = await self._identify_service(port, banner)
387
- if service_info:
388
- result.service = service_info
389
- except UnicodeDecodeError:
390
- # Se não for texto, mostra como hexdump
391
- result.banner = banner_bytes.hex(' ', 1)
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
426
-
427
- # Verifica vulnerabilidades conhecidas para portas abertas
428
- if result.status == "open" and self.check_vulns and result.service:
429
- result.service.vulns = self._check_known_vulns(port, result.service.name)
430
-
431
- return result
432
-
433
- except (asyncio.TimeoutError, ConnectionRefusedError, OSError):
434
- # Porta fechada ou inacessível
435
- return None
436
-
437
- except Exception as e:
438
- logger.error(f"Erro ao verificar porta {port}: {str(e)}")
439
- return None
440
-
441
- finally:
442
- # Fecha a conexão se ainda estiver aberta
443
- if 'writer' in locals():
444
- writer.close()
445
- try:
446
- await writer.wait_closed()
447
- except:
448
- pass
449
-
450
- async def _identify_service(self, port: int, banner: str) -> Optional[ServiceInfo]:
451
- """Tenta identificar o serviço rodando na porta com base no banner."""
452
- if not banner:
453
- return None
454
-
455
- banner_lower = banner.lower()
456
- service = ServiceInfo(name="unknown")
457
-
458
- # Verifica por padrões comuns de banners
459
- if "apache" in banner_lower or "httpd" in banner_lower:
460
- service.name = "Apache HTTP Server"
461
- if match := re.search(r'Apache[/\s]([0-9.]+)', banner, re.IGNORECASE):
462
- service.version = match.group(1)
463
- service.cpe = f"cpe:/a:apache:http_server:{service.version}"
464
-
465
- elif "nginx" in banner_lower:
466
- service.name = "Nginx"
467
- if match := re.search(r'nginx[/\s]([0-9.]+)', banner, re.IGNORECASE):
468
- service.version = match.group(1)
469
- service.cpe = f"cpe:/a:nginx:nginx:{service.version}"
470
-
471
- elif "microsoft-iis" in banner_lower or "microsoft httpapi" in banner_lower:
472
- service.name = "Microsoft IIS"
473
- if match := re.search(r'Microsoft-IIS/([0-9.]+)', banner, re.IGNORECASE):
474
- service.version = match.group(1)
475
- service.cpe = f"cpe:/a:microsoft:iis:{service.version}"
476
-
477
- elif "openbsd openssh" in banner_lower or "openssh" in banner_lower:
478
- service.name = "OpenSSH"
479
- if match := re.search(r'openssh[_-]?([0-9.]+[a-z]*)', banner, re.IGNORECASE):
480
- service.version = match.group(1)
481
- service.cpe = f"cpe:/a:openbsd:openssh:{service.version}"
482
-
483
- elif "postfix" in banner_lower:
484
- service.name = "Postfix"
485
- if match := re.search(r'postfix[\s(]([0-9.]+)', banner, re.IGNORECASE):
486
- service.version = match.group(1)
487
- service.cpe = f"cpe:/a:postfix:postfix:{service.version}"
488
-
489
- elif "exim" in banner_lower:
490
- service.name = "Exim"
491
- if match := re.search(r'exim[\s(]([0-9.]+)', banner, re.IGNORECASE):
492
- service.version = match.group(1)
493
- service.cpe = f"cpe:/a:exim:exim:{service.version}"
494
-
495
- elif "dovecot" in banner_lower:
496
- service.name = "Dovecot"
497
- if match := re.search(r'dovecot[\s(]([0-9.]+)', banner, re.IGNORECASE):
498
- service.version = match.group(1)
499
- service.cpe = f"cpe:/a:dovecot:dovecot:{service.version}"
500
-
501
- elif "proftpd" in banner_lower:
502
- service.name = "ProFTPD"
503
- if match := re.search(r'proftpd[\s(]([0-9.]+)', banner, re.IGNORECASE):
504
- service.version = match.group(1)
505
- service.cpe = f"cpe:/a:proftpd:proftpd:{service.version}"
506
-
507
- elif "vsftpd" in banner_lower:
508
- service.name = "vsFTPd"
509
- if match := re.search(r'vsftpd[\s(]([0-9.]+)', banner, re.IGNORECASE):
510
- service.version = match.group(1)
511
- service.cpe = f"cpe:/a:vsftpd:vsftpd:{service.version}"
512
-
513
- elif "mysql" in banner_lower:
514
- service.name = "MySQL"
515
- if match := re.search(r'([0-9.]+)[- ]*mysql', banner, re.IGNORECASE):
516
- service.version = match.group(1)
517
- service.cpe = f"cpe:/a:mysql:mysql:{service.version}"
518
-
519
- elif "postgresql" in banner_lower or 'postgres' in banner_lower:
520
- service.name = "PostgreSQL"
521
- if match := re.search(r'postgresql[\s(]([0-9.]+)', banner, re.IGNORECASE):
522
- service.version = match.group(1)
523
- service.cpe = f"cpe:/a:postgresql:postgresql:{service.version}"
524
-
525
- elif "redis" in banner_lower:
526
- service.name = "Redis"
527
- if match := re.search(r'redis[\s:]([0-9.]+)', banner, re.IGNORECASE):
528
- service.version = match.group(1)
529
- service.cpe = f"cpe:/a:redis:redis:{service.version}"
530
-
531
- elif "mongodb" in banner_lower:
532
- service.name = "MongoDB"
533
- if match := re.search(r'mongod?b[\s(]([0-9.]+)', banner, re.IGNORECASE):
534
- service.version = match.group(1)
535
- service.cpe = f"cpe:/a:mongodb:mongodb:{service.version}"
536
-
537
- elif "microsoft sql server" in banner_lower or "sql server" in banner_lower:
538
- service.name = "Microsoft SQL Server"
539
- if match := re.search(r'sql server[\s(]([0-9.]+)', banner, re.IGNORECASE):
540
- service.version = match.group(1)
541
- service.cpe = f"cpe:/a:microsoft:sql_server:{service.version}"
542
-
543
- elif "oracle" in banner_lower and "database" in banner_lower:
544
- service.name = "Oracle Database"
545
- if match := re.search(r'oracle[\s(]([0-9.]+)', banner, re.IGNORECASE):
546
- service.version = match.group(1)
547
- service.cpe = f"cpe:/a:oracle:database:{service.version}"
548
-
549
- # Se não identificou pelo banner, tenta pela porta
550
- if service.name == "unknown" and port in SERVICE_MAP:
551
- service.name = SERVICE_MAP[port]
552
-
553
- # Verifica vulnerabilidades conhecidas
554
- if self.check_vulns:
555
- service.vulns = self._check_known_vulns(port, service.name)
556
-
557
- return service if service.name != "unknown" else None
558
-
559
- def _identify_ssl_service(self, port: int, ssl_info: Dict[str, Any]) -> str:
560
- """Tenta identificar o serviço baseado na porta e informações SSL."""
561
- # Mapeia portas comuns para serviços SSL
562
- ssl_services = {
563
- 443: "HTTPS",
564
- 465: "SMTPS",
565
- 563: "NNTPS",
566
- 636: "LDAPS",
567
- 853: "DNS-over-TLS",
568
- 989: "FTPS (data)",
569
- 990: "FTPS (control)",
570
- 992: "Telnet over TLS/SSL",
571
- 993: "IMAPS",
572
- 994: "IRC over SSL",
573
- 995: "POP3S",
574
- 1443: "HTTPS (alt)",
575
- 2376: "Docker TLS",
576
- 2377: "Docker Swarm",
577
- 3001: "HTTPS (Node.js)",
578
- 3306: "MySQL over SSL",
579
- 3389: "RDP over TLS",
580
- 4000: "HTTPS (alt)",
581
- 4001: "HTTPS (alt)",
582
- 4002: "HTTPS (alt)",
583
- 4003: "HTTPS (alt)",
584
- 4004: "HTTPS (alt)",
585
- 4005: "HTTPS (alt)",
586
- 4006: "HTTPS (alt)",
587
- 4007: "HTTPS (alt)",
588
- 4008: "HTTPS (alt)",
589
- 4009: "HTTPS (alt)",
590
- 4433: "HTTPS (alt)",
591
- 4443: "HTTPS (alt)",
592
- 5000: "HTTPS (alt)",
593
- 5001: "HTTPS (alt)",
594
- 5002: "HTTPS (alt)",
595
- 5003: "HTTPS (alt)",
596
- 5004: "HTTPS (alt)",
597
- 5005: "HTTPS (alt)",
598
- 5006: "HTTPS (alt)",
599
- 5007: "HTTPS (alt)",
600
- 5008: "HTTPS (alt)",
601
- 5009: "HTTPS (alt)",
602
- 5432: "PostgreSQL over SSL",
603
- 5671: "AMQPS",
604
- 5800: "VNC over TLS",
605
- 5901: "VNC over TLS (alt)",
606
- 6001: "HTTPS (alt)",
607
- 6002: "HTTPS (alt)",
608
- 6003: "HTTPS (alt)",
609
- 6004: "HTTPS (alt)",
610
- 6005: "HTTPS (alt)",
611
- 6006: "HTTPS (alt)",
612
- 6007: "HTTPS (alt)",
613
- 6008: "HTTPS (alt)",
614
- 6009: "HTTPS (alt)",
615
- 7000: "HTTPS (alt)",
616
- 7001: "HTTPS (alt)",
617
- 7002: "HTTPS (alt)",
618
- 7003: "HTTPS (alt)",
619
- 7004: "HTTPS (alt)",
620
- 7005: "HTTPS (alt)",
621
- 7006: "HTTPS (alt)",
622
- 7007: "HTTPS (alt)",
623
- 7008: "HTTPS (alt)",
624
- 7009: "HTTPS (alt)",
625
- 8000: "HTTPS (alt)",
626
- 8001: "HTTPS (alt)",
627
- 8002: "HTTPS (alt)",
628
- 8003: "HTTPS (alt)",
629
- 8004: "HTTPS (alt)",
630
- 8005: "HTTPS (alt)",
631
- 8006: "HTTPS (alt)",
632
- 8007: "HTTPS (alt)",
633
- 8008: "HTTPS (alt)",
634
- 8009: "HTTPS (alt)",
635
- 8080: "HTTPS (alt)",
636
- 8081: "HTTPS (alt)",
637
- 8082: "HTTPS (alt)",
638
- 8083: "HTTPS (alt)",
639
- 8084: "HTTPS (alt)",
640
- 8085: "HTTPS (alt)",
641
- 8086: "HTTPS (alt)",
642
- 8087: "HTTPS (alt)",
643
- 8088: "HTTPS (alt)",
644
- 8089: "HTTPS (alt)",
645
- 8090: "HTTPS (alt)",
646
- 8091: "HTTPS (alt)",
647
- 8443: "HTTPS (alt)",
648
- 8444: "HTTPS (alt)",
649
- 8445: "HTTPS (alt)",
650
- 8446: "HTTPS (alt)",
651
- 8447: "HTTPS (alt)",
652
- 8448: "HTTPS (alt)",
653
- 8449: "HTTPS (alt)",
654
- 9000: "HTTPS (alt)",
655
- 9001: "HTTPS (alt)",
656
- 9002: "HTTPS (alt)",
657
- 9003: "HTTPS (alt)",
658
- 9004: "HTTPS (alt)",
659
- 9005: "HTTPS (alt)",
660
- 9006: "HTTPS (alt)",
661
- 9007: "HTTPS (alt)",
662
- 9008: "HTTPS (alt)",
663
- 9009: "HTTPS (alt)",
664
- 9010: "HTTPS (alt)",
665
- 9443: "HTTPS (alt)",
666
- 10000: "HTTPS (alt)",
667
- 10443: "HTTPS (alt)",
668
- 18080: "HTTPS (alt)",
669
- 18081: "HTTPS (alt)",
670
- 18082: "HTTPS (alt)",
671
- 18083: "HTTPS (alt)",
672
- 18084: "HTTPS (alt)",
673
- 18085: "HTTPS (alt)",
674
- 18086: "HTTPS (alt)",
675
- 18087: "HTTPS (alt)",
676
- 18088: "HTTPS (alt)",
677
- 18089: "HTTPS (alt)",
678
- 20000: "HTTPS (alt)",
679
- 27017: "MongoDB over SSL",
680
- 27018: "MongoDB over SSL (alt)",
681
- 27019: "MongoDB over SSL (alt)",
682
- 28017: "MongoDB over SSL (alt)",
683
- 30000: "HTTPS (alt)",
684
- 30001: "HTTPS (alt)",
685
- 30002: "HTTPS (alt)",
686
- 30003: "HTTPS (alt)",
687
- 30004: "HTTPS (alt)",
688
- 30005: "HTTPS (alt)",
689
- 30006: "HTTPS (alt)",
690
- 30007: "HTTPS (alt)",
691
- 30008: "HTTPS (alt)",
692
- 30009: "HTTPS (alt)",
693
- 30010: "HTTPS (alt)",
694
- 40000: "HTTPS (alt)",
695
- 40001: "HTTPS (alt)",
696
- 40002: "HTTPS (alt)",
697
- 40003: "HTTPS (alt)",
698
- 40004: "HTTPS (alt)",
699
- 40005: "HTTPS (alt)",
700
- 40006: "HTTPS (alt)",
701
- 40007: "HTTPS (alt)",
702
- 40008: "HTTPS (alt)",
703
- 40009: "HTTPS (alt)",
704
- 40010: "HTTPS (alt)",
705
- 50000: "HTTPS (alt)",
706
- 50001: "HTTPS (alt)",
707
- 50002: "HTTPS (alt)",
708
- 50003: "HTTPS (alt)",
709
- 50004: "HTTPS (alt)",
710
- 50005: "HTTPS (alt)",
711
- 50006: "HTTPS (alt)",
712
- 50007: "HTTPS (alt)",
713
- 50008: "HTTPS (alt)",
714
- 50009: "HTTPS (alt)",
715
- 50010: "HTTPS (alt)",
716
- 60000: "HTTPS (alt)",
717
- 60001: "HTTPS (alt)",
718
- 60002: "HTTPS (alt)",
719
- 60003: "HTTPS (alt)",
720
- 60004: "HTTPS (alt)",
721
- 60005: "HTTPS (alt)",
722
- 60006: "HTTPS (alt)",
723
- 60007: "HTTPS (alt)",
724
- 60008: "HTTPS (alt)",
725
- 60009: "HTTPS (alt)",
726
- 60010: "HTTPS (alt)",
727
- }
728
-
729
- return ssl_services.get(port, "SSL Service")
730
-
731
- async def _check_ssl(self, port: int) -> Optional[Dict[str, Any]]:
732
- """Verifica informações SSL/TLS da porta."""
733
- ssl_info = {}
734
-
735
- try:
736
- # Cria um contexto SSL
737
- ssl_context = ssl.create_default_context()
738
- ssl_context.check_hostname = False
739
- ssl_context.verify_mode = ssl.CERT_NONE
740
-
741
- # Tenta conectar com SSL/TLS
742
- reader, writer = await asyncio.wait_for(
743
- asyncio.open_connection(
744
- self.target,
745
- port,
746
- ssl=ssl_context,
747
- server_hostname=self.target
748
- ),
749
- timeout=self.timeout
750
- )
751
-
752
- # Obtém o certificado
753
- ssl_object = writer.get_extra_info('ssl_object')
754
- if ssl_object and hasattr(ssl_object, 'getpeercert'):
755
- cert = ssl_object.getpeercert()
756
-
757
- # Extrai informações do certificado
758
- if cert:
759
- ssl_info = {
760
- 'version': ssl_object.version(),
761
- 'cipher': ssl_object.cipher(),
762
- 'compression': ssl_object.compression(),
763
- 'issuer': dict(x[0] for x in cert.get('issuer', [])),
764
- 'subject': dict(x[0] for x in cert.get('subject', [])),
765
- 'not_before': cert.get('notBefore'),
766
- 'not_after': cert.get('notAfter'),
767
- 'serial_number': cert.get('serialNumber'),
768
- 'subject_alt_name': [
769
- name[1] for name in cert.get('subjectAltName', [])
770
- if name[0] == 'DNS'
771
- ],
772
- 'ocsp': cert.get('OCSP', []),
773
- 'ca_issuers': cert.get('caIssuers', []),
774
- 'crl_distribution_points': cert.get('crlDistributionPoints', []),
775
- }
776
-
777
- # Verifica se o certificado está expirado
778
- from datetime import datetime
779
- now = datetime.utcnow()
780
- not_after = datetime.strptime(ssl_info['not_after'], '%b %d %H:%M:%S %Y %Z')
781
- ssl_info['expired'] = now > not_after
782
-
783
- # Verifica se o certificado é auto-assinado
784
- ssl_info['self_signed'] = (
785
- ssl_info['issuer'] == ssl_info['subject']
786
- and ssl_info['issuer'].get('organizationName', '').lower() != 'let\'s encrypt'
787
- )
788
-
789
- # Verifica se o certificado é válido para o domínio
790
- import idna
791
- from socket import gethostbyname
792
-
793
- try:
794
- hostname = idna.encode(self.target).decode('ascii')
795
- ip = gethostbyname(hostname)
796
-
797
- # Verifica se o IP está nos subjectAltNames
798
- alt_names = []
799
- for name in ssl_info.get('subject_alt_name', []):
800
- if name.startswith('*'):
801
- # Lida com wildcards básicos
802
- domain = name[2:] # Remove o *.
803
- if hostname.endswith(domain):
804
- alt_names.append(hostname)
805
- else:
806
- alt_names.append(name)
807
-
808
- ssl_info['valid_hostname'] = (
809
- hostname in alt_names or
810
- f'*.{hostname.split(".", 1)[1]}' in alt_names
811
- )
812
-
813
- # Verifica se o IP está nos subjectAltNames
814
- ssl_info['valid_ip'] = ip in alt_names
815
-
816
- except (UnicodeError, IndexError, OSError):
817
- ssl_info['valid_hostname'] = False
818
- ssl_info['valid_ip'] = False
819
-
820
- return ssl_info
821
-
822
- except (ssl.SSLError, asyncio.TimeoutError, ConnectionRefusedError, OSError) as e:
823
- logger.debug(f"Erro ao verificar SSL na porta {port}: {str(e)}")
824
- return None
825
-
826
- except Exception as e:
827
- logger.error(f"Erro inesperado ao verificar SSL na porta {port}: {str(e)}", exc_info=True)
828
- return None
829
-
830
- finally:
831
- if 'writer' in locals():
832
- writer.close()
833
- try:
834
- await writer.wait_closed()
835
- except:
836
- pass
837
-
838
- def _check_known_vulns(self, port: int, service_name: str) -> List[str]:
839
- """Verifica vulnerabilidades conhecidas para o serviço na porta."""
840
- vulns = []
841
-
842
- # Verifica vulnerabilidades específicas do serviço
843
- for service, cves in VULNERABILITIES.items():
844
- if service.lower() in service_name.lower():
845
- vulns.extend(cves)
846
-
847
- # Verifica vulnerabilidades específicas da porta
848
- if port == 22: # SSH
849
- vulns.extend(["CVE-2016-0777", "CVE-2016-0778", "CVE-2018-15473"])
850
- elif port == 445: # SMB
851
- vulns.extend(["EternalBlue", "SMBGhost", "EternalRomance", "SambaCry"])
852
- elif port == 3389: # RDP
853
- vulns.extend(["BlueKeep", "CVE-2019-0708", "CVE-2019-1181", "CVE-2019-1182"])
854
- elif port == 27017: # MongoDB
855
- vulns.extend(["Unauthenticated Access", "CVE-2016-6494"])
856
- elif port == 9200: # Elasticsearch
857
- vulns.extend(["CVE-2015-1427", "CVE-2015-3337", "CVE-2015-5531"])
858
- elif port == 11211: # Memcached
859
- vulns.extend(["DRDoS Amplification", "CVE-2016-8704", "CVE-2016-8705"])
860
- elif port == 2375: # Docker
861
- vulns.extend(["CVE-2019-5736", "CVE-2019-13139", "CVE-2019-14271"])
862
- elif port == 10250: # Kubelet
863
- vulns.extend(["CVE-2018-1002105", "CVE-2019-11253", "CVE-2019-11255"])
864
-
865
- return list(set(vulns)) # Remove duplicatas
866
-
867
-
868
- def format_scan_results(results: List[PortScanResult], output_format: str = "text") -> str:
869
- """Formata os resultados da varredura no formato solicitado."""
870
- if output_format.lower() == "json":
871
- return json.dumps([r.to_dict() for r in results], indent=2)
872
-
873
- # Formato de texto para saída no console
874
- output = []
875
- output.append("")
876
- output.append(f"[bold]Resultado da varredura de portas[/bold]")
877
- output.append(f"Alvo: {results[0].target if results else 'N/A'}")
878
- output.append(f"Portas verificadas: {len(results)}")
879
- output.append("-" * 80)
880
-
881
- # Cabeçalho da tabela
882
- output.append(
883
- f"{'PORTA':<8} {'PROTOCOLO':<10} {'STATUS':<10} {'SERVIÇO':<25} {'VULNERABILIDADES'}"
884
- )
885
- output.append("-" * 80)
886
-
887
- # Linhas da tabela
888
- for result in results:
889
- if result.status == "open":
890
- port = f"[green]{result.port}[/green]"
891
- status = "[green]ABERTA[/green]"
892
- else:
893
- port = f"[yellow]{result.port}[/yellow]"
894
- status = "[yellow]FILTRADA[/yellow]"
895
-
896
- service = result.service.name if result.service else "desconhecido"
897
- version = f" {result.service.version}" if result.service and result.service.version else ""
898
- service_info = f"{service}{version}"
899
-
900
- if result.service and result.service.ssl:
901
- service_info += " 🔒"
902
-
903
- vulns = ", ".join(result.service.vulns) if result.service and result.service.vulns else "-"
904
-
905
- output.append(
906
- f"{port:<8} {'tcp':<10} {status:<10} {service_info:<25} {vulns}"
907
- )
908
-
909
- # Resumo
910
- open_ports = [r for r in results if r.status == "open"]
911
- output.append("-" * 80)
912
- output.append(f"Total de portas abertas: {len(open_ports)}")
913
-
914
- # Conta serviços por tipo
915
- services = {}
916
- for result in open_ports:
917
- if result.service:
918
- service_name = result.service.name
919
- services[service_name] = services.get(service_name, 0) + 1
920
-
921
- if services:
922
- output.append("\nServiços identificados:")
923
- for service, count in sorted(services.items()):
924
- output.append(f" - {service}: {count} porta{'s' if count > 1 else ''}")
925
-
926
- # Verifica vulnerabilidades críticas
927
- critical_vulns = []
928
- for result in open_ports:
929
- if result.service and result.service.vulns:
930
- for vuln in result.service.vulns:
931
- if any(cve in vuln.upper() for cve in ["CVE", "MS"]):
932
- critical_vulns.append((result.port, vuln))
933
-
934
- if critical_vulns:
935
- output.append("\n[bold red]VULNERABILIDADES CRÍTICAS ENCONTRADAS:[/bold red]")
936
- for port, vuln in critical_vulns:
937
- output.append(f" - Porta {port}: {vuln}")
938
-
939
- return "\n".join(output)
940
-
941
-
942
- __all__ = ["PortScanner", "PortScanResult", "format_scan_results"]