vamp-cloud-enum 1.0__tar.gz → 1.2__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/LICENSE +21 -0
- {vamp_cloud_enum-1.0 → vamp_cloud_enum-1.2}/PKG-INFO +17 -2
- {vamp_cloud_enum-1.0 → vamp_cloud_enum-1.2}/README.md +14 -1
- {vamp_cloud_enum-1.0 → vamp_cloud_enum-1.2}/pyproject.toml +1 -1
- {vamp_cloud_enum-1.0 → vamp_cloud_enum-1.2}/vamp_cloud_enum.egg-info/PKG-INFO +17 -2
- {vamp_cloud_enum-1.0 → vamp_cloud_enum-1.2}/vamp_cloud_enum.egg-info/SOURCES.txt +1 -0
- {vamp_cloud_enum-1.0 → vamp_cloud_enum-1.2}/vamp_cloud_enum.py +382 -4
- {vamp_cloud_enum-1.0 → vamp_cloud_enum-1.2}/setup.cfg +0 -0
- {vamp_cloud_enum-1.0 → vamp_cloud_enum-1.2}/vamp_cloud_enum.egg-info/dependency_links.txt +0 -0
- {vamp_cloud_enum-1.0 → vamp_cloud_enum-1.2}/vamp_cloud_enum.egg-info/entry_points.txt +0 -0
- {vamp_cloud_enum-1.0 → vamp_cloud_enum-1.2}/vamp_cloud_enum.egg-info/requires.txt +0 -0
- {vamp_cloud_enum-1.0 → vamp_cloud_enum-1.2}/vamp_cloud_enum.egg-info/top_level.txt +0 -0
- {vamp_cloud_enum-1.0 → vamp_cloud_enum-1.2}/vampsec_report.py +0 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 VampSecure Studios
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: vamp-cloud-enum
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2
|
|
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
|
|
@@ -16,11 +16,14 @@ Classifier: Programming Language :: Python :: 3
|
|
|
16
16
|
Classifier: Topic :: Security
|
|
17
17
|
Requires-Python: >=3.9
|
|
18
18
|
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE
|
|
19
20
|
Requires-Dist: aiohttp>=3.9.0
|
|
20
21
|
Requires-Dist: rich>=13.7.0
|
|
22
|
+
Dynamic: license-file
|
|
21
23
|
|
|
24
|
+
<!-- © VampSecure Studios — VampSecure Labs Security Research Division -->
|
|
22
25
|
<p align="center">
|
|
23
|
-
<img src="https://img.shields.io/badge/version-1.
|
|
26
|
+
<img src="https://img.shields.io/badge/version-1.1-crimson?style=flat-square" />
|
|
24
27
|
<img src="https://img.shields.io/badge/python-3.11+-blue?style=flat-square&logo=python&logoColor=white" />
|
|
25
28
|
<img src="https://img.shields.io/badge/providers-AWS%20%7C%20Azure%20%7C%20GCP-teal?style=flat-square" />
|
|
26
29
|
<img src="https://img.shields.io/badge/VampSecure_Labs-Security_Research-8b0000?style=flat-square" />
|
|
@@ -69,6 +72,13 @@ pip install -r requirements.txt
|
|
|
69
72
|
|
|
70
73
|
## Installation
|
|
71
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
|
+
|
|
72
82
|
```bash
|
|
73
83
|
git clone https://github.com/belky-me/vamp-cloud-enum.git
|
|
74
84
|
cd vamp-cloud-enum
|
|
@@ -186,3 +196,8 @@ python vamp_cloud_enum.py -d example.com -d example-cdn.com \
|
|
|
186
196
|
© VampSecure Studios — VampSecure Labs Security Research Division<br/>
|
|
187
197
|
For authorized security assessments only. Unauthorized use is prohibited.
|
|
188
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.
|
|
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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: vamp-cloud-enum
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2
|
|
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
|
|
@@ -16,11 +16,14 @@ Classifier: Programming Language :: Python :: 3
|
|
|
16
16
|
Classifier: Topic :: Security
|
|
17
17
|
Requires-Python: >=3.9
|
|
18
18
|
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE
|
|
19
20
|
Requires-Dist: aiohttp>=3.9.0
|
|
20
21
|
Requires-Dist: rich>=13.7.0
|
|
22
|
+
Dynamic: license-file
|
|
21
23
|
|
|
24
|
+
<!-- © VampSecure Studios — VampSecure Labs Security Research Division -->
|
|
22
25
|
<p align="center">
|
|
23
|
-
<img src="https://img.shields.io/badge/version-1.
|
|
26
|
+
<img src="https://img.shields.io/badge/version-1.1-crimson?style=flat-square" />
|
|
24
27
|
<img src="https://img.shields.io/badge/python-3.11+-blue?style=flat-square&logo=python&logoColor=white" />
|
|
25
28
|
<img src="https://img.shields.io/badge/providers-AWS%20%7C%20Azure%20%7C%20GCP-teal?style=flat-square" />
|
|
26
29
|
<img src="https://img.shields.io/badge/VampSecure_Labs-Security_Research-8b0000?style=flat-square" />
|
|
@@ -69,6 +72,13 @@ pip install -r requirements.txt
|
|
|
69
72
|
|
|
70
73
|
## Installation
|
|
71
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
|
+
|
|
72
82
|
```bash
|
|
73
83
|
git clone https://github.com/belky-me/vamp-cloud-enum.git
|
|
74
84
|
cd vamp-cloud-enum
|
|
@@ -186,3 +196,8 @@ python vamp_cloud_enum.py -d example.com -d example-cdn.com \
|
|
|
186
196
|
© VampSecure Studios — VampSecure Labs Security Research Division<br/>
|
|
187
197
|
For authorized security assessments only. Unauthorized use is prohibited.
|
|
188
198
|
</p>
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## Versión
|
|
203
|
+
v1.1 — VampSecure Labs Security Research Division
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
vamp_cloud_enum.py — Enumerador de Buckets/Blobs Cloud Públicos
|
|
4
4
|
================================================================
|
|
5
5
|
VampSecure Labs · VampSecure Studios
|
|
6
|
-
Para Uso Exclusivo en Pruebas de Penetración Autorizadas — v1.
|
|
6
|
+
Para Uso Exclusivo en Pruebas de Penetración Autorizadas — v1.1
|
|
7
7
|
|
|
8
8
|
DESCRIPCIÓN GENERAL
|
|
9
9
|
-------------------
|
|
@@ -99,19 +99,19 @@ from vampsec_report import (
|
|
|
99
99
|
# CONSTANTES Y CONFIGURACIÓN
|
|
100
100
|
# =============================================================================
|
|
101
101
|
|
|
102
|
-
VERSION = "1.
|
|
102
|
+
VERSION = "1.2"
|
|
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.
|
|
114
|
+
vamp-cloud-enum v1.2 · Cloud Bucket Enumerator
|
|
115
115
|
────────────────────────────────────────────────────────────────────────
|
|
116
116
|
USO EXCLUSIVO EN AUDITORÍAS AUTORIZADAS · El uso no autorizado es ilegal
|
|
117
117
|
"""
|
|
@@ -1428,6 +1428,25 @@ def parse_args() -> argparse.Namespace:
|
|
|
1428
1428
|
dest="html_out",
|
|
1429
1429
|
help="Guardar informe HTML dark-theme",
|
|
1430
1430
|
)
|
|
1431
|
+
p.add_argument(
|
|
1432
|
+
"--check-misconfigs",
|
|
1433
|
+
action="store_true",
|
|
1434
|
+
dest="check_misconfigs",
|
|
1435
|
+
help=(
|
|
1436
|
+
"Comprobar malas configuraciones en S3/Azure/GCS sin credenciales "
|
|
1437
|
+
"(acceso público, listado de contenedores) — v1.1"
|
|
1438
|
+
),
|
|
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
|
+
"y Azure Function Apps. Genera muchas peticiones (usa con precaución). "
|
|
1447
|
+
"Hallazgos: AWS_LAMBDA_URL_EXPOSED, AZURE_FUNCTION_EXPOSED — v1.2"
|
|
1448
|
+
),
|
|
1449
|
+
)
|
|
1431
1450
|
|
|
1432
1451
|
add_report_args(p)
|
|
1433
1452
|
return p.parse_args()
|
|
@@ -1461,10 +1480,317 @@ def _load_wordlist(path: str) -> List[str]:
|
|
|
1461
1480
|
return []
|
|
1462
1481
|
|
|
1463
1482
|
|
|
1483
|
+
# =============================================================================
|
|
1484
|
+
# COMPROBACIÓN DE EXPOSICIÓN DE FUNCIONES SERVERLESS (v1.2)
|
|
1485
|
+
# =============================================================================
|
|
1486
|
+
|
|
1487
|
+
# Regiones AWS más comunes para Lambda function URLs
|
|
1488
|
+
_AWS_LAMBDA_REGIONES: List[str] = [
|
|
1489
|
+
"us-east-1", "us-east-2", "us-west-1", "us-west-2",
|
|
1490
|
+
"eu-west-1", "eu-west-2", "eu-central-1",
|
|
1491
|
+
"ap-southeast-1", "ap-southeast-2", "ap-northeast-1",
|
|
1492
|
+
]
|
|
1493
|
+
|
|
1494
|
+
# Funciones Azure habituales a probar (variantes del nombre del endpoint)
|
|
1495
|
+
_AZURE_FUNC_NOMBRES: List[str] = [
|
|
1496
|
+
"api", "function", "func", "http", "trigger", "webhook",
|
|
1497
|
+
"process", "handler", "main", "app",
|
|
1498
|
+
]
|
|
1499
|
+
|
|
1500
|
+
|
|
1501
|
+
def _generar_candidatos_lambda(dominios: List[str]) -> List[str]:
|
|
1502
|
+
"""
|
|
1503
|
+
Genera candidatos para el ID de Lambda function URL usando partes del dominio.
|
|
1504
|
+
|
|
1505
|
+
Los function URL IDs de AWS Lambda son cadenas de 26 caracteres aleatorias
|
|
1506
|
+
y no se pueden predecir. Esta función genera candidatos basados en las partes
|
|
1507
|
+
del dominio para detectar funciones con nombres personalizados o patrones
|
|
1508
|
+
predecibles que algunas organizaciones configuran.
|
|
1509
|
+
|
|
1510
|
+
Retorna lista de candidatos únicos (en minúsculas, solo alfanuméricos y guiones).
|
|
1511
|
+
"""
|
|
1512
|
+
candidatos: set = set()
|
|
1513
|
+
for dominio in dominios:
|
|
1514
|
+
# Extraer parte principal del dominio (sin TLD)
|
|
1515
|
+
partes = dominio.rstrip(".").split(".")
|
|
1516
|
+
# Usar el nombre del dominio sin TLD y sin subdominios secundarios
|
|
1517
|
+
if len(partes) >= 2:
|
|
1518
|
+
nombre = partes[-2]
|
|
1519
|
+
else:
|
|
1520
|
+
nombre = partes[0]
|
|
1521
|
+
# Normalizar: solo alfanuméricos y guiones
|
|
1522
|
+
import re as _re
|
|
1523
|
+
nombre_norm = _re.sub(r"[^a-z0-9\-]", "-", nombre.lower())
|
|
1524
|
+
nombre_norm = _re.sub(r"-+", "-", nombre_norm).strip("-")
|
|
1525
|
+
if not nombre_norm:
|
|
1526
|
+
continue
|
|
1527
|
+
|
|
1528
|
+
candidatos.add(nombre_norm)
|
|
1529
|
+
# Variantes comunes
|
|
1530
|
+
for sfx in ("api", "func", "lambda", "fn", "app", "service", "svc", "handler"):
|
|
1531
|
+
candidatos.add(f"{nombre_norm}-{sfx}")
|
|
1532
|
+
candidatos.add(f"{sfx}-{nombre_norm}")
|
|
1533
|
+
|
|
1534
|
+
return list(candidatos)
|
|
1535
|
+
|
|
1536
|
+
|
|
1537
|
+
def _check_lambda_function_urls(
|
|
1538
|
+
dominios: List[str],
|
|
1539
|
+
findings: List[BucketResult],
|
|
1540
|
+
) -> None:
|
|
1541
|
+
"""
|
|
1542
|
+
Prueba URLs de funciones Lambda AWS en busca de endpoints públicamente accesibles.
|
|
1543
|
+
|
|
1544
|
+
Realiza peticiones HEAD a `https://<candidato>.lambda-url.<región>.on.aws/`
|
|
1545
|
+
para detectar funciones con URLs públicas habilitadas.
|
|
1546
|
+
|
|
1547
|
+
Severidad:
|
|
1548
|
+
HIGH — Respuesta 200 (función accesible sin autenticación)
|
|
1549
|
+
MEDIUM — Respuesta 403 (función existe pero requiere autenticación)
|
|
1550
|
+
"""
|
|
1551
|
+
import urllib.request as _ureq
|
|
1552
|
+
import urllib.error as _uerr
|
|
1553
|
+
|
|
1554
|
+
candidatos = _generar_candidatos_lambda(dominios)
|
|
1555
|
+
|
|
1556
|
+
for candidato in candidatos:
|
|
1557
|
+
for region in _AWS_LAMBDA_REGIONES:
|
|
1558
|
+
url = f"https://{candidato}.lambda-url.{region}.on.aws/"
|
|
1559
|
+
try:
|
|
1560
|
+
req = _ureq.Request(
|
|
1561
|
+
url,
|
|
1562
|
+
method="HEAD",
|
|
1563
|
+
headers={"User-Agent": f"VampSecureLabs/{VERSION}"},
|
|
1564
|
+
)
|
|
1565
|
+
with _ureq.urlopen(req, timeout=8) as resp:
|
|
1566
|
+
codigo = resp.status
|
|
1567
|
+
if codigo == 200:
|
|
1568
|
+
findings.append(BucketResult(
|
|
1569
|
+
name=candidato, provider="aws-lambda", url=url,
|
|
1570
|
+
status="PUBLIC", http_code=codigo,
|
|
1571
|
+
headers=dict(resp.headers),
|
|
1572
|
+
body_snippet=f"Lambda URL pública (200 OK) — {region}",
|
|
1573
|
+
severity="HIGH",
|
|
1574
|
+
finding_id="AWS_LAMBDA_URL_EXPOSED",
|
|
1575
|
+
))
|
|
1576
|
+
except _uerr.HTTPError as exc:
|
|
1577
|
+
if exc.code == 403:
|
|
1578
|
+
# 403 confirma que la función existe (pero requiere auth)
|
|
1579
|
+
findings.append(BucketResult(
|
|
1580
|
+
name=candidato, provider="aws-lambda", url=url,
|
|
1581
|
+
status="FORBIDDEN", http_code=403,
|
|
1582
|
+
headers={},
|
|
1583
|
+
body_snippet=f"Lambda URL existe pero requiere autenticación (403) — {region}",
|
|
1584
|
+
severity="MEDIUM",
|
|
1585
|
+
finding_id="AWS_LAMBDA_URL_EXPOSED",
|
|
1586
|
+
))
|
|
1587
|
+
except _uerr.URLError:
|
|
1588
|
+
# DNS NXDOMAIN u otro error de red: la función no existe con ese nombre/región
|
|
1589
|
+
pass
|
|
1590
|
+
except Exception:
|
|
1591
|
+
pass
|
|
1592
|
+
|
|
1593
|
+
|
|
1594
|
+
def _check_azure_function_apps(
|
|
1595
|
+
dominios: List[str],
|
|
1596
|
+
findings: List[BucketResult],
|
|
1597
|
+
) -> None:
|
|
1598
|
+
"""
|
|
1599
|
+
Prueba endpoints de Azure Function Apps en busca de funciones accesibles sin auth.
|
|
1600
|
+
|
|
1601
|
+
Realiza peticiones GET a `https://<nombre>.azurewebsites.net/api/<func>?code=`
|
|
1602
|
+
donde <nombre> se genera a partir de los dominios objetivo y <func> es una
|
|
1603
|
+
lista de nombres habituales de funciones HTTP trigger.
|
|
1604
|
+
|
|
1605
|
+
Severidad:
|
|
1606
|
+
HIGH — Respuesta 200 (función accesible sin código de autorización)
|
|
1607
|
+
"""
|
|
1608
|
+
import urllib.request as _ureq
|
|
1609
|
+
import urllib.error as _uerr
|
|
1610
|
+
import re as _re
|
|
1611
|
+
|
|
1612
|
+
# Generar nombres de Function App candidatos a partir de los dominios
|
|
1613
|
+
app_names: List[str] = []
|
|
1614
|
+
for dominio in dominios:
|
|
1615
|
+
partes = dominio.rstrip(".").split(".")
|
|
1616
|
+
if len(partes) >= 2:
|
|
1617
|
+
nombre = partes[-2]
|
|
1618
|
+
else:
|
|
1619
|
+
nombre = partes[0]
|
|
1620
|
+
nombre_norm = _re.sub(r"[^a-z0-9\-]", "-", nombre.lower())
|
|
1621
|
+
nombre_norm = _re.sub(r"-+", "-", nombre_norm).strip("-")
|
|
1622
|
+
if not nombre_norm:
|
|
1623
|
+
continue
|
|
1624
|
+
app_names.append(nombre_norm)
|
|
1625
|
+
# Variantes comunes de Function App
|
|
1626
|
+
for sfx in ("api", "functions", "func", "app", "service"):
|
|
1627
|
+
app_names.append(f"{nombre_norm}-{sfx}")
|
|
1628
|
+
app_names.append(f"{sfx}-{nombre_norm}")
|
|
1629
|
+
|
|
1630
|
+
for app_name in app_names:
|
|
1631
|
+
for func_nombre in _AZURE_FUNC_NOMBRES:
|
|
1632
|
+
# Probar sin código de autorización (debería devolver 401/403 si es seguro)
|
|
1633
|
+
url = f"https://{app_name}.azurewebsites.net/api/{func_nombre}?code="
|
|
1634
|
+
try:
|
|
1635
|
+
req = _ureq.Request(
|
|
1636
|
+
url,
|
|
1637
|
+
headers={"User-Agent": f"VampSecureLabs/{VERSION}"},
|
|
1638
|
+
)
|
|
1639
|
+
with _ureq.urlopen(req, timeout=8) as resp:
|
|
1640
|
+
codigo = resp.status
|
|
1641
|
+
if codigo == 200:
|
|
1642
|
+
cuerpo = resp.read(256).decode("utf-8", errors="ignore")
|
|
1643
|
+
findings.append(BucketResult(
|
|
1644
|
+
name=f"{app_name}/{func_nombre}", provider="azure-functions", url=url,
|
|
1645
|
+
status="PUBLIC", http_code=codigo,
|
|
1646
|
+
headers=dict(resp.headers),
|
|
1647
|
+
body_snippet=cuerpo[:200],
|
|
1648
|
+
severity="HIGH",
|
|
1649
|
+
finding_id="AZURE_FUNCTION_EXPOSED",
|
|
1650
|
+
))
|
|
1651
|
+
except _uerr.HTTPError as exc:
|
|
1652
|
+
# 401/403 = función existe pero requiere auth → no es hallazgo
|
|
1653
|
+
# 404 = función no existe → silencioso
|
|
1654
|
+
pass
|
|
1655
|
+
except _uerr.URLError:
|
|
1656
|
+
# DNS NXDOMAIN: Function App no existe → silencioso
|
|
1657
|
+
break # Si el dominio no resuelve, saltar todas las funciones del mismo app_name
|
|
1658
|
+
except Exception:
|
|
1659
|
+
pass
|
|
1660
|
+
|
|
1661
|
+
|
|
1464
1662
|
# =============================================================================
|
|
1465
1663
|
# FUNCIÓN PRINCIPAL
|
|
1466
1664
|
# =============================================================================
|
|
1467
1665
|
|
|
1666
|
+
# =============================================================================
|
|
1667
|
+
# COMPROBACIONES DE MALA CONFIGURACIÓN (v1.1) — urllib.request sin credenciales
|
|
1668
|
+
# =============================================================================
|
|
1669
|
+
|
|
1670
|
+
def _check_s3_public_access(
|
|
1671
|
+
bucket_names: List[str],
|
|
1672
|
+
findings: List[BucketResult],
|
|
1673
|
+
) -> None:
|
|
1674
|
+
"""
|
|
1675
|
+
Comprueba acceso público en buckets S3 sin credenciales (v1.1).
|
|
1676
|
+
|
|
1677
|
+
Realiza peticiones GET anónimas a las URLs canónicas de S3 y clasifica
|
|
1678
|
+
el resultado según la respuesta HTTP.
|
|
1679
|
+
|
|
1680
|
+
Severidad:
|
|
1681
|
+
CRITICAL — La respuesta contiene ListBucketResult (listado público)
|
|
1682
|
+
HIGH — Respuesta 200 OK sin listado (contenido accesible)
|
|
1683
|
+
INFO — 403/404 (bucket privado o inexistente)
|
|
1684
|
+
"""
|
|
1685
|
+
import urllib.request
|
|
1686
|
+
import urllib.error
|
|
1687
|
+
|
|
1688
|
+
for nombre in bucket_names:
|
|
1689
|
+
url_raiz = f"https://{nombre}.s3.amazonaws.com/"
|
|
1690
|
+
try:
|
|
1691
|
+
req = urllib.request.Request(
|
|
1692
|
+
url_raiz, headers={"User-Agent": f"VampSecureLabs/{VERSION}"}
|
|
1693
|
+
)
|
|
1694
|
+
with urllib.request.urlopen(req, timeout=10) as resp:
|
|
1695
|
+
codigo = resp.status
|
|
1696
|
+
cuerpo = resp.read(512).decode("utf-8", errors="ignore")
|
|
1697
|
+
if "ListBucketResult" in cuerpo:
|
|
1698
|
+
findings.append(BucketResult(
|
|
1699
|
+
name=nombre, provider="s3", url=url_raiz,
|
|
1700
|
+
status="LISTING", http_code=codigo,
|
|
1701
|
+
headers={}, body_snippet=cuerpo[:200],
|
|
1702
|
+
severity="CRITICAL", finding_id="CLOUD-MC-S3-001",
|
|
1703
|
+
))
|
|
1704
|
+
else:
|
|
1705
|
+
findings.append(BucketResult(
|
|
1706
|
+
name=nombre, provider="s3", url=url_raiz,
|
|
1707
|
+
status="PUBLIC", http_code=codigo,
|
|
1708
|
+
headers={}, body_snippet=cuerpo[:200],
|
|
1709
|
+
severity="HIGH", finding_id="CLOUD-MC-S3-002",
|
|
1710
|
+
))
|
|
1711
|
+
except urllib.error.HTTPError as exc:
|
|
1712
|
+
if exc.code not in (403, 404):
|
|
1713
|
+
# Error inesperado; ignorar silenciosamente
|
|
1714
|
+
pass
|
|
1715
|
+
except Exception:
|
|
1716
|
+
# Error de red o DNS; ignorar
|
|
1717
|
+
pass
|
|
1718
|
+
|
|
1719
|
+
|
|
1720
|
+
def _check_azure_blob_public(
|
|
1721
|
+
storage_accounts: List[str],
|
|
1722
|
+
findings: List[BucketResult],
|
|
1723
|
+
) -> None:
|
|
1724
|
+
"""
|
|
1725
|
+
Comprueba acceso público en cuentas Azure Blob Storage sin credenciales (v1.1).
|
|
1726
|
+
|
|
1727
|
+
Realiza peticiones GET anónimas a la API de listado de contenedores.
|
|
1728
|
+
|
|
1729
|
+
Severidad:
|
|
1730
|
+
HIGH — La respuesta XML contiene EnumerationResults (listado público)
|
|
1731
|
+
"""
|
|
1732
|
+
import urllib.request
|
|
1733
|
+
import urllib.error
|
|
1734
|
+
|
|
1735
|
+
for cuenta in storage_accounts:
|
|
1736
|
+
url = f"https://{cuenta}.blob.core.windows.net/?comp=list"
|
|
1737
|
+
try:
|
|
1738
|
+
req = urllib.request.Request(
|
|
1739
|
+
url, headers={"User-Agent": f"VampSecureLabs/{VERSION}"}
|
|
1740
|
+
)
|
|
1741
|
+
with urllib.request.urlopen(req, timeout=10) as resp:
|
|
1742
|
+
codigo = resp.status
|
|
1743
|
+
cuerpo = resp.read(512).decode("utf-8", errors="ignore")
|
|
1744
|
+
if "EnumerationResults" in cuerpo:
|
|
1745
|
+
findings.append(BucketResult(
|
|
1746
|
+
name=cuenta, provider="azure", url=url,
|
|
1747
|
+
status="LISTING", http_code=codigo,
|
|
1748
|
+
headers={}, body_snippet=cuerpo[:200],
|
|
1749
|
+
severity="HIGH", finding_id="CLOUD-MC-AZ-001",
|
|
1750
|
+
))
|
|
1751
|
+
except urllib.error.HTTPError:
|
|
1752
|
+
pass # 403/404 esperado para cuentas privadas
|
|
1753
|
+
except Exception:
|
|
1754
|
+
pass # Error de red
|
|
1755
|
+
|
|
1756
|
+
|
|
1757
|
+
def _check_gcs_public(
|
|
1758
|
+
bucket_names: List[str],
|
|
1759
|
+
findings: List[BucketResult],
|
|
1760
|
+
) -> None:
|
|
1761
|
+
"""
|
|
1762
|
+
Comprueba acceso público en buckets GCS sin credenciales (v1.1).
|
|
1763
|
+
|
|
1764
|
+
Realiza peticiones GET anónimas a la API JSON de Cloud Storage.
|
|
1765
|
+
|
|
1766
|
+
Severidad:
|
|
1767
|
+
HIGH — La respuesta JSON 200 devuelve metadatos del bucket (acceso público)
|
|
1768
|
+
"""
|
|
1769
|
+
import urllib.request
|
|
1770
|
+
import urllib.error
|
|
1771
|
+
|
|
1772
|
+
for nombre in bucket_names:
|
|
1773
|
+
url = f"https://storage.googleapis.com/{nombre}?alt=json"
|
|
1774
|
+
try:
|
|
1775
|
+
req = urllib.request.Request(
|
|
1776
|
+
url, headers={"User-Agent": f"VampSecureLabs/{VERSION}"}
|
|
1777
|
+
)
|
|
1778
|
+
with urllib.request.urlopen(req, timeout=10) as resp:
|
|
1779
|
+
codigo = resp.status
|
|
1780
|
+
cuerpo = resp.read(512).decode("utf-8", errors="ignore")
|
|
1781
|
+
if codigo == 200:
|
|
1782
|
+
findings.append(BucketResult(
|
|
1783
|
+
name=nombre, provider="gcp", url=url,
|
|
1784
|
+
status="PUBLIC", http_code=codigo,
|
|
1785
|
+
headers={}, body_snippet=cuerpo[:200],
|
|
1786
|
+
severity="HIGH", finding_id="CLOUD-MC-GCS-001",
|
|
1787
|
+
))
|
|
1788
|
+
except urllib.error.HTTPError:
|
|
1789
|
+
pass # 403/404 esperado para buckets privados
|
|
1790
|
+
except Exception:
|
|
1791
|
+
pass # Error de red
|
|
1792
|
+
|
|
1793
|
+
|
|
1468
1794
|
async def run(args: argparse.Namespace) -> int:
|
|
1469
1795
|
"""
|
|
1470
1796
|
Orquesta la enumeración completa de buckets cloud.
|
|
@@ -1538,6 +1864,58 @@ async def run(args: argparse.Namespace) -> int:
|
|
|
1538
1864
|
)
|
|
1539
1865
|
all_results.append(result)
|
|
1540
1866
|
|
|
1867
|
+
# ── Comprobaciones de mala configuración (v1.1) ───────────────────────────
|
|
1868
|
+
if getattr(args, "check_misconfigs", False) and args.domains:
|
|
1869
|
+
console.print("\n[bold cyan]Comprobando malas configuraciones cloud (sin credenciales)…[/]\n")
|
|
1870
|
+
misconfig_findings: List[BucketResult] = []
|
|
1871
|
+
# Generar nombres de bucket candidatos a partir de los dominios objetivo
|
|
1872
|
+
nombres_mc: List[str] = []
|
|
1873
|
+
for dominio in args.domains:
|
|
1874
|
+
norm = re.sub(r"[^a-z0-9\-]", "-", dominio.lower().strip())
|
|
1875
|
+
norm = re.sub(r"-+", "-", norm).strip("-")
|
|
1876
|
+
if norm:
|
|
1877
|
+
nombres_mc.append(norm)
|
|
1878
|
+
|
|
1879
|
+
if "s3" in providers:
|
|
1880
|
+
_check_s3_public_access(nombres_mc, misconfig_findings)
|
|
1881
|
+
if "azure" in providers:
|
|
1882
|
+
_check_azure_blob_public(nombres_mc, misconfig_findings)
|
|
1883
|
+
if "gcp" in providers:
|
|
1884
|
+
_check_gcs_public(nombres_mc, misconfig_findings)
|
|
1885
|
+
|
|
1886
|
+
if misconfig_findings:
|
|
1887
|
+
console.print(
|
|
1888
|
+
f" [yellow]Malas configuraciones detectadas:[/] {len(misconfig_findings)}"
|
|
1889
|
+
)
|
|
1890
|
+
found_all.extend(misconfig_findings)
|
|
1891
|
+
else:
|
|
1892
|
+
console.print(" [green]Sin malas configuraciones detectadas.[/]")
|
|
1893
|
+
|
|
1894
|
+
# ── Comprobaciones de exposición serverless (v1.2) ────────────────────────
|
|
1895
|
+
if getattr(args, "check_functions", False) and args.domains:
|
|
1896
|
+
console.print(
|
|
1897
|
+
"\n[bold cyan]Comprobando exposición de funciones serverless "
|
|
1898
|
+
"(Lambda URLs + Azure Functions)…[/]\n"
|
|
1899
|
+
)
|
|
1900
|
+
function_findings: List[BucketResult] = []
|
|
1901
|
+
|
|
1902
|
+
if "s3" in providers:
|
|
1903
|
+
# Lambda es parte del ecosistema AWS (misma opción de proveedor que S3)
|
|
1904
|
+
console.print(" [dim]→ AWS Lambda function URLs…[/]")
|
|
1905
|
+
_check_lambda_function_urls(args.domains, function_findings)
|
|
1906
|
+
|
|
1907
|
+
if "azure" in providers:
|
|
1908
|
+
console.print(" [dim]→ Azure Function Apps…[/]")
|
|
1909
|
+
_check_azure_function_apps(args.domains, function_findings)
|
|
1910
|
+
|
|
1911
|
+
if function_findings:
|
|
1912
|
+
console.print(
|
|
1913
|
+
f" [yellow]Funciones serverless expuestas:[/] {len(function_findings)}"
|
|
1914
|
+
)
|
|
1915
|
+
found_all.extend(function_findings)
|
|
1916
|
+
else:
|
|
1917
|
+
console.print(" [green]Sin funciones serverless expuestas detectadas.[/]")
|
|
1918
|
+
|
|
1541
1919
|
# ── Salida en consola ─────────────────────────────────────────────────────
|
|
1542
1920
|
print_summary_table(found_all)
|
|
1543
1921
|
print_critical_panels(found_all)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|