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):
|
def parse_single_version(v_str):
|
||||||
"""
|
"""
|
||||||
Limpia y extrae la versión semántica limpia de una cadena.
|
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):
|
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.
|
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", "*"]:
|
if not product_version_str or product_version_str.lower() in ["n/a", "unknown", "*"]:
|
||||||
return True, "Versión indeterminada en la fuente (Revisar manualmente)"
|
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}"
|
f"Versión marcada como corregida mediante parche {patch_version}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
if not t_ver:
|
if not t_ver:
|
||||||
if target_version_str.strip().lower() in product_version_str.lower():
|
if target_version_str.strip().lower() in product_version_str.lower():
|
||||||
return True, f"Coincidencia directa por cadena '{product_version_str}'"
|
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
|
## 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:
|
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 += """
|
md_content += """
|
||||||
---
|
---
|
||||||
@ -244,12 +313,13 @@ def generate_markdown_report(total_consolidados, total_analizadas, total_aplicab
|
|||||||
|
|
||||||
just = app['Justificación'].strip()
|
just = app['Justificación'].strip()
|
||||||
score_vector = app.get('BaseScoreVector', 'N/A')
|
score_vector = app.get('BaseScoreVector', 'N/A')
|
||||||
|
parcheo_ind = app.get('Parcheo', 'Diferible')
|
||||||
|
|
||||||
md_content += f"""
|
md_content += f"""
|
||||||
### {badge} - {app['ID']} - {app['CVE']}
|
### {badge} - {app['ID']} - {app['CVE']}
|
||||||
|
|
||||||
* **Producto / Versión:** `{app['Producto']}` (v`{app['Versión']}`) | **Fuente:** {app['Fuente']}
|
* **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}`
|
* **Vector CVSS:** `{score_vector}`
|
||||||
* **Regla versión:** {app['Regla versión']}
|
* **Regla versión:** {app['Regla versión']}
|
||||||
* **Justificación / Descripción:**
|
* **Justificación / Descripción:**
|
||||||
@ -297,6 +367,7 @@ def process_targets(config_path="targets.yaml"):
|
|||||||
num_aplicables = 0
|
num_aplicables = 0
|
||||||
num_no_aplicables = 0
|
num_no_aplicables = 0
|
||||||
num_revision = 0
|
num_revision = 0
|
||||||
|
parcheo_vulnerabilidades_target = []
|
||||||
|
|
||||||
for item in items:
|
for item in items:
|
||||||
score = item.get("baseScore", 0.0) or 0.0
|
score = item.get("baseScore", 0.0) or 0.0
|
||||||
@ -329,6 +400,9 @@ def process_targets(config_path="targets.yaml"):
|
|||||||
|
|
||||||
if is_affected:
|
if is_affected:
|
||||||
num_aplicables += 1
|
num_aplicables += 1
|
||||||
|
parcheo_vuln = evaluar_parcheo_cvss(score_vector)
|
||||||
|
parcheo_vulnerabilidades_target.append(parcheo_vuln)
|
||||||
|
|
||||||
aplicables_severidad.append({
|
aplicables_severidad.append({
|
||||||
"Fuente": "ENISA",
|
"Fuente": "ENISA",
|
||||||
"ID": euvd_id,
|
"ID": euvd_id,
|
||||||
@ -338,7 +412,8 @@ def process_targets(config_path="targets.yaml"):
|
|||||||
"BaseScore": f"{score} {score_ver}".strip(),
|
"BaseScore": f"{score} {score_ver}".strip(),
|
||||||
"BaseScoreVector": score_vector,
|
"BaseScoreVector": score_vector,
|
||||||
"Regla versión": match_rule,
|
"Regla versión": match_rule,
|
||||||
"Justificación": desc_short
|
"Justificación": desc_short,
|
||||||
|
"Parcheo": parcheo_vuln
|
||||||
})
|
})
|
||||||
else:
|
else:
|
||||||
num_no_aplicables += 1
|
num_no_aplicables += 1
|
||||||
@ -348,6 +423,9 @@ def process_targets(config_path="targets.yaml"):
|
|||||||
total_no_aplicables += num_no_aplicables
|
total_no_aplicables += num_no_aplicables
|
||||||
total_revision += num_revision
|
total_revision += num_revision
|
||||||
|
|
||||||
|
# Evaluar el parcheo acumulado para el producto
|
||||||
|
parcheo_global_target = calcular_parcheo_global(parcheo_vulnerabilidades_target)
|
||||||
|
|
||||||
resumen_objetivos.append({
|
resumen_objetivos.append({
|
||||||
"Fuente": "ENISA",
|
"Fuente": "ENISA",
|
||||||
"Producto": product,
|
"Producto": product,
|
||||||
@ -355,10 +433,11 @@ def process_targets(config_path="targets.yaml"):
|
|||||||
"Analizadas": num_analizadas,
|
"Analizadas": num_analizadas,
|
||||||
"Aplicables": num_aplicables,
|
"Aplicables": num_aplicables,
|
||||||
"No aplicables": num_no_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)
|
time.sleep(0.5)
|
||||||
|
|
||||||
reporte_json = {
|
reporte_json = {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user