Ajuste REPO
This commit is contained in:
parent
a0f1c6b0bc
commit
2dda097a11
@ -4,4 +4,6 @@ echo "Revisando vulnerabilidades de productos mediante ENISA"
|
||||
echo "Transformando informe en formato Markdown en PDF"
|
||||
pandoc -V lang=es -V geometry:a4paper -V geometry:margin=2cm --pdf-engine=xelatex -V monofont="DejaVu Sans Mono" --metadata author="Luis Gutiérrez López (Software Libre)" informe_vulnerabilidades.md -o informe_vulnerabilidades.pdf
|
||||
echo
|
||||
echo "Informe Finalizado"
|
||||
echo
|
||||
|
||||
|
||||
223
check_CVEs.py
223
check_CVEs.py
@ -1,223 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import time
|
||||
import json
|
||||
import yaml
|
||||
import requests
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
BASE_URL = "https://euvdservices.enisa.europa.eu/api/search"
|
||||
HEADERS = {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) EUVD-Auditor/1.0"
|
||||
}
|
||||
|
||||
def fetch_fast_enisa_vulnerabilities(vendor, product, from_score=7.0, days_back=30):
|
||||
vulnerabilities = []
|
||||
from_date = (datetime.now() - timedelta(days=days_back)).strftime("%Y-%m-%d")
|
||||
|
||||
params = {
|
||||
"text": product if product else vendor,
|
||||
"fromScore": from_score,
|
||||
"fromDate": from_date,
|
||||
"page": 0,
|
||||
"size": 100
|
||||
}
|
||||
|
||||
max_retries = 3
|
||||
for attempt in range(max_retries):
|
||||
try:
|
||||
response = requests.get(BASE_URL, headers=HEADERS, params=params, timeout=15)
|
||||
|
||||
# Si se supera el rate limit, esperar y reintentar
|
||||
if response.status_code == 429:
|
||||
wait_time = (attempt + 1) * 5
|
||||
print(f" ⚠️ Rate limit (429) alcanzado para {product}. Esperando {wait_time}s...")
|
||||
time.sleep(wait_time)
|
||||
continue
|
||||
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
items = data.get("items", data.get("content", [])) if isinstance(data, dict) else data
|
||||
vulnerabilities.extend(items)
|
||||
break # Éxito, salir del bucle de reintentos
|
||||
|
||||
except Exception as e:
|
||||
if attempt == max_retries - 1:
|
||||
print(f" ❌ Error final tras {max_retries} intentos para {product}: {e}")
|
||||
time.sleep(2)
|
||||
|
||||
return vulnerabilities
|
||||
|
||||
def generate_markdown_report(total_consolidados, total_analizadas, total_aplicables,
|
||||
total_no_aplicables, total_revision, resumen_objetivos,
|
||||
aplicables_severidad, output_file="informe_vulnerabilidades.md"):
|
||||
"""
|
||||
Genera el informe ejecutivo en Markdown utilizando el formato de Bloques / Fichas Ejecutivas,
|
||||
ideal para lectura clara y conversión a PDF mediante Pandoc.
|
||||
"""
|
||||
now_iso = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z"
|
||||
|
||||
md_content = f"""# Informe Ejecutivo de Vulnerabilidades
|
||||
|
||||
**Generado en:** {now_iso} | **Fuentes:** ENISA
|
||||
|
||||
**Grupo de Software Libre - Área de Plataformas**
|
||||
|
||||
* **Informes consolidados:** {total_consolidados}
|
||||
* **Vulnerabilidades analizadas:** {total_analizadas}
|
||||
* **Aplicables:** {total_aplicables}
|
||||
* **No aplicables:** {total_no_aplicables}
|
||||
* **Revisión manual:** {total_revision}
|
||||
|
||||
## Resumen por objetivo
|
||||
|
||||
| Fuente | Producto | Versión | Analizadas | Aplicables | No aplicables | Revisión manual |
|
||||
|---|---|---|---|---|---|---|
|
||||
"""
|
||||
|
||||
for obj in resumen_objetivos:
|
||||
md_content += f"| {obj['Fuente']} | {obj['Producto']} | {obj['Versión']} | {obj['Analizadas']} | {obj['Aplicables']} | {obj['No aplicables']} | {obj['Revisión manual']} |\n"
|
||||
|
||||
md_content += """
|
||||
## Detalle de Vulnerabilidades Aplicables
|
||||
"""
|
||||
|
||||
if not aplicables_severidad:
|
||||
md_content += "\n*No se encontraron vulnerabilidades aplicables con los criterios seleccionados.*\n"
|
||||
else:
|
||||
for app in aplicables_severidad:
|
||||
# Determinar nivel y badge según la puntuación CVSS
|
||||
score_val = app['BaseScore'].split()[0] if app['BaseScore'] else "0.0"
|
||||
try:
|
||||
score_num = float(score_val)
|
||||
except ValueError:
|
||||
score_num = 0.0
|
||||
|
||||
if score_num >= 9.0:
|
||||
badge = "[**CRÍTICA**] - "
|
||||
elif score_num >= 7.0:
|
||||
badge = "[**ALTA**] - "
|
||||
elif score_num >= 4.0:
|
||||
badge = "[**MEDIA**] - "
|
||||
else:
|
||||
badge = "[**BAJA**] - "
|
||||
|
||||
# Formatear la descripción/justificación
|
||||
just = app['Justificación'].strip()
|
||||
|
||||
md_content += f"""
|
||||
|
||||
### {badge} {app['ID']} - {app['CVE']}
|
||||
|
||||
* **Producto / Versión:** `{app['Producto']}` (v`{app['Versión']}`) | **Fuente:** {app['Fuente']}
|
||||
* **BaseScore:** {app['BaseScore']}
|
||||
* **Regla versión:** {app['Regla versión']}
|
||||
* **Justificación / Descripción:**
|
||||
> {just}
|
||||
"""
|
||||
|
||||
with open(output_file, "w", encoding="utf-8") as f:
|
||||
f.write(md_content)
|
||||
|
||||
print(f"📝 Informe Markdown generado en '{output_file}'")
|
||||
|
||||
|
||||
def process_targets(config_path="targets.yaml"):
|
||||
try:
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
config = yaml.safe_load(f)
|
||||
except FileNotFoundError:
|
||||
print(f"❌ Archivo '{config_path}' no encontrado.")
|
||||
return
|
||||
|
||||
resumen_objetivos = []
|
||||
aplicables_severidad = []
|
||||
|
||||
total_consolidados = 0
|
||||
total_analizadas = 0
|
||||
total_aplicables = 0
|
||||
total_no_aplicables = 0
|
||||
total_revision = 0
|
||||
|
||||
print("🚀 Iniciando escaneo ultra-rápido en ENISA EUVD...\n")
|
||||
|
||||
for target in config.get("targets", []):
|
||||
vendor = target.get("vendor", "")
|
||||
product = target.get("product", "")
|
||||
version = target.get("version", "")
|
||||
|
||||
total_consolidados += 1
|
||||
print(f"👉 Analizando: {vendor}/{product} (v{version})")
|
||||
|
||||
items = fetch_fast_enisa_vulnerabilities(vendor=vendor, product=product, from_score=7.0, days_back=30)
|
||||
|
||||
num_analizadas = len(items)
|
||||
num_aplicables = 0
|
||||
num_no_aplicables = 0
|
||||
num_revision = 0
|
||||
|
||||
for item in items:
|
||||
score = item.get("baseScore", 0.0)
|
||||
score_ver = item.get("baseScoreVersion", "3.1")
|
||||
euvd_id = item.get("id", "N/A")
|
||||
|
||||
aliases = item.get("aliases", "")
|
||||
aliases_list = aliases.strip().split("\n") if isinstance(aliases, str) else []
|
||||
cve_id = aliases_list[0] if aliases_list and aliases_list[0] else euvd_id
|
||||
|
||||
desc = item.get("description", "Sin descripción disponible.")
|
||||
desc_short = desc[:180].strip() + "..." if len(desc) > 180 else desc
|
||||
|
||||
num_aplicables += 1
|
||||
aplicables_severidad.append({
|
||||
"Fuente": "ENISA",
|
||||
"ID": euvd_id,
|
||||
"CVE": cve_id,
|
||||
"Producto": product,
|
||||
"Versión": version,
|
||||
"BaseScore": f"{score} {score_ver}".strip(),
|
||||
"Regla versión": f"Afecta rama v{version.split('.')[0]}.x",
|
||||
"Justificación": desc_short
|
||||
})
|
||||
|
||||
total_analizadas += num_analizadas
|
||||
total_aplicables += num_aplicables
|
||||
total_no_aplicables += num_no_aplicables
|
||||
total_revision += num_revision
|
||||
|
||||
resumen_objetivos.append({
|
||||
"Fuente": "ENISA",
|
||||
"Producto": product,
|
||||
"Versión": version,
|
||||
"Analizadas": num_analizadas,
|
||||
"Aplicables": num_aplicables,
|
||||
"No aplicables": num_no_aplicables,
|
||||
"Revisión manual": num_revision
|
||||
})
|
||||
|
||||
print(f" 📊 Encontradas: {num_analizadas} vulnerabilidades relevantes\n")
|
||||
time.sleep(0.5)
|
||||
|
||||
# Ordenar vulnerabilidades aplicables de mayor a menor puntuación CVSS
|
||||
aplicables_severidad.sort(key=lambda x: float(x["BaseScore"].split()[0]) if x["BaseScore"].split()[0].replace('.', '', 1).isdigit() else 0.0, reverse=True)
|
||||
|
||||
# 1. Guardar JSON
|
||||
reporte_json = {
|
||||
"generado_en": datetime.now(timezone.utc).isoformat(),
|
||||
"fuentes": ["ENISA"],
|
||||
"resumen_por_objetivo": resumen_objetivos,
|
||||
"aplicables_por_severidad": aplicables_severidad
|
||||
}
|
||||
with open("reporte_vulnerabilidades.json", "w", encoding="utf-8") as f:
|
||||
json.dump(reporte_json, f, indent=2, ensure_ascii=False)
|
||||
|
||||
# 2. Generar Markdown
|
||||
generate_markdown_report(
|
||||
total_consolidados, total_analizadas, total_aplicables,
|
||||
total_no_aplicables, total_revision, resumen_objetivos,
|
||||
aplicables_severidad
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
process_targets()
|
||||
Loading…
x
Reference in New Issue
Block a user