vamp-cloud-enum 1.1__tar.gz → 1.3__tar.gz

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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vamp-cloud-enum
3
- Version: 1.1
3
+ Version: 1.3
4
4
  Summary: Cloud asset enumeration and exposure analysis for authorized audits
5
5
  Author-email: VampSecure Studios <contact@vampsecurestudios.com>
6
6
  License: MIT
@@ -21,8 +21,9 @@ Requires-Dist: aiohttp>=3.9.0
21
21
  Requires-Dist: rich>=13.7.0
22
22
  Dynamic: license-file
23
23
 
24
+ <!-- © VampSecure Studios — VampSecure Labs Security Research Division -->
24
25
  <p align="center">
25
- <img src="https://img.shields.io/badge/version-1.0-crimson?style=flat-square" />
26
+ <img src="https://img.shields.io/badge/version-1.1-crimson?style=flat-square" />
26
27
  <img src="https://img.shields.io/badge/python-3.11+-blue?style=flat-square&logo=python&logoColor=white" />
27
28
  <img src="https://img.shields.io/badge/providers-AWS%20%7C%20Azure%20%7C%20GCP-teal?style=flat-square" />
28
29
  <img src="https://img.shields.io/badge/VampSecure_Labs-Security_Research-8b0000?style=flat-square" />
@@ -71,6 +72,13 @@ pip install -r requirements.txt
71
72
 
72
73
  ## Installation
73
74
 
75
+
76
+ ```bash
77
+ pip install vamp-cloud-enum
78
+ # o con Homebrew:
79
+ brew install vampsecure-labs/labs/vamp-cloud-enum
80
+ ```
81
+
74
82
  ```bash
75
83
  git clone https://github.com/belky-me/vamp-cloud-enum.git
76
84
  cd vamp-cloud-enum
@@ -188,3 +196,8 @@ python vamp_cloud_enum.py -d example.com -d example-cdn.com \
188
196
  © VampSecure Studios — VampSecure Labs Security Research Division<br/>
189
197
  For authorized security assessments only. Unauthorized use is prohibited.
190
198
  </p>
199
+
200
+ ---
201
+
202
+ ## Versión
203
+ v1.1 — VampSecure Labs Security Research Division
@@ -1,5 +1,6 @@
1
+ <!-- © VampSecure Studios — VampSecure Labs Security Research Division -->
1
2
  <p align="center">
2
- <img src="https://img.shields.io/badge/version-1.0-crimson?style=flat-square" />
3
+ <img src="https://img.shields.io/badge/version-1.1-crimson?style=flat-square" />
3
4
  <img src="https://img.shields.io/badge/python-3.11+-blue?style=flat-square&logo=python&logoColor=white" />
4
5
  <img src="https://img.shields.io/badge/providers-AWS%20%7C%20Azure%20%7C%20GCP-teal?style=flat-square" />
5
6
  <img src="https://img.shields.io/badge/VampSecure_Labs-Security_Research-8b0000?style=flat-square" />
@@ -48,6 +49,13 @@ pip install -r requirements.txt
48
49
 
49
50
  ## Installation
50
51
 
52
+
53
+ ```bash
54
+ pip install vamp-cloud-enum
55
+ # o con Homebrew:
56
+ brew install vampsecure-labs/labs/vamp-cloud-enum
57
+ ```
58
+
51
59
  ```bash
52
60
  git clone https://github.com/belky-me/vamp-cloud-enum.git
53
61
  cd vamp-cloud-enum
@@ -165,3 +173,8 @@ python vamp_cloud_enum.py -d example.com -d example-cdn.com \
165
173
  © VampSecure Studios — VampSecure Labs Security Research Division<br/>
166
174
  For authorized security assessments only. Unauthorized use is prohibited.
167
175
  </p>
176
+
177
+ ---
178
+
179
+ ## Versión
180
+ v1.1 — VampSecure Labs Security Research Division
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "vamp-cloud-enum"
7
- version = "1.1"
7
+ version = "1.3"
8
8
  description = "Cloud asset enumeration and exposure analysis for authorized audits"
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vamp-cloud-enum
3
- Version: 1.1
3
+ Version: 1.3
4
4
  Summary: Cloud asset enumeration and exposure analysis for authorized audits
5
5
  Author-email: VampSecure Studios <contact@vampsecurestudios.com>
6
6
  License: MIT
@@ -21,8 +21,9 @@ Requires-Dist: aiohttp>=3.9.0
21
21
  Requires-Dist: rich>=13.7.0
22
22
  Dynamic: license-file
23
23
 
24
+ <!-- © VampSecure Studios — VampSecure Labs Security Research Division -->
24
25
  <p align="center">
25
- <img src="https://img.shields.io/badge/version-1.0-crimson?style=flat-square" />
26
+ <img src="https://img.shields.io/badge/version-1.1-crimson?style=flat-square" />
26
27
  <img src="https://img.shields.io/badge/python-3.11+-blue?style=flat-square&logo=python&logoColor=white" />
27
28
  <img src="https://img.shields.io/badge/providers-AWS%20%7C%20Azure%20%7C%20GCP-teal?style=flat-square" />
28
29
  <img src="https://img.shields.io/badge/VampSecure_Labs-Security_Research-8b0000?style=flat-square" />
@@ -71,6 +72,13 @@ pip install -r requirements.txt
71
72
 
72
73
  ## Installation
73
74
 
75
+
76
+ ```bash
77
+ pip install vamp-cloud-enum
78
+ # o con Homebrew:
79
+ brew install vampsecure-labs/labs/vamp-cloud-enum
80
+ ```
81
+
74
82
  ```bash
75
83
  git clone https://github.com/belky-me/vamp-cloud-enum.git
76
84
  cd vamp-cloud-enum
@@ -188,3 +196,8 @@ python vamp_cloud_enum.py -d example.com -d example-cdn.com \
188
196
  © VampSecure Studios — VampSecure Labs Security Research Division<br/>
189
197
  For authorized security assessments only. Unauthorized use is prohibited.
190
198
  </p>
199
+
200
+ ---
201
+
202
+ ## Versión
203
+ v1.1 — VampSecure Labs Security Research Division
@@ -99,19 +99,19 @@ from vampsec_report import (
99
99
  # CONSTANTES Y CONFIGURACIÓN
100
100
  # =============================================================================
101
101
 
102
- VERSION = "1.1"
102
+ VERSION = "1.3"
103
103
  TOOL_NAME = "vamp-cloud-enum"
104
104
  AUTHOR = "© VampSecure Studios — VampSecure Labs Security Research Division"
105
105
 
106
106
  console = Console()
107
107
 
108
108
  BANNER = r"""
109
- __ ___ __ __ ___ ___ ___ ___ _ _ ___ ___ _ _ ___ ___
109
+ __ ___ __ __ ___ ___ ___ ___ _ _ ___ ___ _ _ ___ ___
110
110
  \ \ / /_\ | \/ | _ \/ __| __/ __| | | | _ \ __| | /_\ | _ ) __|
111
111
  \ V / _ \| |\/| | _/\__ \ _| (__| |_| | / _|| |__ / _ \| _ \__ \
112
112
  \_/_/ \_\_| |_|_| |___/___\___|\___/|_|_\___|____/_/ \_\___/___/
113
113
  by Antonio Hernandez "Belky" — VampSecure Studios
114
- vamp-cloud-enum v1.1 · Cloud Bucket Enumerator
114
+ vamp-cloud-enum v1.3 · Cloud Bucket Enumerator
115
115
  ────────────────────────────────────────────────────────────────────────
116
116
  USO EXCLUSIVO EN AUDITORÍAS AUTORIZADAS · El uso no autorizado es ilegal
117
117
  """
@@ -1437,6 +1437,17 @@ def parse_args() -> argparse.Namespace:
1437
1437
  "(acceso público, listado de contenedores) — v1.1"
1438
1438
  ),
1439
1439
  )
1440
+ p.add_argument(
1441
+ "--check-functions",
1442
+ action="store_true",
1443
+ dest="check_functions",
1444
+ help=(
1445
+ "Comprobar exposición de funciones serverless: AWS Lambda function URLs, "
1446
+ "Azure Function Apps y GCP Cloud Functions. Genera muchas peticiones "
1447
+ "(usa con precaución). Hallazgos: AWS_LAMBDA_URL_EXPOSED, "
1448
+ "AZURE_FUNCTION_EXPOSED, GCP-FUNC-001 — v1.3"
1449
+ ),
1450
+ )
1440
1451
 
1441
1452
  add_report_args(p)
1442
1453
  return p.parse_args()
@@ -1470,6 +1481,324 @@ def _load_wordlist(path: str) -> List[str]:
1470
1481
  return []
1471
1482
 
1472
1483
 
1484
+ # =============================================================================
1485
+ # COMPROBACIÓN DE EXPOSICIÓN DE FUNCIONES SERVERLESS (v1.2)
1486
+ # =============================================================================
1487
+
1488
+ # Regiones AWS más comunes para Lambda function URLs
1489
+ _AWS_LAMBDA_REGIONES: List[str] = [
1490
+ "us-east-1", "us-east-2", "us-west-1", "us-west-2",
1491
+ "eu-west-1", "eu-west-2", "eu-central-1",
1492
+ "ap-southeast-1", "ap-southeast-2", "ap-northeast-1",
1493
+ ]
1494
+
1495
+ # Funciones Azure habituales a probar (variantes del nombre del endpoint)
1496
+ _AZURE_FUNC_NOMBRES: List[str] = [
1497
+ "api", "function", "func", "http", "trigger", "webhook",
1498
+ "process", "handler", "main", "app",
1499
+ ]
1500
+
1501
+
1502
+ def _generar_candidatos_lambda(dominios: List[str]) -> List[str]:
1503
+ """
1504
+ Genera candidatos para el ID de Lambda function URL usando partes del dominio.
1505
+
1506
+ Los function URL IDs de AWS Lambda son cadenas de 26 caracteres aleatorias
1507
+ y no se pueden predecir. Esta función genera candidatos basados en las partes
1508
+ del dominio para detectar funciones con nombres personalizados o patrones
1509
+ predecibles que algunas organizaciones configuran.
1510
+
1511
+ Retorna lista de candidatos únicos (en minúsculas, solo alfanuméricos y guiones).
1512
+ """
1513
+ candidatos: set = set()
1514
+ for dominio in dominios:
1515
+ # Extraer parte principal del dominio (sin TLD)
1516
+ partes = dominio.rstrip(".").split(".")
1517
+ # Usar el nombre del dominio sin TLD y sin subdominios secundarios
1518
+ if len(partes) >= 2:
1519
+ nombre = partes[-2]
1520
+ else:
1521
+ nombre = partes[0]
1522
+ # Normalizar: solo alfanuméricos y guiones
1523
+ import re as _re
1524
+ nombre_norm = _re.sub(r"[^a-z0-9\-]", "-", nombre.lower())
1525
+ nombre_norm = _re.sub(r"-+", "-", nombre_norm).strip("-")
1526
+ if not nombre_norm:
1527
+ continue
1528
+
1529
+ candidatos.add(nombre_norm)
1530
+ # Variantes comunes
1531
+ for sfx in ("api", "func", "lambda", "fn", "app", "service", "svc", "handler"):
1532
+ candidatos.add(f"{nombre_norm}-{sfx}")
1533
+ candidatos.add(f"{sfx}-{nombre_norm}")
1534
+
1535
+ return list(candidatos)
1536
+
1537
+
1538
+ def _check_lambda_function_urls(
1539
+ dominios: List[str],
1540
+ findings: List[BucketResult],
1541
+ ) -> None:
1542
+ """
1543
+ Prueba URLs de funciones Lambda AWS en busca de endpoints públicamente accesibles.
1544
+
1545
+ Realiza peticiones HEAD a `https://<candidato>.lambda-url.<región>.on.aws/`
1546
+ para detectar funciones con URLs públicas habilitadas.
1547
+
1548
+ Severidad:
1549
+ HIGH — Respuesta 200 (función accesible sin autenticación)
1550
+ MEDIUM — Respuesta 403 (función existe pero requiere autenticación)
1551
+ """
1552
+ import urllib.request as _ureq
1553
+ import urllib.error as _uerr
1554
+
1555
+ candidatos = _generar_candidatos_lambda(dominios)
1556
+
1557
+ for candidato in candidatos:
1558
+ for region in _AWS_LAMBDA_REGIONES:
1559
+ url = f"https://{candidato}.lambda-url.{region}.on.aws/"
1560
+ try:
1561
+ req = _ureq.Request(
1562
+ url,
1563
+ method="HEAD",
1564
+ headers={"User-Agent": f"VampSecureLabs/{VERSION}"},
1565
+ )
1566
+ with _ureq.urlopen(req, timeout=8) as resp:
1567
+ codigo = resp.status
1568
+ if codigo == 200:
1569
+ findings.append(BucketResult(
1570
+ name=candidato, provider="aws-lambda", url=url,
1571
+ status="PUBLIC", http_code=codigo,
1572
+ headers=dict(resp.headers),
1573
+ body_snippet=f"Lambda URL pública (200 OK) — {region}",
1574
+ severity="HIGH",
1575
+ finding_id="AWS_LAMBDA_URL_EXPOSED",
1576
+ ))
1577
+ except _uerr.HTTPError as exc:
1578
+ if exc.code == 403:
1579
+ # 403 confirma que la función existe (pero requiere auth)
1580
+ findings.append(BucketResult(
1581
+ name=candidato, provider="aws-lambda", url=url,
1582
+ status="FORBIDDEN", http_code=403,
1583
+ headers={},
1584
+ body_snippet=f"Lambda URL existe pero requiere autenticación (403) — {region}",
1585
+ severity="MEDIUM",
1586
+ finding_id="AWS_LAMBDA_URL_EXPOSED",
1587
+ ))
1588
+ except _uerr.URLError:
1589
+ # DNS NXDOMAIN u otro error de red: la función no existe con ese nombre/región
1590
+ pass
1591
+ except Exception:
1592
+ pass
1593
+
1594
+
1595
+ def _check_azure_function_apps(
1596
+ dominios: List[str],
1597
+ findings: List[BucketResult],
1598
+ ) -> None:
1599
+ """
1600
+ Prueba endpoints de Azure Function Apps en busca de funciones accesibles sin auth.
1601
+
1602
+ Realiza peticiones GET a `https://<nombre>.azurewebsites.net/api/<func>?code=`
1603
+ donde <nombre> se genera a partir de los dominios objetivo y <func> es una
1604
+ lista de nombres habituales de funciones HTTP trigger.
1605
+
1606
+ Severidad:
1607
+ HIGH — Respuesta 200 (función accesible sin código de autorización)
1608
+ """
1609
+ import urllib.request as _ureq
1610
+ import urllib.error as _uerr
1611
+ import re as _re
1612
+
1613
+ # Generar nombres de Function App candidatos a partir de los dominios
1614
+ app_names: List[str] = []
1615
+ for dominio in dominios:
1616
+ partes = dominio.rstrip(".").split(".")
1617
+ if len(partes) >= 2:
1618
+ nombre = partes[-2]
1619
+ else:
1620
+ nombre = partes[0]
1621
+ nombre_norm = _re.sub(r"[^a-z0-9\-]", "-", nombre.lower())
1622
+ nombre_norm = _re.sub(r"-+", "-", nombre_norm).strip("-")
1623
+ if not nombre_norm:
1624
+ continue
1625
+ app_names.append(nombre_norm)
1626
+ # Variantes comunes de Function App
1627
+ for sfx in ("api", "functions", "func", "app", "service"):
1628
+ app_names.append(f"{nombre_norm}-{sfx}")
1629
+ app_names.append(f"{sfx}-{nombre_norm}")
1630
+
1631
+ for app_name in app_names:
1632
+ for func_nombre in _AZURE_FUNC_NOMBRES:
1633
+ # Probar sin código de autorización (debería devolver 401/403 si es seguro)
1634
+ url = f"https://{app_name}.azurewebsites.net/api/{func_nombre}?code="
1635
+ try:
1636
+ req = _ureq.Request(
1637
+ url,
1638
+ headers={"User-Agent": f"VampSecureLabs/{VERSION}"},
1639
+ )
1640
+ with _ureq.urlopen(req, timeout=8) as resp:
1641
+ codigo = resp.status
1642
+ if codigo == 200:
1643
+ cuerpo = resp.read(256).decode("utf-8", errors="ignore")
1644
+ findings.append(BucketResult(
1645
+ name=f"{app_name}/{func_nombre}", provider="azure-functions", url=url,
1646
+ status="PUBLIC", http_code=codigo,
1647
+ headers=dict(resp.headers),
1648
+ body_snippet=cuerpo[:200],
1649
+ severity="HIGH",
1650
+ finding_id="AZURE_FUNCTION_EXPOSED",
1651
+ ))
1652
+ except _uerr.HTTPError as exc:
1653
+ # 401/403 = función existe pero requiere auth → no es hallazgo
1654
+ # 404 = función no existe → silencioso
1655
+ pass
1656
+ except _uerr.URLError:
1657
+ # DNS NXDOMAIN: Function App no existe → silencioso
1658
+ break # Si el dominio no resuelve, saltar todas las funciones del mismo app_name
1659
+ except Exception:
1660
+ pass
1661
+
1662
+
1663
+ # =============================================================================
1664
+ # ENUMERADOR DE GCP CLOUD FUNCTIONS EXPUESTAS (v1.3)
1665
+ # =============================================================================
1666
+
1667
+ # Regiones GCP más comunes para Cloud Functions gen1
1668
+ _GCP_FUNC_REGIONES: List[str] = [
1669
+ "us-central1", "europe-west1", "us-east1", "asia-east1",
1670
+ ]
1671
+
1672
+ # Nombres de función habituales a probar en cada región/proyecto candidato
1673
+ _GCP_FUNC_NOMBRES: List[str] = [
1674
+ "api", "webhook", "handler", "process", "auth", "login", "upload",
1675
+ "download", "admin", "user", "data", "event", "trigger", "callback",
1676
+ "notify", "health", "status", "test", "staging", "prod",
1677
+ ]
1678
+
1679
+
1680
+ class GCPFunctionsEnumerator:
1681
+ """
1682
+ Enumerador de GCP Cloud Functions expuestas públicamente.
1683
+
1684
+ Prueba URLs del tipo:
1685
+ https://<region>-<project>.cloudfunctions.net/<func_name>
1686
+
1687
+ para detectar funciones accesibles sin autenticación.
1688
+
1689
+ Severidad asignada:
1690
+ HIGH — HTTP 200 (función pública, responde sin auth)
1691
+ MEDIUM — HTTP 403 (función existe pero requiere autenticación)
1692
+ """
1693
+
1694
+ def enumerate(self, project_candidates: List[str]) -> List[BucketResult]:
1695
+ """
1696
+ Comprueba los project_candidates en las 4 regiones GCP y los 20 nombres
1697
+ de función habituales.
1698
+
1699
+ Parámetros
1700
+ ----------
1701
+ project_candidates : Lista de nombres de proyecto GCP candidatos
1702
+ (derivados del dominio/org objetivo)
1703
+
1704
+ Retorna
1705
+ -------
1706
+ Lista de BucketResult con hallazgos GCP-FUNC-001.
1707
+ """
1708
+ import urllib.request as _ureq
1709
+ import urllib.error as _uerr
1710
+ import time as _time
1711
+
1712
+ hallazgos: List[BucketResult] = []
1713
+
1714
+ for project in project_candidates:
1715
+ for region in _GCP_FUNC_REGIONES:
1716
+ for func_name in _GCP_FUNC_NOMBRES:
1717
+ url = (
1718
+ f"https://{region}-{project}.cloudfunctions.net/{func_name}"
1719
+ )
1720
+ try:
1721
+ req = _ureq.Request(
1722
+ url,
1723
+ method="HEAD",
1724
+ headers={"User-Agent": f"VampSecureLabs/{VERSION}"},
1725
+ )
1726
+ with _ureq.urlopen(req, timeout=5) as resp:
1727
+ codigo = resp.status
1728
+ if codigo == 200:
1729
+ hallazgos.append(BucketResult(
1730
+ name = f"{project}/{func_name}",
1731
+ provider = "gcp-functions",
1732
+ url = url,
1733
+ status = "PUBLIC",
1734
+ http_code = codigo,
1735
+ headers = dict(resp.headers),
1736
+ body_snippet = (
1737
+ f"GCP Cloud Function pública (200 OK) — "
1738
+ f"región: {region}, proyecto: {project}"
1739
+ ),
1740
+ severity = "HIGH",
1741
+ finding_id = "GCP-FUNC-001",
1742
+ ))
1743
+ except _uerr.HTTPError as exc:
1744
+ if exc.code == 403:
1745
+ # 403 → la función existe pero requiere autenticación IAM
1746
+ hallazgos.append(BucketResult(
1747
+ name = f"{project}/{func_name}",
1748
+ provider = "gcp-functions",
1749
+ url = url,
1750
+ status = "FORBIDDEN",
1751
+ http_code = 403,
1752
+ headers = {},
1753
+ body_snippet = (
1754
+ f"GCP Cloud Function existe pero requiere "
1755
+ f"autenticación (403) — región: {region}"
1756
+ ),
1757
+ severity = "MEDIUM",
1758
+ finding_id = "GCP-FUNC-001",
1759
+ ))
1760
+ except _uerr.URLError:
1761
+ # DNS NXDOMAIN u otro error de red → la función no existe
1762
+ pass
1763
+ except Exception:
1764
+ pass
1765
+ _time.sleep(0.3) # rate limit: 0.3s entre requests
1766
+
1767
+ return hallazgos
1768
+
1769
+
1770
+ def _generar_candidatos_gcp(dominios: List[str]) -> List[str]:
1771
+ """
1772
+ Genera candidatos para el ID de proyecto GCP a partir de los dominios objetivo.
1773
+
1774
+ Un ID de proyecto GCP se forma con letras minúsculas, dígitos y guiones
1775
+ (6-30 caracteres). Derivamos candidatos del dominio principal y variantes.
1776
+ """
1777
+ import re as _re
1778
+
1779
+ candidatos: set = set()
1780
+ for dominio in dominios:
1781
+ partes = dominio.rstrip(".").split(".")
1782
+ # Nombre base: dominio sin TLD
1783
+ if len(partes) >= 2:
1784
+ nombre = partes[-2]
1785
+ else:
1786
+ nombre = partes[0]
1787
+
1788
+ nombre_norm = _re.sub(r"[^a-z0-9\-]", "-", nombre.lower())
1789
+ nombre_norm = _re.sub(r"-+", "-", nombre_norm).strip("-")
1790
+ if not nombre_norm:
1791
+ continue
1792
+
1793
+ candidatos.add(nombre_norm)
1794
+ # Variantes habituales de nombre de proyecto GCP
1795
+ for sfx in ("api", "func", "functions", "app", "service", "backend", "prod"):
1796
+ candidatos.add(f"{nombre_norm}-{sfx}")
1797
+ candidatos.add(f"{sfx}-{nombre_norm}")
1798
+
1799
+ return list(candidatos)
1800
+
1801
+
1473
1802
  # =============================================================================
1474
1803
  # FUNCIÓN PRINCIPAL
1475
1804
  # =============================================================================
@@ -1702,6 +2031,38 @@ async def run(args: argparse.Namespace) -> int:
1702
2031
  else:
1703
2032
  console.print(" [green]Sin malas configuraciones detectadas.[/]")
1704
2033
 
2034
+ # ── Comprobaciones de exposición serverless (v1.3) ────────────────────────
2035
+ if getattr(args, "check_functions", False) and args.domains:
2036
+ console.print(
2037
+ "\n[bold cyan]Comprobando exposición de funciones serverless "
2038
+ "(Lambda URLs + Azure Functions + GCP Cloud Functions)…[/]\n"
2039
+ )
2040
+ function_findings: List[BucketResult] = []
2041
+
2042
+ if "s3" in providers:
2043
+ # Lambda es parte del ecosistema AWS (misma opción de proveedor que S3)
2044
+ console.print(" [dim]→ AWS Lambda function URLs…[/]")
2045
+ _check_lambda_function_urls(args.domains, function_findings)
2046
+
2047
+ if "azure" in providers:
2048
+ console.print(" [dim]→ Azure Function Apps…[/]")
2049
+ _check_azure_function_apps(args.domains, function_findings)
2050
+
2051
+ if "gcp" in providers:
2052
+ console.print(" [dim]→ GCP Cloud Functions…[/]")
2053
+ project_candidates = _generar_candidatos_gcp(args.domains)
2054
+ gcp_funcs = GCPFunctionsEnumerator()
2055
+ gcp_findings = gcp_funcs.enumerate(project_candidates)
2056
+ function_findings.extend(gcp_findings)
2057
+
2058
+ if function_findings:
2059
+ console.print(
2060
+ f" [yellow]Funciones serverless expuestas:[/] {len(function_findings)}"
2061
+ )
2062
+ found_all.extend(function_findings)
2063
+ else:
2064
+ console.print(" [green]Sin funciones serverless expuestas detectadas.[/]")
2065
+
1705
2066
  # ── Salida en consola ─────────────────────────────────────────────────────
1706
2067
  print_summary_table(found_all)
1707
2068
  print_critical_panels(found_all)
File without changes
File without changes