2026-03-19 | Pinperepette

Il Bug Diventa Arma

Quanto tempo passa tra una CVE e il primo exploit pubblico? NVD + ExploitDB, un join su CVE ID, un delta in giorni. 6,530 match. Dopo aver rimosso il backfill NVD, il 34% ha exploit pre-disclosure. Tra i delta positivi, la massa e' nei primi 7 giorni.

CVE NVD ExploitDB Data Analysis

// Il Numero Che Non Guardi

Sezione 00. La domanda sbagliata

Tutti guardano il CVSS. E' il primo numero che cerchi quando esce una CVE. 9.8, critico, panico, patch immediata. 4.3, medio, ci pensiamo lunedi'. E' cosi' che funziona, in teoria.

In pratica, il CVSS ti dice quanto e' grave un bug. Non ti dice quanto velocemente qualcuno lo trasformera' in un exploit.

La domanda che nessuno fa e' questa: quanto tempo ho?

Non quanto e' grave. Quanto tempo passa tra il momento in cui una vulnerabilita' diventa pubblica e il momento in cui qualcuno la arma. Il delta. Il numero che decide se patchi in tempo o se sei gia' dentro la finestra di esposizione.

I dati per rispondere esistono. Sono pubblici. NVD ha le date di pubblicazione di ogni CVE dal 1999. ExploitDB ha le date degli exploit. Basta un join su CVE ID e un'operazione aritmetica.

Ma c'e' un dettaglio che cambia tutto: la data di pubblicazione su NVD non e' la data in cui il bug viene scoperto. E' la data in cui la burocrazia lo registra. L'exploit non aspetta la burocrazia.

L'ho fatto. Su 215,581 CVE e 24,924 exploit. Il risultato non e' quello che mi aspettavo.

Tesi. Una vulnerabilita' non e' un rischio quando viene scoperta. Lo diventa quando qualcuno la arma. I dati dicono che in un terzo dei casi, l'arma esiste gia' quando la CVE esce. E quando non esiste, arriva in giorni, non settimane. Il tempo che misuriamo e' quello pubblico. Quello reale e' sempre piu' corto.

// I Dataset

Sezione 01. Le due fonti

Servono due cose. Le date di nascita delle CVE e le date di nascita degli exploit. Tutto il resto e' rumore.

NIST NVD

Il National Vulnerability Database e' l'estensione analitica del MITRE CVE. Ogni entry contiene: CVE ID, data di pubblicazione, descrizione, CVSS score, vettore di attacco. L'API 2.0 e' pubblica, restituisce JSON, paginata a blocchi di 2000 risultati. Rate limit: 5 richieste ogni 30 secondi senza API key.

Quello che ci serve: cve_id e published_date. Fine.

Exploit Database

ExploitDB e' il mirror piu' grande di exploit pubblici. Il repository GitLab contiene un file CSV, files_exploits.csv, con ogni exploit indicizzato: ID, data di pubblicazione, piattaforma, e un campo codes che contiene i CVE ID associati.

Quello che ci serve: cve_id (estratto da codes con regex) e exploit_date. Fine.

01
NVD
CVE ID + data pubblicazione
02
ExploitDB
CVE ID + data exploit
03
JOIN
Match per CVE ID
04
Dt
exploit_date - published_date

Bias dichiarato. ExploitDB non copre tutto. Gli exploit privati, quelli venduti nei mercati underground, quelli usati da APT: non ci sono. Quello che misuriamo e' il tempo pubblico. Quello reale e' piu' corto. Questo non invalida l'analisi. La rende una stima conservativa.

// La Pipeline

Sezione 02. Quattro script, zero fuffa

Quattro script Python. Nessuna dipendenza esotica. requests per le API, csv per i dati, matplotlib per i grafici. Tutto replicabile in 20 minuti.

Script 01: Fetch NVD

Scarica tutte le CVE pubblicate tra 2014 e 2024 dall'API NVD 2.0. Paginazione automatica, retry su errore, rispetta il rate limit. Output: un CSV con due colonne.

1# 01_fetch_nvd.py: il cuore e' qui
2params = {
3 'pubStartDate': '2014-01-01T00:00:00.000',
4 'pubEndDate': '2024-12-31T23:59:59.999',
5 'resultsPerPage': 2000,
6 'startIndex': start_index
7}
8r = requests.get(API_URL, params=params)
9# Estrai: cve_id, published[:10]
$ python 01_fetch_nvd.py [*] Fetch CVE da NVD API 2.0 [*] Range: 2014-01-01 -> 2024-12-31 [*] Chunk: 120 giorni per query [+] Salvate 215,581 CVE in data/nvd_cves.csv

Script 02: Fetch ExploitDB

Scarica il CSV completo dal mirror GitLab. Parsa il campo codes con una regex CVE-\d{4}-\d{4,}. Un exploit puo' mappare piu' CVE: li splitta. Output: un CSV con due colonne.

1# 02_fetch_exploitdb.py: regex sul campo codes
2cve_pattern = re.compile(r'CVE-\d{4}-\d{4,}')
3for row in reader:
4 cve_ids = cve_pattern.findall(row['codes'])
5 for cve_id in cve_ids:
6 rows.append((cve_id, date_published))
$ python 02_fetch_exploitdb.py [*] Download files_exploits.csv da ExploitDB... [+] Trovati 24,924 mapping CVE->exploit [+] Salvato in data/exploitdb_cves.csv

Script 03: Match e Delta

Carica i due CSV. Inner join su CVE ID. Per ogni match: Dt = exploit_date - published_date. Se una CVE ha piu' exploit, prende il primo (la data piu' vecchia). Output: CSV con quattro colonne.

1# 03_match_and_delta.py: l'operazione che conta
2for cve_id, pub_date in nvd.items():
3 if cve_id in exploits:
4 delta_days = (exploits[cve_id] - pub_date).days
$ python 03_match_and_delta.py [*] NVD: 215,581 CVE con data [*] ExploitDB: 24,924 CVE con exploit [+] Match: 6,530 CVE con exploit pubblico --- Statistiche --- Pre-disclosure (delta < 0): 3,077 (47.1%) Same-day (delta = 0): 281 (4.3%) Entro 7 giorni: 1,505 (23.0%) Entro 30 giorni: 2,201 (33.7%) Mediana: 0 giorni

Il numero chiave. Il 47.1% delle CVE matchate ha delta negativo. Ma attenzione: una parte sono backfill, CVE pubblicate su NVD anni dopo che il bug era noto. Se filtri il rumore (delta < -365 giorni), restano 5,257 match puliti. E anche li', il 34% ha delta negativo: exploit pubblicato nei 12 mesi prima della CVE. La mediana della vista pulita resta vicina a zero.

// I Numeri

Sezione 03. Tre grafici, zero decorazione

Lo script 04_plot.py genera tre grafici. Non dashboard, non infografiche. Tre visualizzazioni che rispondono a tre domande.

Grafico 1: Istogramma del tempo di weaponization

Due pannelli, uno per lato dello zero. A sinistra: le CVE con exploit pre-disclosure (da -365 a 0 giorni). A destra: le CVE con exploit post-pubblicazione (da 0 a 365 giorni). Le linee verticali marcano 7, 30 e 90 giorni.

A destra il picco e' schiacciato verso lo zero: la massa degli exploit arriva nei primissimi giorni. Ma il pannello sinistro e' denso: 1,804 CVE hanno exploit pubblicato nei 30 giorni prima della CVE. Non e' un'anomalia. E' il ricercatore che carica il PoC prima che NVD formalizzi.

Istogramma tempo di weaponization CVE verso exploit

Grafico 2: CDF: entro quanti giorni sei fregato

La CDF e' sulla vista pulita: esclude il backfill NVD (delta < -365) e mostra la distribuzione da -365 a +365 giorni. La linea verticale a zero marca il momento della pubblicazione CVE. Tutto cio' che sta a sinistra e' exploit pre-disclosure.

I marker segnano le soglie chiave:

Finestra % CVE (vista pulita) Traduzione
delta <= 0 ~40% Exploit gia' presente alla pubblicazione CVE
entro 7 giorni ~55% Piu' della meta'
entro 30 giorni ~68% Due su tre
entro 90 giorni ~78% Tre su quattro
CDF cumulativa tempo exploit

Grafico 3: Outliers: i casi estremi

Il terzo grafico scompone il dataset in tre viste:

Breakdown per categorie. Il pannello sinistro separa backfill NVD (delta < -365 giorni, CVE pubblicate anni dopo il bug), pre-disclosure reale (-365..0 giorni, exploit prima della formalizzazione CVE), same-day, e le fasce post-pubblicazione. La categoria piu' pesante dopo il backfill e' la pre-disclosure reale: 1,804 CVE con exploit pubblicato nei 30 giorni prima della CVE.

Top 20 exploit piu' veloci. Il pannello centrale mostra le CVE con delta positivo piu' basso, quelle armate in 1-2 giorni dalla pubblicazione. Questi sono i casi in cui la weaponization e' stata quasi istantanea: advisory esce, exploit segue nelle ore successive.

Pre-disclosure reale. Il pannello destro zooma sulla distribuzione da -365 a 0 giorni. Il picco e' concentrato vicino a zero: il grosso degli exploit pre-disclosure arriva nei 30 giorni prima della CVE. Sono ricercatori che caricano il PoC prima che NVD formalizzi. La linea a -30 giorni mostra quanti cadono in quella finestra. Esempio tipico: un ricercatore trova una SQL injection, scrive il PoC, lo carica su ExploitDB. Il vendor viene notificato, patcha, e solo dopo MITRE assegna il CVE ID. Il bug era gia' patchato, il PoC gia' pubblico. Ma per NVD non esisteva ancora.

Outliers pre-disclosure same-day coda lunga

// Le CVE Che Contano Davvero

Sezione 04. CISA KEV: da possibilita' a realta'

Finora abbiamo misurato cosa e' possibile: una CVE ha un exploit pubblico su ExploitDB. Ma avere un exploit non significa che qualcuno lo stia usando. ExploitDB e' pieno di PoC accademici che nessun attaccante ha mai toccato.

C'e' un terzo dataset che cambia il significato dei numeri: la Known Exploited Vulnerabilities (KEV) di CISA. Non e' un archivio di vulnerabilita'. E' una lista curata di CVE osservate in attacchi attivi nel mondo reale. Se una CVE e' nella KEV, qualcuno la sta usando in produzione.

Se ExploitDB misura cosa e' possibile, KEV misura cosa sta gia' succedendo.

05
KEV
CVE sfruttate in the wild
06
Incrocio
KEV ∩ ExploitDB ∩ NVD

Il catalogo KEV contiene 1,543 CVE. Di queste, 333 hanno anche un exploit pubblico su ExploitDB nel nostro dataset. Sono poche, ma sono quelle con evidenza pubblica sia di exploit che di uso reale.

La domanda: per queste 333 CVE, l'exploit pubblico esisteva gia' prima che CISA le aggiungesse al catalogo?

$ python 05_fetch_kev.py [+] 1,543 CVE nel catalogo KEV $ python 06_kev_analysis.py [*] KEV: 1,543 CVE attivamente sfruttate [+] KEV con exploit pubblico: 333 (21.6%) --- Tempo exploit -> KEV --- Exploit pubblico PRIMA di KEV: 296 (88.9%) Mediana exploit->KEV: 1,071 giorni Exploit pubblico > 30d prima: 291 (87.4%) Exploit pubblico > 1 anno prima: 263 (79.0%)

Il numero che taglia. Su 333 CVE KEV con exploit pubblico, l'88.9% aveva l'exploit disponibile su ExploitDB prima che CISA le aggiungesse al catalogo. Mediana: 1,071 giorni. Non e' il tempo dell'attacco. E' il ritardo con cui viene riconosciuto. L'exploit era pubblico, scaricabile, per quasi tre anni prima che il sistema dicesse "questa e' sotto attacco attivo".

Metrica Valore Traduzione
exploit prima di KEV 88.9% L'exploit era gia' pubblico
mediana exploit->KEV 1,071 giorni Quasi 3 anni di esposizione
> 1 anno prima 79.0% Quattro su cinque: exploit pubblico da oltre un anno
CISA KEV analisi incrociata con ExploitDB

Non tutte le vulnerabilita' con exploit vengono usate. Ma quelle nella KEV si'. E per queste, l'exploit non era un segreto. Era pubblico. Da anni.

Posizionamento corretto. KEV non e' una metrica temporale pura. E' un catalogo curato, biasato verso high-impact, e non ha date precise di primo sfruttamento. Quello che KEV aggiunge e' un filtro di rilevanza: separa i PoC accademici dalle armi usate in produzione. I numeri sopra non dicono "gli attacchi arrivano dopo 1,071 giorni". Dicono: "quando CISA conferma l'attacco, l'exploit era pubblico da 1,071 giorni".

// Cosa Significano

Sezione 05. I numeri non parlano da soli

Il primo istinto e' dire "il 47% ha delta negativo, e' tutto pre-disclosure". Non e' cosi' semplice. Bisogna separare il segnale dal rumore.

Su 3,077 CVE con delta negativo, 1,273 hanno delta inferiore a -365 giorni. Sono backfill: NVD ha pubblicato la CVE anni dopo che il bug era gia' noto e sfruttato. CVE-2005-4891 ha delta -5,321 giorni: exploit del 2005, CVE pubblicata nel 2020. Non e' pre-disclosure, e' burocrazia in ritardo.

Tolto il backfill, restano 5,257 match. E qui i numeri sono puliti.

Vista pulita (5,257 CVE, escluso backfill NVD). Il 34% ha exploit pre-disclosure reale (da -365 a 0 giorni). Sono ricercatori che caricano il PoC su ExploitDB prima che NVD formalizzi la CVE. Il bug era noto, l'exploit circolava, la burocrazia non aveva ancora assegnato il numero. Sulla vista pulita, entro 7 giorni dalla CVE hai gia' oltre la meta' degli exploit disponibili.

Tre insight che escono dai dati:

1. La data NVD non e' la data del bug. La published_date su NVD e' un evento burocratico, non tecnico. La vulnerabilita' viene scoperta, comunicata al vendor, a volte patchata. Solo dopo, NVD pubblica. L'exploit non aspetta la burocrazia. Ecco perche' il delta e' spesso negativo: non perche' l'attaccante e' piu' veloce, ma perche' NVD e' piu' lento. Lo scenario tipico: il ricercatore trova il bug, scrive il PoC, lo carica su ExploitDB o GitHub. Giorni o settimane dopo, MITRE assegna il CVE ID e NVD pubblica. Delta negativo. Non significa che gli attaccanti siano piu' veloci del sistema. Significa che la CVE arriva dopo che il bug e' gia' pubblico.

2. Il CVSS non sembra predire la velocita'. Nei dati non emerge una correlazione chiara tra CVSS score e tempo di weaponization. Il CVSS misura l'impatto teorico, non l'attrattivita' per chi scrive exploit. I fattori che accelerano la weaponization sono probabilmente altri: disponibilita' del codice sorgente, complessita' dell'exploit, valore del target. Servirebbero dati CVSS per ogni CVE matchata per dimostrarlo, ed e' un possibile follow-up.

3. La finestra reale e' cortissima. Se guardi solo i delta positivi (exploit dopo la CVE), la concentrazione nei primi 7 giorni e' schiacciante. Non hai settimane per patchare. Hai giorni. E questo e' il tempo pubblico. Quello privato e' piu' corto.

// I Limiti

Sezione 06. Quello che non vediamo

Questo non e' un paper accademico e non finge di esserlo. I dati hanno limiti precisi.

Copertura. ExploitDB non ha tutti gli exploit del mondo. Ha quelli pubblici, quelli che qualcuno ha caricato. Gli exploit privati (quelli usati da APT, quelli venduti a broker come Zerodium, quelli che girano nei forum chiusi) non ci sono. La distribuzione reale e' spostata a sinistra: il tempo vero e' piu' corto di quello che misuriamo.

Backfill NVD. 1,273 CVE hanno delta inferiore a -365 giorni. NVD ha pubblicato queste entry anni dopo che il bug era noto. Non sono pre-disclosure. Sono ritardo burocratico. Li escludiamo dalla vista pulita, ma li documentiamo perche' raccontano un problema reale: NVD non e' un sistema real-time.

Date. La "data di pubblicazione" su ExploitDB e' la data di upload, non necessariamente la data in cui l'exploit e' stato scritto o usato per la prima volta. Potrebbe essere stato usato settimane prima. Stesso discorso per la data CVE: la vulnerabilita' potrebbe essere stata scoperta e comunicata al vendor mesi prima della pubblicazione pubblica.

Matching. Il join su CVE ID e' pulito ma non completo. Alcuni exploit su ExploitDB non hanno il CVE ID nel campo codes. Altri hanno CVE ID sbagliati. I falsi negativi esistono.

La frase giusta. "Su 6,530 CVE con exploit pubblico, 1,273 sono backfill NVD. Delle 5,257 restanti, il 34% ha exploit pre-disclosure. Tra quelle con delta positivo, la massa si concentra nei primi 7 giorni. Il tempo reale e' probabilmente piu' corto."

// Il Lab

Sezione 07. Replicalo

Tutto il codice e' nella cartella scripts/il-bug-diventa-arma.

0
Script Python
0
Output visivi
0
Dataset pubblici
0
Machine Learning
$ pip install requests matplotlib numpy $ python 01_fetch_nvd.py [+] Salvate 215,581 CVE in data/nvd_cves.csv $ python 02_fetch_exploitdb.py [+] Salvato in data/exploitdb_cves.csv $ python 03_match_and_delta.py [+] Match: 6,530 CVE con exploit pubblico $ python 04_plot.py [+] 01_histogram.png [+] 02_cdf.png [+] 03_outliers.png $ python 05_fetch_kev.py [+] 1,543 CVE nel catalogo KEV $ python 06_kev_analysis.py [+] KEV con exploit pubblico: 333 (21.6%) [+] 04_kev.png

Lo script NVD richiede tempo (rate limit). ExploitDB e' immediato. Il matching e il plot richiedono secondi. Nessuna API key obbligatoria.

// Conclusione

Fine trasmissione

Non ho fatto machine learning. Non ho fatto clustering. Non ho fatto dashboard interattive. Ho preso tre dataset pubblici, li ho incrociati su una chiave comune, e ho fatto una sottrazione.

Il risultato non e' il numero che mi aspettavo. Il 47% con delta negativo sembrava una bomba. Poi ho separato il backfill NVD dal segnale vero. Il dato pulito e' meno sensazionale ma piu' onesto: il 34% ha exploit pre-disclosure reale, e tra i delta positivi la concentrazione nei primi giorni e' schiacciante. Quando ho aggiunto CISA KEV, il quadro e' peggiorato: per le CVE sotto attacco reale, l'exploit era pubblico da quasi tre anni prima che qualcuno lo riconoscesse.

La finestra di patching esiste. Ma non e' quella che pensi.

E il numero che dovresti guardare non e' il CVSS. E' il tempo.

"Il problema non e' quando scoprono il bug.
E' quanto ci mettono a trasformarlo in arma."

I bug sono rumore. I dataset rivelano il pattern.