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.
- {vamp_cloud_enum-1.2/vamp_cloud_enum.egg-info → vamp_cloud_enum-1.3}/PKG-INFO +1 -1
- {vamp_cloud_enum-1.2 → vamp_cloud_enum-1.3}/pyproject.toml +1 -1
- {vamp_cloud_enum-1.2 → vamp_cloud_enum-1.3/vamp_cloud_enum.egg-info}/PKG-INFO +1 -1
- {vamp_cloud_enum-1.2 → vamp_cloud_enum-1.3}/vamp_cloud_enum.py +154 -7
- {vamp_cloud_enum-1.2 → vamp_cloud_enum-1.3}/LICENSE +0 -0
- {vamp_cloud_enum-1.2 → vamp_cloud_enum-1.3}/README.md +0 -0
- {vamp_cloud_enum-1.2 → vamp_cloud_enum-1.3}/setup.cfg +0 -0
- {vamp_cloud_enum-1.2 → vamp_cloud_enum-1.3}/vamp_cloud_enum.egg-info/SOURCES.txt +0 -0
- {vamp_cloud_enum-1.2 → vamp_cloud_enum-1.3}/vamp_cloud_enum.egg-info/dependency_links.txt +0 -0
- {vamp_cloud_enum-1.2 → vamp_cloud_enum-1.3}/vamp_cloud_enum.egg-info/entry_points.txt +0 -0
- {vamp_cloud_enum-1.2 → vamp_cloud_enum-1.3}/vamp_cloud_enum.egg-info/requires.txt +0 -0
- {vamp_cloud_enum-1.2 → vamp_cloud_enum-1.3}/vamp_cloud_enum.egg-info/top_level.txt +0 -0
- {vamp_cloud_enum-1.2 → vamp_cloud_enum-1.3}/vampsec_report.py +0 -0
|
@@ -99,7 +99,7 @@ from vampsec_report import (
|
|
|
99
99
|
# CONSTANTES Y CONFIGURACIÓN
|
|
100
100
|
# =============================================================================
|
|
101
101
|
|
|
102
|
-
VERSION = "1.
|
|
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.
|
|
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
|
-
"
|
|
1447
|
-
"Hallazgos: AWS_LAMBDA_URL_EXPOSED,
|
|
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.
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|