Scanner AI e debito tecnico: il conto è arrivato
Gli scanner di sicurezza basati su AI trasformano bug dormienti in exploit critici. Come fare triage delle vulnerabilità emerse senza affogare.

Nel 2024 una segnalazione di bug è finita nel tracker del progetto PHP. Descriveva una condizione di esaurimento della memoria, del tipo che fa crollare un processo: fastidioso, ma nella gerarchia informale dei bug quasi una scrollata di spalle. È rimasta lì. Poi, durante un audit indipendente su PHP finanziato da OpenAI, un ricercatore della società di sicurezza Calif, lavorando con scanner di sicurezza basati su AI e con la sua solida esperienza, ha impiegato circa due settimane a trasformare quel crash fastidioso in esecuzione di codice remoto. Qualsiasi server con una build vulnerabile di PHP poteva essere costretto a eseguire codice dell'attaccante o a rivelare dati sensibili. Tra i siti nel raggio d'azione c'erano NASA, Wikipedia e la Casa Bianca, perché WordPress e Drupal girano su PHP, e PHP gira su una frazione sorprendente del web.
La patch è stata rilasciata prima che qualcuno di malintenzionato ci arrivasse, quindi la storia è finita bene. Ma i ricercatori coinvolti continuano a ripetere la stessa cosa: questo è un inizio, non una fine. Quattro decenni di codice scritto da esseri umani contengono un oceano di bug che abbiamo collettivamente catalogato come «crash, non correggere», e gli strumenti basati su AI hanno abbattuto il costo di scoprire quali di quei crash siano in realtà armi cariche. Nelle ultime settimane ho parlato con persone che si occupano della sicurezza di sistemi reali per capire cosa cambia davvero nella pratica. Quello che segue non è un sondaggio: è una serie di errori che ho già visto fare ai team, e di cosa hanno fatto invece le persone che hanno fatto le cose giuste.
Errore numero uno: catalogare i bug di tipo crash come «fastidiosi ma innocui»
Il bug di PHP al centro di questa storia non era nascosto. È stato segnalato, riconosciuto e declassato di priorità, perché «causava solo» un errore di esaurimento della memoria. Questo è il fallimento di triage più comune nelle organizzazioni software consolidate, e lo sviluppo di exploit assistito dall'AI lo ha appena reso costoso. Suman Jana, professore alla Columbia che ha lavorato proprio su questa classe di ricerche, ha descritto la dinamica senza giri di parole durante le interviste sulla divulgazione: in ogni momento ogni grande progetto ha un mucchio di segnalazioni di crash che nessuno prende sul serio, perché trasformare un crash in un exploit richiede lunghe catene di ragionamenti noiosi e iperspecifici. Gli umani evitano questo lavoro. I modelli non si annoiano.
Ecco la versione meccanica del motivo per cui la distinzione conta. Un bug di memoria che corrompe l'heap con dati non controllati di solito si limita a uccidere il processo: l'allocatore si accorge di qualcosa di incoerente e interrompe l'esecuzione, oppure il processo dereferenzia spazzatura e va in segmentation fault. Ma lo stesso bug, alimentato con un input dimensionato e modellato con cura, diventa una primitiva di scrittura:
void handle_request(const char *input, size_t len) {
char *buf = malloc(64);
memcpy(buf, input, len); /* len never validated */
/* len = 65: heap metadata trashed -> likely abort, filed as 'crash' */
/* len = attacker-tuned: adjacent object's function pointer overwritten */
process(buf); /* calls through the corrupted pointer -> their code runs */
free(buf);
}
La linea che separa questi due esiti non è il bug, è l'input. Capire quali bit sovrascrivere, in quale ordine, per far atterrare l'esecuzione su un indirizzo scelto è un lavoro estenuante ma meccanico, e «estenuante ma meccanico» è esattamente il cuore di ciò in cui i modelli linguistici sono bravi ad assistere. La correzione per questo errore è prima concettuale che tecnica: smettete di usare «si limita a crashare» come indicatore di gravità per il codice con gestione della memoria non sicura. Un crash in C o C++ (o nel nucleo C di un interprete come PHP) è un exploit che non è ancora stato sviluppato. La nuova domanda di triage non è «va in crash?», ma «la corruzione è influenzabile dall'attaccante?». Se un input non attendibile controlla la dimensione, l'offset o il contenuto di una scrittura errata, considerate lo sfruttamento come probabile e date la priorità di conseguenza, anche quando nessuno ha ancora dimostrato un exploit funzionante. Soprattutto in quel caso, perché oggi gli exploit dimostrati richiedono settimane, non anni.
Errore numero due: trattare l'output dello scanner come un backlog
Il secondo punto critico emerge nel momento in cui un team esegue davvero scanner moderni assistiti dall'AI su un patrimonio di codice che include codice vecchio di vent'anni. Lo strumento restituisce centinaia di risultati, la dashboard diventa rossa e il team fa ciò che i backlog gli hanno insegnato a fare: ordinare per punteggio di gravità e partire dall'alto. Sei mesi dopo hanno sistemato quaranta cose, il conteggio è salito e il morale è a terra. Gli ingegneri con cui ho parlato che gestiscono bene la situazione hanno detto più o meno la stessa cosa: l'output di uno scanner AI non è un backlog, è intelligence grezza, e richiede una fase di triage da parte di un analista prima che venga aperto un singolo ticket.
Un triage efficace in questo scenario si pone domande sul percorso di sfruttamento, non domande sul punteggio di gravità:
- Il codice vulnerabile è raggiungibile da input non attendibili? Un heap overflow in una funzione che elabora solo un file di configurazione locale è una bestia diversa dallo stesso overflow nella gestione delle richieste.
- È esposto a internet oggi? Non «dovrebbe esserlo»: lo è. Controllate le configurazioni del load balancer e il DNS, non il diagramma dell'architettura.
- L'input controlla la corruzione? Dimensione, offset o contenuto dell'accesso errato. Questa è la linea tra crash ed exploit di cui si parlava nella sezione precedente.
- Cosa ottiene un attaccante con un exploit? Un crash in un worker sandboxato che si riavvia è diverso da uno nel processo che detiene le credenziali del database.
- Esiste un exploit in circolazione o un articolo tecnico pubblico? Questo porta qualsiasi cosa in cima alla coda, indipendentemente dal punteggio.
Quest'ultimo punto merita enfasi. Punteggi come CVSS sono stati progettati per un mondo in cui gli umani trovavano bug a ritmo umano, e sovrappesano sistematicamente la gravità teorica rispetto alla raggiungibilità. Quando uno scanner può generare gravità teorica su scala industriale, si affoga. I team che se la cavano meglio hanno invertito la pipeline: prima raggiungibilità ed esposizione, poi gravità, e tutto ciò che è irraggiungibile per costruzione viene documentato e chiuso anziché corretto, perché una correzione rilasciata porta con sé il proprio rischio di regressione, e quel budget di rischio vi servirà per i problemi che contano. È anche qui che i controlli di comprensione del codice basati su AI dimostrano il loro valore sul fronte difensivo: lo stesso strumento che trova il bug può spesso rispondere in pochi minuti a una domanda come «mostrami ogni percorso di chiamata dal confine di rete a questa funzione», una domanda che prima richiedeva a un ingegnere senior una giornata per ogni problema.

Errore numero tre: presumere che le patch possano andare alla velocità con cui si trovano i problemi
Ecco un'asimmetria che dovrebbe essere stampata su un poster in ogni stanza di ogni team di sicurezza. Trovare il percorso di exploit del bug di PHP ha richiesto a un solo ricercatore esperto, con l'aiuto dell'AI, circa due settimane. Sviluppare la patch ha richiesto quattro ingegneri per due mesi. Trovare è un problema di ricerca: serve un solo percorso attraverso il codice. Correggere è un problema di tipo dimostrativo: servono tutti i percorsi chiusi, e i miliardi di installazioni che non sono sotto attacco devono continuare a funzionare esattamente come prima. Il lavoro del difensore è strettamente più difficile di quello dell'attaccante, e l'AI riduce il divario dal lato dell'attaccante molto più velocemente di quanto lo faccia dal lato del difensore.
I team che ignorano questa asimmetria fanno una di due scommesse sbagliate. O affrettano le patch per tenere il passo con la pressione della divulgazione e rilasciano regressioni, perché la patch di PHP ha toccato gli interni dell'interprete usati da circa tre quarti dei siti che dichiarano il loro stack, quindi il raggio d'azione di una correzione sbagliata è enorme; oppure si scoraggiano e non fanno nulla in attesa di una correzione perfetta. Chi ha gestito bene questa finestra ha fatto qualcosa di meno eroico e più efficace: ha separato «mitigare ora» da «correggere bene». Le mitigazioni sono economiche, rapide e reversibili: limitare l'endpoint interessato sul reverse proxy, distribuire una regola WAF che blocchi la forma dell'input dell'exploit, disattivare il percorso di codice vulnerabile dietro un feature flag, ridurre chi può raggiungere il servizio mentre la patch definitiva matura. Ho già scritto di come la decisione tra patch e mitigazione sia un vero compromesso e non un fallimento morale, e questo episodio è quella lezione su scala planetaria.
Il corollario che nessuno vuole sentire: il vostro tempo medio di mitigazione è ormai la metrica che conta, perché la finestra tra «bug trovato dai white hat» e «bug trovato da qualcuno di peggiore» si sta riducendo. Nel caso di PHP la finestra è rimasta aperta solo perché i ricercatori sono stati responsabili e la fondazione si è mossa in fretta. La libreria di compressione Brotli ha raccontato una storia meno lusinghiera nello stesso periodo: un bug comparabile è rimasto nel codice per otto anni, è stato corretto in git ma non rilasciato per otto mesi, ed è stato riscoperto in modo indipendente da almeno quattro ricercatori. Quando la scoperta è economica e parallela, «prima o poi qualcuno lo troverà» smette di essere un pensiero confortante e diventa una garanzia di pianificazione.
Errore numero quattro: fidarsi della correzione solo perché l'ha scritta un LLM
Il controargomento ottimista a tutto questo suona così: va bene, oggi l'AI trova bug più velocemente di quanto riusciamo a correggerli, ma l'AI li correggerà anche. Anthropic ha rilasciato a ottobre strumenti proprio in questa direzione: un LLM genera una patch, poi un secondo modello genera una dimostrazione verificabile automaticamente che la patch preserva il comportamento previsto, e un piccolo verificatore di dimostrazioni, semplice e affidabile, convalida la dimostrazione. Il verificatore è l'unico componente che deve essere corretto, e può esserlo, perché fa quasi nulla. Jana è davvero entusiasta di questa direzione, e capisco perché: è il primo percorso credibile per rendere la correzione quasi automatizzabile quanto la scoperta.
Ma ho visto due team adottare flussi di lavoro di «patch di sicurezza generate dall'AI», e entrambi hanno fatto lo stesso errore: hanno trattato la patch del modello come una soluzione anziché come un candidato. In un caso la patch ha chiuso l'heap overflow segnalato aggiungendo un controllo sulla lunghezza, e ha spostato l'overflow su un secondo percorso di allocazione a sei righe di distanza che il modello non aveva considerato. La correzione è passata la revisione perché sembrava ovviamente corretta, ed è esattamente questo il problema: le patch generate da LLM sono fluide in un modo in cui quelle umane non lo sono, e la fluidità riduce lo scrutinio. I team che hanno tratto valore dalle patch generate hanno aggiunto tre controlli, in quest'ordine:
- Una suite di regressione che includa l'input dell'exploit. La proof-of-concept che ha fatto scattare la segnalazione diventa un test permanente. Se lo scanner vi ha fornito un input che causa crash, quello è il vostro primo nuovo caso di test, senza costi aggiuntivi.
- Una caccia alle varianti. Prima del merge, qualcuno (o qualcosa) risponde alla domanda: dove altro compare questo stesso schema? Correggere un'istanza di uno schema sistemico è il modo in cui si ottengono i sequel delle CVE.
- Verifica indipendente dell'invariante, non del diff. Dichiarate cosa deve essere vero da ora in poi, ad esempio «nessun percorso dalla gestione delle richieste raggiunge memcpy con una lunghezza non validata», e verificate quello, con test, analisi statica o strumenti formali. Rileggere solo il diff significa rileggere i compiti del modello.
La verifica formale aiuterà in questo senso man mano che maturerà, ma notate la divisione dei compiti che la rende affidabile: le parti creative possono avere allucinazioni, perché l'arbitro finale è meccanico e piccolo. È questo il principio di progettazione da copiare, a prescindere dai vostri strumenti. Qualsiasi cosa produca un LLM (scoperta, patch o dimostrazione) dovrebbe concludersi in qualcosa che non condivide le sue modalità di errore. Se il vostro passaggio di verifica è un altro prompt, non avete verificato niente.

Errore numero cinque: presumere che qualcun altro abbia verificato il codice
Sotto ogni lezione tecnica c'è una lezione economica. PHP è infrastruttura alla scala di un servizio pubblico: alimenta WordPress, Drupal e una lunga coda del web che include agenzie spaziali e siti di governi, e la sua sicurezza si regge sulla PHP Foundation, un piccolo ente finanziato, più i volontari. Il comodo mito dell'open source è sempre stato «con abbastanza occhi, ogni bug è superficiale». Per trent'anni, in realtà, tutti hanno presunto che quegli occhi appartenessero a qualcun altro. Ora l'AI fornisce gli occhi, senza distinzioni, sia agli attaccanti sia ai difensori, il che significa che il mito si sta finalmente avverando, in modo avversariale e con una scadenza.
La divulgazione di PHP2Shell ha funzionato grazie a un accordo insolito: OpenAI ha finanziato un audit esterno, una società ben attrezzata ha svolto il lavoro e Wiz ha contribuito a confermare ed estendere i risultati. È carità, non un sistema, e non scala fino alle migliaia di progetti critici ma privi di finanziamenti presenti nella vostra catena di dipendenze. Se la produzione della vostra organizzazione dipende da un interprete, un framework o una libreria open source, e così è, le soluzioni che contano sono noiose: finanziate le fondazioni, sponsorizzate gli audit, contribuite con ore di ingegneria e, quando eseguite i vostri scanner e trovate qualcosa in una dipendenza, segnalatelo a monte invece di applicare una patch al vostro fork e andare avanti. L'alternativa è il mondo previsto da Matthew Green della Johns Hopkins: nulla cambia strutturalmente finché un incidente abbastanza grave da turbare le persone normali non lo impone. Il suo consiglio nel frattempo è poco scintillante e corretto: quando compare il prompt di aggiornamento, installatelo subito, non la settimana prossima. Abbiamo imparato la stessa lezione nel modo difficile durante l'ultimo grande ciclo di divulgazioni su PHP, quando staccare la spina ha battuto le soluzioni ingegnose per i team che non riuscivano a patchare abbastanza in fretta.
Gli occhi sono arrivati. Solo che non sono schizzinosi su quale parte stiano.
Il playbook del lunedì mattina per le vulnerabilità emerse grazie all'AI
Tutto quanto detto sopra si riduce a una breve lista di cose che farei davvero questo trimestre se gestissi un'organizzazione ingegneristica con codice legacy in produzione, e se state leggendo questo, di codice legacy in produzione ne avete:
- Patchate in modo aggressivo e misurate quanto tempo ci mettete. La latenza dei vostri aggiornamenti è ormai un controllo di sicurezza con una soglia di sicurezza che si riduce. Conoscete il vostro numero prima di averne bisogno.
- Ririsolvete il backlog dei bug di tipo crash esistenti usando la domanda sullo sfruttabilità, non il punteggio di gravità: l'input non attendibile controlla la corruzione? Tutto ciò che è di memoria non sicura e raggiungibile va promosso.
- Eseguite voi stessi scanning assistito dall'AI, partendo dal codice più vecchio. Il costo marginale della riscoperta è ormai quasi zero, il che significa che dovete presumere che qualsiasi cosa rintracciabile nella vostra superficie d'attacco verrà trovata. Trovatela prima voi.
- Separate la mitigazione dalla correzione nel vostro processo di gestione degli incidenti. Preparate in anticipo il playbook: regole del proxy, feature flag, limitazione degli accessi. Guadagnare tempo è una strategia legittima; fingere di non averne bisogno no.
- Subordinate le patch generate a test sugli input degli exploit e a caccie alle varianti, mai all'eleganza del diff.
- Investite denaro o ore nei progetti upstream da cui dipendete. Audit sponsorizzati hanno trovato questo problema. Non è una coincidenza: è il modello che funziona, e deve smettere di essere eccezionale.
C'è un dibattito legittimo sul fatto che questa era stia «pagando il debito tecnico» o ci stia incastrando in un tapis roulant senza fine di lavori di correzione privi di nuove funzionalità. Probabilmente entrambe le cose: ogni patch non banale porta con sé il rischio del prossimo bug sottile, e nessun sistema è mai finito. Preferisco però vivere in un mondo in cui il debito viene riscosso da revisori e white hat, piuttosto che in uno in cui continuiamo a fingere che le sabbie mobili siano roccia solida solo perché nulla ci ha ancora inghiottiti. PHP2Shell si è concluso con una patch e un sospiro di sollievo. Il prossimo è già nella coda dei crash di qualcuno, archiviato come innocuo. Andate a controllare la vostra.


