vamp-cloud-enum 1.2__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.2
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "vamp-cloud-enum"
7
- version = "1.2"
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.2
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
@@ -99,7 +99,7 @@ from vampsec_report import (
99
99
  # CONSTANTES Y CONFIGURACIÓN
100
100
  # =============================================================================
101
101
 
102
- VERSION = "1.2"
102
+ VERSION = "1.3"
103
103
  TOOL_NAME = "vamp-cloud-enum"
104
104
  AUTHOR = "© VampSecure Studios — VampSecure Labs Security Research Division"
105
105
 
@@ -111,7 +111,7 @@ __ ___ __ __ ___ ___ ___ ___ _ _ ___ ___ _ _ ___ ___
111
111
  \ V / _ \| |\/| | _/\__ \ _| (__| |_| | / _|| |__ / _ \| _ \__ \
112
112
  \_/_/ \_\_| |_|_| |___/___\___|\___/|_|_\___|____/_/ \_\___/___/
113
113
  by Antonio Hernandez "Belky" — VampSecure Studios
114
- vamp-cloud-enum v1.2 · 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
  """
@@ -1442,9 +1442,10 @@ def parse_args() -> argparse.Namespace:
1442
1442
  action="store_true",
1443
1443
  dest="check_functions",
1444
1444
  help=(
1445
- "Comprobar exposición de funciones serverless: AWS Lambda function URLs "
1446
- "y Azure Function Apps. Genera muchas peticiones (usa con precaución). "
1447
- "Hallazgos: AWS_LAMBDA_URL_EXPOSED, AZURE_FUNCTION_EXPOSED — v1.2"
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"
1448
1449
  ),
1449
1450
  )
1450
1451
 
@@ -1659,6 +1660,145 @@ def _check_azure_function_apps(
1659
1660
  pass
1660
1661
 
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
+
1662
1802
  # =============================================================================
1663
1803
  # FUNCIÓN PRINCIPAL
1664
1804
  # =============================================================================
@@ -1891,11 +2031,11 @@ async def run(args: argparse.Namespace) -> int:
1891
2031
  else:
1892
2032
  console.print(" [green]Sin malas configuraciones detectadas.[/]")
1893
2033
 
1894
- # ── Comprobaciones de exposición serverless (v1.2) ────────────────────────
2034
+ # ── Comprobaciones de exposición serverless (v1.3) ────────────────────────
1895
2035
  if getattr(args, "check_functions", False) and args.domains:
1896
2036
  console.print(
1897
2037
  "\n[bold cyan]Comprobando exposición de funciones serverless "
1898
- "(Lambda URLs + Azure Functions)…[/]\n"
2038
+ "(Lambda URLs + Azure Functions + GCP Cloud Functions)…[/]\n"
1899
2039
  )
1900
2040
  function_findings: List[BucketResult] = []
1901
2041
 
@@ -1908,6 +2048,13 @@ async def run(args: argparse.Namespace) -> int:
1908
2048
  console.print(" [dim]→ Azure Function Apps…[/]")
1909
2049
  _check_azure_function_apps(args.domains, function_findings)
1910
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
+
1911
2058
  if function_findings:
1912
2059
  console.print(
1913
2060
  f" [yellow]Funciones serverless expuestas:[/] {len(function_findings)}"
File without changes
File without changes
File without changes