Incluyo columna 'Necesidad Actualizacion'
This commit is contained in:
parent
2c789e9d45
commit
d30a0163a9
105
check_EUVD.py
105
check_EUVD.py
@ -14,6 +14,70 @@ HEADERS = {
|
||||
}
|
||||
|
||||
|
||||
def evaluar_parcheo_cvss(vector_str):
|
||||
"""
|
||||
Evalúa el vector CVSS v3.x y devuelve la calificación de Parcheo:
|
||||
'Urgente', 'Diferible' o 'Descartable'.
|
||||
|
||||
Reglas aplicadas:
|
||||
- Descartable: AV:L / AV:P o PR:H
|
||||
- Urgente: AV:N + AC:L + PR:N + UI:N y además (C:H o I:H o A:H o S:C)
|
||||
- Diferible: Resto de casos
|
||||
"""
|
||||
if not vector_str or vector_str == "N/A":
|
||||
return "Diferible"
|
||||
|
||||
# Convertir el vector en un diccionario de componentes (ej: {'AV': 'N', 'AC': 'L', ...})
|
||||
metrics = {}
|
||||
parts = vector_str.split('/')
|
||||
for part in parts:
|
||||
if ':' in part:
|
||||
k, v = part.split(':', 1)
|
||||
metrics[k.upper()] = v.upper()
|
||||
|
||||
av = metrics.get('AV', '')
|
||||
ac = metrics.get('AC', '')
|
||||
pr = metrics.get('PR', '')
|
||||
ui = metrics.get('UI', '')
|
||||
s = metrics.get('S', '')
|
||||
c = metrics.get('C', '')
|
||||
i = metrics.get('I', '')
|
||||
a = metrics.get('A', '')
|
||||
|
||||
# 1. Reglas de Descartable
|
||||
if av in ['L', 'P'] or pr == 'H':
|
||||
return "Descartable"
|
||||
|
||||
# 2. Reglas de Urgente
|
||||
es_remoto_facil = (av == 'N' and ac == 'L' and pr == 'N' and ui == 'N')
|
||||
impacto_alto = (c == 'H' or i == 'H' or a == 'H' or s == 'C')
|
||||
|
||||
if es_remoto_facil and impacto_alto:
|
||||
return "Urgente"
|
||||
|
||||
# 3. Resto de escenarios -> Diferible
|
||||
return "Diferible"
|
||||
|
||||
|
||||
def calcular_parcheo_global(lista_parcheo_vulnerabilidades):
|
||||
"""
|
||||
Determina la calificación de Parcheo del producto según la máxima criticidad encontrada:
|
||||
Si hay al menos una Urgente -> Urgente
|
||||
Sino, si hay al menos una Diferible -> Diferible
|
||||
Sino, si todas son Descartables -> Descartable
|
||||
Sin vulnerabilidades aplicables -> Sin Acción
|
||||
"""
|
||||
if not lista_parcheo_vulnerabilidades:
|
||||
return "Sin Acción"
|
||||
|
||||
if "Urgente" in lista_parcheo_vulnerabilidades:
|
||||
return "Urgente"
|
||||
elif "Diferible" in lista_parcheo_vulnerabilidades:
|
||||
return "Diferible"
|
||||
else:
|
||||
return "Descartable"
|
||||
|
||||
|
||||
def parse_single_version(v_str):
|
||||
"""
|
||||
Limpia y extrae la versión semántica limpia de una cadena.
|
||||
@ -34,11 +98,6 @@ def parse_single_version(v_str):
|
||||
def matches_product_version(target_version_str, product_version_str):
|
||||
"""
|
||||
Evalúa matemáticamente si target_version_str se ve afectada por product_version_str.
|
||||
Soporta:
|
||||
- Expresiones con operadores: "<19.1.1", "<=19.1.1", ">19.0", ">=19.0"
|
||||
- Rangos combinados: "19.1 <19.1.1", "19.1 <19.2.3"
|
||||
- Rangos con palabra "to": "1.7.0 to 1.7.10"
|
||||
- Coincidencia exacta o comodines '*'
|
||||
"""
|
||||
if not product_version_str or product_version_str.lower() in ["n/a", "unknown", "*"]:
|
||||
return True, "Versión indeterminada en la fuente (Revisar manualmente)"
|
||||
@ -69,7 +128,6 @@ def matches_product_version(target_version_str, product_version_str):
|
||||
f"Versión marcada como corregida mediante parche {patch_version}"
|
||||
)
|
||||
|
||||
|
||||
if not t_ver:
|
||||
if target_version_str.strip().lower() in product_version_str.lower():
|
||||
return True, f"Coincidencia directa por cadena '{product_version_str}'"
|
||||
@ -204,12 +262,23 @@ def generate_markdown_report(total_consolidados, total_analizadas, total_aplicab
|
||||
|
||||
## Resumen por objetivo
|
||||
|
||||
| Fuente | Producto | Versión | Analizadas | Aplicables | No aplicables | Revisión manual |
|
||||
|---|---|---|---|---|---|---|
|
||||
| Fuente | Producto | Versión | Analizadas | Aplicables | No aplicables | Revisión manual | Necesidad Parcheo |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
"""
|
||||
|
||||
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"
|
||||
parcheo_val = obj.get('Parcheo', 'Sin Acción')
|
||||
# Formato visual destacado según urgencia
|
||||
if parcheo_val == "Urgente":
|
||||
parcheo_str = "**URGENTE**"
|
||||
elif parcheo_val == "Diferible":
|
||||
parcheo_str = "Diferible"
|
||||
elif parcheo_val == "Descartable":
|
||||
parcheo_str = "Descartable"
|
||||
else:
|
||||
parcheo_str = "Sin Acción"
|
||||
|
||||
md_content += f"| {obj['Fuente']} | {obj['Producto']} | {obj['Versión']} | {obj['Analizadas']} | {obj['Aplicables']} | {obj['No aplicables']} | {obj['Revisión manual']} | {parcheo_str} |\n"
|
||||
|
||||
md_content += """
|
||||
---
|
||||
@ -244,12 +313,13 @@ def generate_markdown_report(total_consolidados, total_analizadas, total_aplicab
|
||||
|
||||
just = app['Justificación'].strip()
|
||||
score_vector = app.get('BaseScoreVector', 'N/A')
|
||||
parcheo_ind = app.get('Parcheo', 'Diferible')
|
||||
|
||||
md_content += f"""
|
||||
### {badge} - {app['ID']} - {app['CVE']}
|
||||
|
||||
* **Producto / Versión:** `{app['Producto']}` (v`{app['Versión']}`) | **Fuente:** {app['Fuente']}
|
||||
* **BaseScore:** {app['BaseScore']}
|
||||
* **BaseScore:** {app['BaseScore']} | **Parcheo Vulnerabilidad:** **{parcheo_ind}**
|
||||
* **Vector CVSS:** `{score_vector}`
|
||||
* **Regla versión:** {app['Regla versión']}
|
||||
* **Justificación / Descripción:**
|
||||
@ -297,6 +367,7 @@ def process_targets(config_path="targets.yaml"):
|
||||
num_aplicables = 0
|
||||
num_no_aplicables = 0
|
||||
num_revision = 0
|
||||
parcheo_vulnerabilidades_target = []
|
||||
|
||||
for item in items:
|
||||
score = item.get("baseScore", 0.0) or 0.0
|
||||
@ -329,6 +400,9 @@ def process_targets(config_path="targets.yaml"):
|
||||
|
||||
if is_affected:
|
||||
num_aplicables += 1
|
||||
parcheo_vuln = evaluar_parcheo_cvss(score_vector)
|
||||
parcheo_vulnerabilidades_target.append(parcheo_vuln)
|
||||
|
||||
aplicables_severidad.append({
|
||||
"Fuente": "ENISA",
|
||||
"ID": euvd_id,
|
||||
@ -338,7 +412,8 @@ def process_targets(config_path="targets.yaml"):
|
||||
"BaseScore": f"{score} {score_ver}".strip(),
|
||||
"BaseScoreVector": score_vector,
|
||||
"Regla versión": match_rule,
|
||||
"Justificación": desc_short
|
||||
"Justificación": desc_short,
|
||||
"Parcheo": parcheo_vuln
|
||||
})
|
||||
else:
|
||||
num_no_aplicables += 1
|
||||
@ -348,6 +423,9 @@ def process_targets(config_path="targets.yaml"):
|
||||
total_no_aplicables += num_no_aplicables
|
||||
total_revision += num_revision
|
||||
|
||||
# Evaluar el parcheo acumulado para el producto
|
||||
parcheo_global_target = calcular_parcheo_global(parcheo_vulnerabilidades_target)
|
||||
|
||||
resumen_objetivos.append({
|
||||
"Fuente": "ENISA",
|
||||
"Producto": product,
|
||||
@ -355,10 +433,11 @@ def process_targets(config_path="targets.yaml"):
|
||||
"Analizadas": num_analizadas,
|
||||
"Aplicables": num_aplicables,
|
||||
"No aplicables": num_no_aplicables,
|
||||
"Revisión manual": num_revision
|
||||
"Revisión manual": num_revision,
|
||||
"Parcheo": parcheo_global_target
|
||||
})
|
||||
|
||||
print(f" Analizadas: {num_analizadas} | Aplicables: {num_aplicables} | Descartadas por versión: {num_no_aplicables}\n")
|
||||
print(f" Analizadas: {num_analizadas} | Aplicables: {num_aplicables} | Descartadas por versión: {num_no_aplicables} | Parcheo: {parcheo_global_target}\n")
|
||||
time.sleep(0.5)
|
||||
|
||||
reporte_json = {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user