Il catalogo CISA Known Exploited Vulnerabilities ha superato le 1.739 voci l’8 ottobre 2026, mentre il database NVD del NIST indicizza oltre 404.000 CVE pubblicate dal 1988 a oggi. Nessun team di sicurezza, nemmeno nelle banche o nelle grandi utility, riesce a patchare tutto entro 24 ore. Il problema non è la mancanza di scanner: Nessus, OpenVAS e Qualys producono report con centinaia di voci “critiche” ogni settimana. Il problema è decidere, con dati verificabili, quali di quelle voci vanno chiuse subito e quali possono aspettare il ciclo di patching mensile.

Questa guida costruisce, passo dopo passo, una pipeline Python che unisce tre segnali indipendenti: il punteggio di impatto CVSS 4.0 (o 3.1 dove il 4.0 non è ancora disponibile), la probabilità di exploit stimata da EPSS di FIRST.org e la presenza nel catalogo CISA KEV. Il risultato è uno script completo, pronto da adattare al proprio ambiente, che trasforma un elenco di CVE in una coda di remediation ordinata per priorità reale.

Perché il CVSS da solo non basta più per decidere cosa patchare

Il CVSS (Common Vulnerability Scoring System) misura l’impatto tecnico di una vulnerabilità: quanto è grave se qualcuno la sfrutta. Non misura quanto è probabile che qualcuno la sfrutti davvero. Due vulnerabilità possono avere lo stesso punteggio 9.8 e un destino completamente diverso: una resta teorica per anni, l’altra viene automatizzata in un worm nel giro di settimane.

Un esempio concreto aiuta a capire la distanza tra i due mondi. CVE-2024-3400, la falla in PAN-OS GlobalProtect di Palo Alto Networks, ha un punteggio CVSS 3.1 di 10.0: il massimo teorico. Ma quello che ha davvero spinto migliaia di aziende a patchare entro ore, non settimane, è stato il suo inserimento nel catalogo CISA KEV il 12 aprile 2024, con sfruttamento attivo già documentato e collegato a campagne ransomware. Lo stesso CVSS massimo, senza quel secondo segnale, avrebbe prodotto solo un altro ticket in coda.

Per questo i team di vulnerability management più maturi non guardano più un solo numero. Costruiscono una funzione di priorità che combina impatto tecnico, probabilità di sfruttamento e conferma empirica di exploitation. Le prossime sezioni spiegano ciascuno dei tre pilastri prima di passare al codice.

Il contesto aiuta a capire perché serve automatizzare, non solo spiegare, questa logica. Un’azienda di medie dimensioni riceve tipicamente report di scansione con decine o centinaia di CVE aperte ogni settimana, distribuite su server, workstation, dispositivi di rete e container. Classificare ciascuna CVE a mano, controllando manualmente tre fonti diverse, è un lavoro che nessun analista riesce a sostenere con continuità senza errori o ritardi.

CVSS 4.0 vs CVSS 3.1: cosa cambia per chi deve classificare

FIRST.org ha pubblicato lo standard CVSS 4.0 per correggere i limiti più discussi della versione 3.1, in particolare la sovrapposizione tra metriche di sfruttabilità e metriche di impatto e la scarsa separazione tra gravità tecnica e contesto ambientale. CVSS 4.0 introduce nomenclature nuove come CVSS-B (base), CVSS-BE (base + environmental) e CVSS-BTE (base + threat + environmental), pensate per lasciare che ogni organizzazione aggiusti il punteggio sulla propria esposizione reale, non su un valore teorico uguale per tutti.

Nella pratica, a ottobre 2026 NVD pubblica entrambi i formati in parallelo. Alcuni record 2026, come CVE-2026-74788, riportano già nativamente un vettore CVSS 4.0 con punteggio 8.7 (HIGH) senza un corrispondente 3.1. Altri CVE più vecchie, inclusi i grandi casi KEV del 2024 e del 2025 citati in questa guida, restano ancorati al CVSS 3.1 originale perché nessuno ha calcolato retroattivamente un vettore 4.0. Una pipeline di prioritizzazione corretta deve quindi leggere entrambi i formati e applicare una regola di precedenza esplicita, non assumere che uno dei due sia sempre presente.

La raccomandazione pratica è semplice: se un record NVD espone un vettore CVSS 4.0, usalo come riferimento primario per il punteggio di impatto. Se esiste solo il 3.1, usalo senza convertirlo artificialmente. Le due scale non sono equivalenti punto per punto e una conversione manuale introduce più rumore che segnale.

EPSS: la probabilità di sfruttamento calcolata da FIRST.org

L’Exploit Prediction Scoring System nasce per rispondere alla domanda che il CVSS non pone: quanto è probabile che questa specifica CVE venga sfruttata nei prossimi 30 giorni? Il modello di FIRST.org produce due numeri per ogni CVE, un punteggio EPSS tra 0 e 1 (la probabilità stimata) e un percentile, che indica come quella CVE si posiziona rispetto a tutte le altre CVE conosciute nello stesso momento.

I numeri aggiornati al 10 ottobre 2026, interrogando direttamente l’API pubblica di FIRST.org, mostrano quanto possa essere netta la distanza tra due CVE con impatto tecnico simile. CVE-2024-3400 ha un EPSS di 0,99999 con percentile 100%, la quasi certezza statistica di sfruttamento attivo. CVE-2026-74788, pubblicata il 16 agosto 2026 con un CVSS 4.0 di 8.7, ha invece un EPSS di appena 0,00488 e un percentile del 40,14%: tecnicamente grave, ma statisticamente poco appetibile per un attaccante in questo momento.

Il percentile conta più del punteggio grezzo quando si fissano soglie operative. Molti team di settore trattano il 90° percentile come linea di allerta e il 95°-99° come priorità quasi immediata, ma questi valori non sono una raccomandazione normativa di FIRST.org: vanno calibrati sui propri dati storici di incidenti, non copiati da un blog. L’EPSS si aggiorna ogni giorno, quindi una CVE può salire di priorità da un giorno all’altro senza che il suo CVSS cambi di una virgola.

Un dettaglio che spesso passa inosservato è che EPSS copre, in teoria, qualsiasi CVE pubblicata nel catalogo MITRE, non solo quelle già arrivate su NVD con un punteggio CVSS completo. Questo significa che puoi ottenere un segnale di rischio anche per vulnerabilità ancora “in coda” di arricchimento sul lato NVD.

Il catalogo CISA KEV: il terzo segnale, quello che non si discute

Il catalogo Known Exploited Vulnerabilities della CISA elenca le CVE per cui esiste evidenza documentata di sfruttamento attivo nel mondo reale, non una previsione statistica. Al momento della stesura di questa guida, il file ufficiale pubblicato da CISA riporta la versione 2026.10.08 con 1.739 vulnerabilità totali, rilasciata l’8 ottobre 2026. Ogni voce indica il prodotto coinvolto, la data di aggiunta al catalogo, una scadenza di remediation per le agenzie federali USA e un campo che segnala se la CVE è nota per l’uso in campagne ransomware.

Quattro delle cinque CVE usate come esempio in questa guida compaiono nel catalogo KEV: CVE-2024-3400 (Palo Alto Networks, aggiunta il 12 aprile 2024), CVE-2024-23897 (Jenkins, aggiunta il 19 agosto 2024), CVE-2025-0282 (Ivanti, aggiunta l’8 gennaio 2025) e CVE-2023-4863 (Google, componente libwebp, aggiunta il 13 settembre 2023). Tutte e quattro sono anche collegate a uso ransomware noto o sospetto nel campo dedicato del catalogo, con l’unica eccezione di CVE-2023-4863 segnata come “Unknown” su quel fronte specifico.

La regola operativa che la maggior parte dei framework di settore adotta è: la presenza in KEV batte qualsiasi altro segnale. Non importa se il CVSS è 7.5 invece di 9.8, non importa se l’EPSS è “solo” al 94° percentile. Se CISA ha confermato lo sfruttamento attivo, quella CVE va in testa alla coda di remediation, punto.

Vale la pena ricordare che le scadenze indicate nel campo “due date” del catalogo KEV sono obbligatorie per le agenzie federali statunitensi sotto la direttiva BOD 22-01, non per le aziende private europee. Questo non le rende meno utili: moltissimi team di sicurezza in Italia e nel resto d’Europa le adottano volontariamente come riferimento, perché rappresentano la stima più conservativa e verificata di quanto tempo resta prima che una falla sfruttata attivamente diventi un problema anche per chi non l’ha ancora patchata.

Il campo knownRansomwareCampaignUse dentro al JSON merita un’attenzione particolare quando costruisci la coda di remediation. Un valore “Known” indica che gruppi ransomware hanno già incorporato quella CVE specifica nei loro strumenti di attacco, un livello di rischio distinto anche all’interno della fascia P0. Alcuni team aggiungono un sotto-ordinamento interno: tra due CVE entrambe in KEV, quella collegata a ransomware noto passa davanti, perché il danno potenziale in caso di sfruttamento riuscito è tipicamente più distruttivo di un accesso non autorizzato isolato.

NVD e il programma CVE: due cataloghi diversi, spesso confusi

Prima di scrivere la prima riga di codice è utile chiarire una distinzione che genera confusione anche tra chi lavora in sicurezza da anni. Il programma CVE, gestito da MITRE, assegna gli identificativi univoci (gli ID “CVE-AAAA-NNNNN”) e mantiene la descrizione minima di ogni vulnerabilità. Il National Vulnerability Database del NIST, invece, prende quegli ID e li arricchisce: aggiunge punteggi CVSS, mappature CWE, elenco dei prodotti coinvolti tramite CPE e, più di recente, anche i vettori CVSS 4.0.

La conseguenza pratica per chi scrive una pipeline di prioritizzazione è questa: un CVE ID può esistere ed essere pubblico su MITRE molto prima che NVD lo arricchisca con un punteggio. Nel frattempo, l’API NVD lo restituirà con metriche vuote o assenti, non con un errore. Un buon script deve trattare questo stato come “in attesa di arricchimento”, non come un dato mancante da nascondere, perché una CVE senza punteggio CVSS non è automaticamente meno pericolosa: semplicemente non è stata ancora classificata.

Prerequisiti: strumenti, versioni e account necessari

Prima di scrivere codice, prepara l’ambiente. La tabella seguente elenca tutto quello che serve, con le versioni verificate al momento della pubblicazione di questa guida.

ComponenteVersione consigliataNote
Python3.12 o superiore (testato su 3.13 e 3.14)Richiede il modulo venv incluso nella standard library
requests2.34.2Client HTTP usato per tutte le chiamate API
Chiave API NVDGratuita, richiesta via emailSenza chiave: 5 richieste ogni 30 secondi. Con chiave: 50 ogni 30 secondi
Connessione a api.first.orgNessuna chiave richiestaL’API pubblica EPSS è accessibile senza registrazione
Connessione a cisa.govNessuna chiave richiestaIl feed KEV è un file JSON pubblico, aggiornato più volte a settimana
Spazio discoMeno di 5 MBIl catalogo KEV completo pesa pochi megabyte in formato JSON

Per richiedere la chiave API NVD visita la pagina ufficiale dei developer NIST e compila il modulo con un’email aziendale o istituzionale. L’approvazione è quasi sempre automatica e arriva via email in pochi minuti, non giorni.

Passo 1 e 2: creare la chiave API NVD e l’ambiente Python

Passo 1: registrare la chiave API

Vai sulla pagina ufficiale “NVD Developers” del NIST, inserisci la tua email e attendi il messaggio di conferma. La chiave arriva come stringa alfanumerica da salvare come variabile d’ambiente, mai dentro al codice sorgente.

Passo 2: preparare l’ambiente virtuale

Crea una cartella di progetto dedicata e isola le dipendenze con un ambiente virtuale, così non sporchi l’installazione Python di sistema.

mkdir cve-priority && cd cve-priority
python3 -m venv venv
source venv/bin/activate   # su Windows: venv\Scripts\activate

pip install requests==2.34.2

export NVD_API_KEY="la-tua-chiave-qui"
# su Windows PowerShell: $env:NVD_API_KEY="la-tua-chiave-qui"

Verifica che tutto funzioni con un test rapido della variabile d’ambiente prima di proseguire. Uno script che non trova la chiave fallisce con errori 403 difficili da diagnosticare più avanti nella pipeline.

Passo 3 e 4: scaricare il catalogo KEV e indicizzarlo

Il catalogo CISA KEV è un singolo file JSON pubblico, senza bisogno di autenticazione. La strategia corretta è scaricarlo una volta per esecuzione e trasformarlo in un dizionario Python indicizzato per CVE ID, così la verifica di appartenenza diventa un confronto istantaneo invece di scansionare 1.739 voci ogni volta.

# kev_lookup.py
import requests

KEV_URL = "https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json"

def load_kev_catalog(timeout=15):
    """Scarica il catalogo KEV e lo indicizza per CVE ID."""
    resp = requests.get(KEV_URL, timeout=timeout)
    resp.raise_for_status()
    data = resp.json()

    catalog = {}
    for entry in data["vulnerabilities"]:
        catalog[entry["cveID"]] = {
            "vendor": entry.get("vendorProject"),
            "product": entry.get("product"),
            "date_added": entry.get("dateAdded"),
            "due_date": entry.get("dueDate"),
            "ransomware_use": entry.get("knownRansomwareCampaignUse", "Unknown"),
        }

    print(f"Catalogo KEV versione {data.get('catalogVersion')}: "
          f"{len(catalog)} vulnerabilita indicizzate")
    return catalog


if __name__ == "__main__":
    kev = load_kev_catalog()
    print("CVE-2024-3400 in KEV:", "CVE-2024-3400" in kev)

Eseguendo lo script oggi otterrai un output simile a “Catalogo KEV versione 2026.10.08: 1739 vulnerabilita indicizzate”, seguito da “CVE-2024-3400 in KEV: True”. Il numero di voci cambia nel tempo: CISA aggiunge nuove CVE più volte a settimana, quindi non salvare questo dato come costante nel tuo codice.

Passo 5 e 6: interrogare l’API EPSS di FIRST.org in batch

L’API EPSS accetta più CVE in una singola richiesta separandole con una virgola nel parametro cve, con un limite massimo di 100 risultati per chiamata secondo la documentazione di FIRST.org. Per liste più lunghe, dividi l’elenco in blocchi da 100 prima di iterare.

# epss_client.py
import requests

EPSS_URL = "https://api.first.org/data/v1/epss"
BATCH_SIZE = 100

def chunked(lst, size):
    for i in range(0, len(lst), size):
        yield lst[i:i + size]

def fetch_epss_scores(cve_ids, timeout=15):
    """Ritorna un dict {cve_id: {"epss": float, "percentile": float}}."""
    results = {}
    for batch in chunked(cve_ids, BATCH_SIZE):
        params = {"cve": ",".join(batch)}
        resp = requests.get(EPSS_URL, params=params, timeout=timeout)
        resp.raise_for_status()
        payload = resp.json()

        for row in payload.get("data", []):
            results[row["cve"]] = {
                "epss": float(row["epss"]),
                "percentile": float(row["percentile"]),
            }
    return results


if __name__ == "__main__":
    sample = ["CVE-2024-3400", "CVE-2026-74788"]
    scores = fetch_epss_scores(sample)
    for cve, s in scores.items():
        print(cve, "EPSS:", round(s["epss"] * 100, 3), "%",
              "| percentile:", round(s["percentile"] * 100, 2), "%")

Una CVE pubblicata da pochi giorni può non apparire ancora nei risultati EPSS: il modello ha bisogno di tempo per raccogliere segnali e assegnare un punteggio stabile. Gestisci sempre il caso “CVE non trovata nella risposta” assegnando un valore predefinito basso, non un errore bloccante.

Passo 7 e 8: recuperare i punteggi CVSS da NVD rispettando i rate limit

L’API NVD 2.0 impone limiti stretti: 5 richieste ogni finestra di 30 secondi senza chiave, 50 con chiave. Superarli produce risposte 403 o 429. La funzione seguente include una pausa fissa tra le chiamate e un retry con backoff esponenziale, oltre alla logica per scegliere tra CVSS 4.0 e 3.1 quando entrambi sono presenti nel record.

# nvd_client.py
import os
import time
import requests

NVD_URL = "https://services.nvd.nist.gov/rest/json/cves/2.0"
API_KEY = os.environ.get("NVD_API_KEY")
SLEEP_BETWEEN_CALLS = 0.65 if API_KEY else 6.5  # 50/30s vs 5/30s

def _pick_cvss(metrics):
    """Preferisce CVSS 4.0, poi 3.1, poi 3.0, poi 2.0."""
    for key in ("cvssMetricV40", "cvssMetricV31", "cvssMetricV30", "cvssMetricV2"):
        if key in metrics:
            m = metrics[key][0]["cvssData"]
            return {
                "version": m["version"],
                "score": m["baseScore"],
                "severity": m.get("baseSeverity"),
            }
    return {"version": None, "score": None, "severity": None}

def fetch_cvss(cve_id, max_retries=4):
    headers = {"apiKey": API_KEY} if API_KEY else {}
    params = {"cveId": cve_id}

    for attempt in range(max_retries):
        resp = requests.get(NVD_URL, params=params, headers=headers, timeout=20)
        if resp.status_code == 200:
            data = resp.json()
            vulns = data.get("vulnerabilities", [])
            if not vulns:
                return {"version": None, "score": None, "severity": None}
            metrics = vulns[0]["cve"].get("metrics", {})
            time.sleep(SLEEP_BETWEEN_CALLS)
            return _pick_cvss(metrics)

        if resp.status_code in (403, 429):
            wait = (2 ** attempt) * 5
            print(f"Rate limit su {cve_id}, attendo {wait}s (tentativo {attempt+1})")
            time.sleep(wait)
            continue

        resp.raise_for_status()

    raise RuntimeError(f"Impossibile recuperare {cve_id} dopo {max_retries} tentativi")

Con una chiave API attiva, 50 richieste ogni 30 secondi significano circa 100 CVE al minuto in condizioni normali: sufficiente per la maggior parte dei cicli di patching mensili. Per inventari più grandi, valuta di salvare i risultati in una cache locale (anche un semplice file SQLite) ed eseguire la pipeline in modalità incrementale, interrogando solo le CVE nuove rispetto all’esecuzione precedente.

Passo 9 e 10: il motore di scoring e la coda di remediation

A questo punto hai tre fonti di dati indipendenti per ogni CVE: presenza in KEV, punteggio EPSS con percentile, punteggio CVSS con versione. Il motore di scoring le combina in un’unica etichetta di priorità, seguendo la regola discussa nelle sezioni precedenti: KEV sempre al primo posto, poi EPSS, poi CVSS come ultimo criterio di impatto tecnico.

# priority_engine.py
from kev_lookup import load_kev_catalog
from epss_client import fetch_epss_scores
from nvd_client import fetch_cvss
import csv

EPSS_HIGH_PERCENTILE = 0.90  # calibra questa soglia sui tuoi dati storici
CVSS_CRITICAL = 9.0

def classify(cve_id, kev_catalog, epss_data, cvss_data):
    in_kev = cve_id in kev_catalog
    epss = epss_data.get(cve_id, {}).get("epss", 0.0)
    percentile = epss_data.get(cve_id, {}).get("percentile", 0.0)
    cvss_score = cvss_data.get("score") or 0.0

    if in_kev:
        priority = "P0 - Immediata (confermata sfruttata)"
    elif percentile >= EPSS_HIGH_PERCENTILE:
        priority = "P1 - Alta (probabilita exploit elevata)"
    elif cvss_score >= CVSS_CRITICAL:
        priority = "P2 - Pianificata (impatto critico, basso rischio osservato)"
    else:
        priority = "P3 - Ciclo normale"

    return {
        "cve": cve_id,
        "cvss_version": cvss_data.get("version"),
        "cvss_score": cvss_score,
        "epss": round(epss * 100, 3),
        "percentile": round(percentile * 100, 2),
        "in_kev": in_kev,
        "ransomware": kev_catalog.get(cve_id, {}).get("ransomware_use", "N/A"),
        "priority": priority,
    }

def run_pipeline(cve_list, output_csv="priority_queue.csv"):
    kev_catalog = load_kev_catalog()
    epss_data = fetch_epss_scores(cve_list)

    rows = []
    for cve_id in cve_list:
        cvss_data = fetch_cvss(cve_id)
        rows.append(classify(cve_id, kev_catalog, epss_data, cvss_data))

    rows.sort(key=lambda r: r["priority"])

    with open(output_csv, "w", newline="") as f:
        writer = csv.DictWriter(f, fieldnames=rows[0].keys())
        writer.writeheader()
        writer.writerows(rows)

    print(f"Report salvato in {output_csv} ({len(rows)} CVE classificate)")
    return rows


if __name__ == "__main__":
    cve_list = [
        "CVE-2024-3400", "CVE-2024-23897",
        "CVE-2025-0282", "CVE-2023-4863", "CVE-2026-74788",
    ]
    run_pipeline(cve_list)

Nota la riga rows.sort(key=lambda r: r[“priority”]): funziona perché le etichette iniziano con “P0”, “P1”, “P2”, “P3” e l’ordinamento alfabetico coincide con l’ordine di urgenza. Se rinomini le etichette, aggiorna anche la chiave di ordinamento di conseguenza.

Prima di collegare questo motore a dati di produzione, vale la pena scrivere un paio di test con dati finti per congelare il comportamento atteso delle regole di priorità. Non serve un framework complesso: anche una funzione minima con assert individua rapidamente un errore di logica prima che finisca in un report inviato al team.

# test_priority_engine.py
from priority_engine import classify

def test_kev_vince_su_tutto():
    kev = {"CVE-TEST-0001": {"ransomware_use": "Known"}}
    epss = {"CVE-TEST-0001": {"epss": 0.01, "percentile": 0.10}}
    cvss = {"version": "3.1", "score": 4.0}
    r = classify("CVE-TEST-0001", kev, epss, cvss)
    assert r["priority"].startswith("P0")

def test_epss_alto_senza_kev():
    kev = {}
    epss = {"CVE-TEST-0002": {"epss": 0.95, "percentile": 0.96}}
    cvss = {"version": "3.1", "score": 6.5}
    r = classify("CVE-TEST-0002", kev, epss, cvss)
    assert r["priority"].startswith("P1")

if __name__ == "__main__":
    test_kev_vince_su_tutto()
    test_epss_alto_senza_kev()
    print("Tutti i test sono passati")

Questi due casi bastano a verificare la parte più delicata della logica: che una CVE in KEV salga sempre a P0 anche con CVSS ed EPSS bassi, e che una CVE con EPSS alto ma assente da KEV finisca comunque in P1 invece di scivolare in fondo alla coda. Aggiungi un terzo test ogni volta che modifichi le soglie, così non rischi di rompere silenziosamente la logica durante una futura revisione dello script.

Passo 11 e 12: automatizzare l’esecuzione e distribuire il report

Una pipeline di prioritizzazione ha valore solo se viene eseguita con regolarità, non una volta e poi dimenticata. L’opzione più semplice su Linux è un cron job giornaliero o settimanale, che alimenta lo script con la lista di CVE aperte estratta dal tuo scanner (Nessus, OpenVAS, Qualys o equivalente).

# Esegue la pipeline ogni giorno alle 06:00 e salva un log
0 6 * * * cd /opt/cve-priority && /opt/cve-priority/venv/bin/python priority_engine.py >> /var/log/cve-priority.log 2>&1

Per ambienti con systemd, un timer offre controllo più granulare (ritardi randomizzati, dipendenze da altri servizi, log centralizzato via journalctl) rispetto a una riga crontab. In entrambi i casi, il passo finale è collegare l’output CSV a un canale che il team guarda davvero: un webhook Slack per le righe P0, un allegato email settimanale per il riepilogo completo, o un’importazione automatica nel sistema di ticketing.

Separa sempre la generazione del report dalla sua notifica. Uno script che fallisce a metà non deve inviare un alert con dati parziali o fuorvianti: scrivi il CSV solo dopo che tutte le CVE della lista sono state classificate con successo.

Esempio di output: la pipeline testata su 5 CVE reali

Per mostrare la pipeline in azione, ecco il risultato ottenuto interrogando le API live di NVD, FIRST.org e CISA KEV l’11 ottobre 2026 per cinque CVE pubbliche e ben documentate.

CVECVSSEPSSPercentileIn KEVPriorità assegnata
CVE-2024-3400 (Palo Alto)3.1: 10.0 CRITICAL99,999%100%Sì (12/04/2024)P0 – Immediata
CVE-2024-23897 (Jenkins)3.1: 9.8 CRITICAL99,999%99,99%Sì (19/08/2024)P0 – Immediata
CVE-2025-0282 (Ivanti)3.1: 9.0 CRITICAL99,979%99,98%Sì (08/01/2025)P0 – Immediata
CVE-2023-4863 (Google libwebp)3.1: 8.8 HIGH99,979%99,98%Sì (13/09/2023)P0 – Immediata
CVE-2026-747884.0: 8.7 HIGH0,488%40,14%NoP2 – Pianificata

Il contrasto nell’ultima riga è il punto centrale di questa guida. CVE-2026-74788 ha un CVSS 4.0 di 8.7, un punteggio che su molti dashboard tradizionali basati solo sul CVSS genererebbe un alert “critico” identico a quello delle prime quattro righe. I dati EPSS e KEV raccontano una storia diversa: nessuna evidenza di sfruttamento attivo, probabilità statistica sotto l’1%. Nella coda di remediation generata dallo script, questa CVE finisce correttamente in fondo, non in testa.

5 errori comuni nella prioritizzazione delle vulnerabilità

  • Confondere EPSS con probabilità di danno. EPSS stima la probabilità di sfruttamento nei prossimi 30 giorni, non la gravità del danno che ne deriverebbe. Una CVE con EPSS alto ma impatto tecnico basso resta meno urgente di una con EPSS medio e impatto critico su un sistema esposto.
  • Riscaricare il catalogo KEV ad ogni singola CVE controllata. Il file va scaricato una sola volta per esecuzione e tenuto in memoria come dizionario. Scaricarlo decine di volte in un ciclo spreca banda e rischia di far scattare limiti di rete lato CISA.
  • Assumere che ogni record NVD abbia sia CVSS 4.0 che 3.1. Molti CVE più vecchie hanno solo il 3.1, molte 2026 hanno solo il 4.0. Un codice che presuppone sempre entrambi i campi va in errore silenzioso o restituisce punteggi nulli senza avviso.
  • Interrogare NVD senza gestire i rate limit. Senza chiave API, 5 richieste ogni 30 secondi si esauriscono in pochi secondi su una lista di 50 CVE. Il risultato sono blocchi 403 intermittenti che fanno sembrare lo script rotto quando il problema è solo la velocità delle chiamate.
  • Fissare una soglia EPSS copiata da un articolo esterno. Il 90° o il 95° percentile sono punti di partenza ragionevoli, non regole universali. Ogni organizzazione dovrebbe validare la propria soglia confrontandola con gli incidenti reali degli ultimi 12-24 mesi.
  • Interpretare l’assenza da KEV come sicurezza garantita. Non essere nel catalogo significa che CISA non ha ancora confermato sfruttamento attivo, non che la vulnerabilità sia innocua. L’EPSS e il CVSS restano segnali validi anche per le CVE fuori da KEV.

Troubleshooting: 8 problemi frequenti e come risolverli

  • Errore 403 Forbidden su ogni chiamata a NVD. Controlla che la variabile d’ambiente NVD_API_KEY sia effettivamente letta dallo script e passata nell’header apiKey, non nell’URL come parametro di query.
  • Errore 404 interrogando CVE per intervallo di date. L’API NVD 2.0 accetta un intervallo massimo di 120 giorni tra pubStartDate e pubEndDate. Intervalli più lunghi vanno divisi in più chiamate consecutive.
  • L’API EPSS risponde con un array vuoto per una CVE specifica. La CVE è troppo recente e il modello non ha ancora dati sufficienti. Assegna un punteggio predefinito basso e rilancia la query nei giorni successivi.
  • Lo script resta bloccato per minuti su liste grandi. È il comportamento atteso senza chiave API (6,5 secondi di pausa per chiamata). Richiedi la chiave gratuita per salire a 50 richieste ogni 30 secondi.
  • Il campo CVSS risulta vuoto per una CVE valida. Controlla il campo vulnStatus nel record: le CVE in stato “Awaiting Analysis” o “Received” non hanno ancora un punteggio NVD assegnato. Gestisci questo caso come priorità da rivalutare, non come errore.
  • Il numero di voci nel catalogo KEV non coincide con quanto letto in un articolo. Il catalogo cambia più volte a settimana. Confronta sempre il campo catalogVersion e dateReleased del JSON scaricato con la data della tua esecuzione, non con un numero fisso trovato altrove.
  • Connessioni che falliscono dietro un proxy aziendale. Configura esplicitamente le variabili d’ambiente HTTPS_PROXY e aumenta il timeout delle richieste requests, che di default può interrompersi troppo presto su reti con ispezione SSL.
  • Risultati diversi tra due esecuzioni della stessa lista di CVE nello stesso giorno. È normale per EPSS, che si aggiorna giornalmente, ma non per KEV o CVSS nello stesso giorno. Se cambiano questi ultimi due, verifica di non avere cache locali obsolete che si sovrappongono ai dati live.

Consigli avanzati per team di sicurezza maturi

Una volta che la pipeline base funziona e passa i test, ci sono margini concreti di miglioramento prima di definirla pronta per la produzione. Nessuno di questi passaggi è obbligatorio per iniziare, ma ognuno riduce il lavoro manuale che resta anche dopo aver automatizzato la classificazione.

  • Aggiungi il contesto dell’asset. Una CVE P2 su un server esposto a Internet con dati sensibili merita un trattamento diverso dalla stessa CVE su una macchina isolata in una rete di test. Integra un campo “esposizione” proveniente dal tuo CMDB o dal tool di asset discovery prima di finalizzare la priorità, così la stessa CVE può generare due priorità diverse su due macchine diverse.
  • Valuta un framework decisionale complementare come SSVC. Oltre a KEV, EPSS e CVSS, alcuni team adottano uno schema ad albero decisionale che incorpora anche fattori come la presenza di un exploit pubblico e la missione critica del sistema colpito, per arrivare a una decisione tracciabile e documentata, non solo a un punteggio numerico isolato.
  • Automatizza l’apertura dei ticket per le righe P0. Collega lo script a Jira o ServiceNow via API: ogni CVE classificata come immediata genera automaticamente un ticket con scadenza, invece di aspettare che qualcuno legga il report CSV e decida manualmente di agire.
  • Tieni uno storico delle classificazioni. Salvare ogni esecuzione in una tabella, anche SQLite, permette di misurare quanto tempo passa realmente tra la classificazione P0 e la chiusura effettiva della patch, un dato prezioso per gli audit di conformità e per dimostrare a un revisore che il processo non è solo teorico.
  • Ricalibra le soglie ogni trimestre. Le minacce cambiano e un valore EPSS che oggi sembra alto potrebbe non esserlo più dentro sei mesi, man mano che il modello di FIRST.org si aggiorna con nuovi dati di sfruttamento raccolti su scala globale.
  • Distingui tra CVE pubbliche e vulnerabilità interne. Questa pipeline lavora bene su CVE pubbliche con identificativo ufficiale. Per bug trovati internamente da un pentest o da un bug bounty privato, serve un sistema di tracking separato finché non ricevono, se mai la ricevono, una CVE assegnata da MITRE.

CVSS 4.0, EPSS e KEV a confronto: cosa misura ciascun segnale

Prima di chiudere con le domande frequenti, una tabella riassuntiva aiuta a fissare la differenza tra i tre segnali usati in questa pipeline, utile anche come riferimento rapido da condividere con il resto del team.

SegnaleCosa misuraFonteAggiornamento
CVSS 4.0 / 3.1Impatto tecnico teorico se la vulnerabilità viene sfruttataNVD (NIST), basato su standard FIRST.orgStatico, salvo revisione manuale
EPSSProbabilità stimata di sfruttamento nei prossimi 30 giorniFIRST.org, modello statisticoGiornaliero
CISA KEVConferma empirica di sfruttamento attivo osservatoCISA (governo USA)Più volte a settimana

Nessuno dei tre segnali, da solo, basta a costruire una strategia di patching responsabile. Insieme, e automatizzati in una pipeline ripetibile come quella descritta in questa guida, trasformano una lista di centinaia di CVE in una manciata di decisioni chiare da prendere ogni settimana.

Il progetto completo descritto in questa guida, dai quattro moduli Python ai test di validazione, può partire anche su un singolo laptop di sviluppo prima di finire in produzione su un server dedicato. Non servono licenze: NVD, FIRST.org e CISA pubblicano tutti i dati necessari come servizi pubblici gratuiti, finanziati da fondi governativi e da un consorzio internazionale di organizzazioni di sicurezza. L’unico lavoro che resta davvero a carico del team è decidere le soglie giuste per il proprio contesto, e verificarle nel tempo contro gli incidenti reali.

Domande frequenti

Cos’è l’EPSS e in cosa differisce dal CVSS?

Il CVSS misura quanto sarebbe grave una vulnerabilità se venisse sfruttata. L’EPSS, calcolato da FIRST.org, stima invece quanto è probabile che venga sfruttata nei prossimi 30 giorni. Sono due domande diverse e vanno letti insieme, non uno al posto dell’altro.

CVSS 4.0 sostituisce completamente CVSS 3.1 su NVD?

No. A ottobre 2026, NVD pubblica entrambi i formati in parallelo a seconda della CVE. Alcuni record recenti hanno solo il vettore 4.0, molti record più vecchi mantengono solo il 3.1 originale. Una pipeline corretta deve gestire entrambi i casi.

Serve davvero una chiave API NVD per questo script?

Non è obbligatoria, ma senza chiave il limite scende a 5 richieste ogni 30 secondi, rendendo la pipeline molto lenta su liste di CVE superiori a una decina di voci. La chiave è gratuita e l’approvazione è quasi sempre immediata.

Quanto spesso va aggiornato il catalogo CISA KEV nella pipeline?

Il file ufficiale cambia più volte a settimana. Scaricarlo ad ogni esecuzione giornaliera della pipeline, come previsto nello script di questa guida, è sufficiente per restare aggiornati senza sovraccaricare i server CISA.

Cosa significa se una CVE non è in KEV ma ha un EPSS molto alto?

Significa che il modello statistico di FIRST.org considera probabile uno sfruttamento imminente, anche se CISA non ha ancora confermato casi reali documentati. Nello schema di priorità proposto in questa guida, questa situazione rientra nella fascia “P1 – Alta” e merita attenzione rapida, anche senza la conferma KEV.

Questa pipeline sostituisce uno scanner di vulnerabilità come Nessus o OpenVAS?

No. Lo scanner resta lo strumento che scopre quali CVE sono presenti nel tuo ambiente. Questa pipeline prende quell’elenco come input e lo riordina per urgenza reale, combinando segnali che la maggior parte degli scanner non integra nativamente tra loro.

Come scelgo la soglia EPSS giusta per la mia azienda?

Parti da un valore di riferimento come il 90° percentile, poi confrontalo per due o tre mesi con gli incidenti e gli alert reali che la tua organizzazione riceve. Se la soglia genera troppi falsi allarmi, alzala. Se mancano exploit reali che poi si sono materializzati, abbassala.

Posso adattare questa pipeline a un ambiente cloud o Kubernetes?

Sì. La logica di scoring (KEV, EPSS, CVSS) resta identica. Cambia solo la fonte dell’elenco di CVE in ingresso, che in un ambiente container arriva tipicamente da uno scanner di immagini come Trivy o Grype invece che da un tool di network scanning tradizionale.

Cosa succede se una CVE esce poi dal catalogo KEV?

Succede raramente, ma CISA può rimuovere voci dal catalogo in casi specifici. Se la tua pipeline gira su base giornaliera, la CVE scomparirà automaticamente dal set KEV alla prossima esecuzione e verrà riclassificata solo in base a EPSS e CVSS. Per questo è importante non salvare mai la lista KEV come file statico: deve essere scaricata fresca a ogni esecuzione, come previsto nello script di questa guida.