PhP : baco o backdoor?
La storia del baco di PHP (anzi, dei due bachi battezzati «php2shell», CVE-2026-109618 e CVE-2026-109619) merita una riflessione. La sua portata è enorme: PHP fa girare, a seconda delle stime, fra il 70 e l’80 per cento dei siti web, e queste falle permettevano a un attaccante di eseguire codice da remoto senza alcuna autenticazione, qualunque fosse l’applicazione installata sopra. Ma non è questo il punto più interessante. Il punto è che la vicenda smonta alcuni luoghi comuni molto diffusi, ed è ora che qualcuno cominci a rifletterci seriamente.
Il primo luogo comune da smontare è questo: il codice open source è più sicuro perché, essendo pubblico, chiunque può leggerlo e trovarne i bachi. In teoria funziona. In pratica perde gran parte del suo fascino quando le righe di codice si contano a centinaia di migliaia, se non a milioni. Qualcuno ha davvero letto tutto il codice di PHP? PHP, è bene ricordarlo, non è una libreria qualsiasi, ma un motore completo: compila al volo il sorgente in bytecode, lo esegue in una macchina virtuale e, dalla versione 8, include persino un compilatore JIT.
I fatti rispondono da soli. Le vulnerabilità erano presenti in tutte le versioni rilasciate nell’ultimo decennio, fino a PHP 5.4. Per più di dieci anni quel codice è rimasto lì, pubblico, a disposizione di chiunque volesse leggerlo, e nessuno le ha viste. Tra i primi a trovarle c’è stato il team di ricerca di Wiz, che dichiara di averle individuate con l’aiuto di modelli di intelligenza artificiale. Possiamo dividere questi casi in tre filoni.
Nel primo, tutti sanno che la falla esiste e tutti corrono ai ripari, come sta succedendo adesso. Anche qui, però, la patch non basta. Le stesse autorità di cybersicurezza raccomandano non solo di aggiornare, ma di cercare sui server tracce di intrusione: processi, file, account o connessioni in uscita che non dovrebbero esserci. C’è un buon motivo: secondo Cloudflare gli attacchi erano cominciati già prima del rilascio delle correzioni, complice la fuga anticipata dei dettagli. C’è poi chi la patch non la riceverà mai: il progetto PHP mantiene ufficialmente solo i rami dalla 8.2 alla 8.5, e per tutte le versioni precedenti non esistono correzioni ufficiali. Per quanto mi riguarda, un sistema che ha tenuto in pancia quella roba per anni va considerato compromesso e ricostruito da zero. Ma concediamo pure che aggiornare basti.
Nel secondo, nessuno sa che la falla esiste, perché è nata per caso: un errore del programmatore, o dei programmatori.
Ma qui viene il bello, e c'e' un precedente storico.
Nel terzo, ALCUNI sanno che la falla esiste e possono sfruttarla, mentre tutti gli altri non ne hanno la minima idea. Non è fantascienza, è già successo. EternalBlue era un exploit per una vulnerabilità del protocollo SMBv1 di Windows, sviluppato dalla NSA e, secondo quanto riportato dalla stampa americana, tenuto nel cassetto per anni. Nel 2017 un gruppo che si faceva chiamare Shadow Brokers lo trafugò e lo rese pubblico. Microsoft aveva rilasciato la correzione un mese prima, ma questo non bastò: poche settimane dopo EternalBlue diventò il motore di WannaCry, e subito dopo di NotPetya, due delle ondate di attacchi più devastanti della storia. Chi sapeva, quindi, c’era. Tutti gli altri l’hanno scoperto quando era ormai troppo tardi.
Ed eccoci al problema. Il secondo e il terzo caso sono praticamente indistinguibili sul piano tecnologico.
Dovremmo quindi farci una domanda: la falla era un baco o una backdoor?
Sia chiaro: ad oggi nessuno ha portato prove che php2shell sia stato inserito di proposito. Tecnicamente si tratta di errori di gestione della memoria (un use-after-free e una cattiva gestione delle condizioni di memoria esaurita), cioè la famiglia di bachi più comune nel codice C. Ma è proprio questo il punto. Una backdoor ben fatta non si presenta come una backdoor: si presenta come un baco.
Nel 2003 qualcuno riuscì a infilare nel repository del kernel Linux una modifica alla chiamata di sistema wait4 che sembrava un banale controllo d’errore. Al posto di un confronto (uid == 0) c’era un’assegnazione (uid = 0): un solo carattere di differenza, che regalava i privilegi di root a chi sapeva come invocarla. Fu intercettata, ma chiunque l’avesse letta distrattamente l’avrebbe presa per un refuso.
E PHP non è nuovo a questi tentativi. Nel 2021 qualcuno violò il server git del progetto e spinse nel sorgente due commit malevoli, firmati a nome di Rasmus Lerdorf e Nikita Popov, che permettevano di eseguire codice arbitrario tramite un header HTTP appositamente costruito. Anche quelli vennero scoperti in fretta.
Il caso più istruttivo, però, è quello di xz-utils, nel 2024. Qui non ci fu nessuna intrusione: un utente che si presentava come «Jia Tan» passò circa due anni a contribuire al progetto con patch utili e corrette, guadagnandosi la fiducia di un maintainer sovraccarico di lavoro, fino a diventarne co-maintainer. A quel punto inserì nelle versioni 5.6.0 e 5.6.1 una backdoor che, attraverso la libreria liblzma, poteva compromettere il demone SSH su diverse distribuzioni Linux. Il codice malevolo non stava nemmeno nel repository git: era nascosto in file di test binari e attivato da script presenti solo nei pacchetti di rilascio. A scoprirla non fu nessuno dei mille occhi che avrebbero dovuto leggere il sorgente. Fu Andres Freund, un ingegnere che stava indagando su un rallentamento di circa mezzo secondo nei login SSH e su un consumo anomalo di CPU. Per puro caso, e appena in tempo: la backdoor era già finita nei rami di sviluppo e di test di Debian, Fedora e openSUSE, ma non ancora nelle versioni stabili delle grandi distribuzioni.
Tre tentativi scoperti, quindi. La domanda che dovrebbe toglierci il sonno è un’altra: quanti tentativi riusciti non sono mai stati scoperti?
Il problema è che consentire l’esecuzione remota di codice arbitrario è un difetto che, certo, può spuntare per caso. Così come, per puro caso, il DNA di un tacchino potrebbe mutare al punto da tirarne fuori un velociraptor. Può succedere? Sì. È probabile? Molto poco.
D’altra parte, può anche succedere in modo del tutto probabile. C’è stato un periodo in cui era prassi comune concedere i diritti di sudo al comando less, così che chiunque potesse leggere i file di log. Peccato che less permetta di lanciare comandi di shell con un semplice !sh, e che con il tasto v apra l’editor di sistema, tipicamente vi, che a sua volta lancia una shell con :!sh. Risultato: chiunque avesse il permesso di leggere i log con sudo si ritrovava una shell di root a portata di tastiera, e un numero enorme di macchine era insicuro senza che nessuno avesse scritto una sola riga di codice sbagliata. La morale? La probabilità c’entra poco.
Quale problema sto sollevando, allora?
Questo: come facciamo a sapere che si trattava di un baco e non di una backdoor?
Certo, un use-after-free è un errore molto comune in C. Ma se dovessi scrivere una backdoor, la scriverei esattamente così: travestita da errore comune.
E quando «trovare una backdoor» significa individuare la differenza fra == e = nei milioni di righe di codice che compongono un kernel, un interprete come PHP, Perl o Python (che compila il sorgente in memoria e lo esegue), o una suite da ufficio, il problema si complica parecchio. Il caso del 2003 lo dimostra bene: l’assegnazione incriminata era racchiusa tra parentesi in modo da non far scattare nemmeno l’avvertimento del compilatore, che normalmente segnala proprio questo genere di sviste.
Perché il problema si complica?
Perché nessun essere umano può fare questo lavoro in modo sistematico. Serve prima un sospetto, qualcuno che noti un’anomalia, come il mezzo secondo di ritardo che ha smascherato xz-utils. Ma nessun essere umano è in grado di leggere tutto il kernel Linux. Quante righe siano, poi, dipende persino da chi le conta: per Linux 7.2 lo strumento cloc ne trovava oltre 43 milioni, di cui circa 33 milioni di codice vero e proprio. Per Linux 7.3 un altro conteggio si ferma attorno ai 41 milioni, con meno di 31 milioni di codice. In ogni caso parliamo di oltre 40 milioni di righe, e lì dentro bisognerebbe scovare quell’unica volta in cui un = doveva essere un ==, o viceversa. Quando non riusciamo nemmeno a metterci d’accordo su quanto sia grande il codice, figuriamoci leggerlo tutto.
Il problema, sul piano del codice, sembra irrisolvibile. Chi propone di usare l’intelligenza artificiale per scovare le falle dimentica sempre che, dall’altra parte, qualcuno userà l’intelligenza artificiale per crearle. E non è un’ipotesi accademica: php2shell è stato trovato da ricercatori che usavano modelli di AI, e la corsa a correggerlo è stata, da subito, una corsa contro attaccanti armati degli stessi strumenti. Uno strumento che serve a entrambi i contendenti non è una soluzione: è un nuovo campo di battaglia.
Esistono poi scenari pestilenziali. Supponiamo di modificare un compilatore C perché inserisca una backdoor in ogni eseguibile che produce. Visto che il compilatore C si compila da solo, la backdoor finirebbe anche dentro il compilatore stesso. Da quel momento potremmo cancellarla dal sorgente: continuerebbe a riprodursi lo stesso, perché è il binario a reinserirla ogni volta che compila. A meno di ricorrere a un compilatore pulito, ci terremmo questa backdoor-verme in ogni software che compiliamo, senza che leggere il codice sorgente serva a nulla.
Non è fantascienza nemmeno questa. L’idea la descrisse Ken Thompson, uno dei padri di Unix, nel 1984, nel discorso di accettazione del premio Turing intitolato «Reflections on Trusting Trust». Il suo compilatore riconosceva quando stava compilando il programma di login, e ci infilava una password segreta. Riconosceva anche quando stava compilando se stesso, e ci reinseriva il meccanismo. Nel sorgente non restava traccia di nulla. Da allora, l’idea ha avuto applicazioni molto concrete. Nel 2009 il virus Induc infettava l’ambiente di sviluppo Delphi, e da lì ogni programma compilato con esso. Nel 2015 XcodeGhost, una versione manomessa del compilatore di Apple distribuita su server cinesi, finì dentro applicazioni iOS scaricate da milioni di persone, WeChat compresa.
Le contromisure esistono, ma dicono molto sulla natura del problema. David A. Wheeler ha proposto la tecnica del «diverse double-compiling»: si ricompila il compilatore con un secondo compilatore indipendente, e si confrontano i risultati. Non serve un compilatore «vergine» di prima dell’infezione, ma uno di cui ci si fidi e che sia stato sviluppato in modo indipendente. Nel frattempo progetti come Bootstrappable Builds e GNU Guix hanno lavorato per ricostruire un’intera catena di compilazione partendo da un seme binario di poche centinaia di byte, abbastanza piccolo da poter essere controllato a mano. In altre parole: l’unico modo per fidarsi di quaranta milioni di righe è ripartire da qualcosa di così piccolo che un essere umano possa davvero leggerlo.
Siamo dunque davanti a un «External Server Error»: l’errore non sta nel server, ma fuori, nell’essere umano, che non riesce a trovare il baco quando è nascosto bene nel codice.
E tutto il codice di cui abbiamo deciso di fidarci, compreso quello che gira nei mainframe delle banche, soffre dello stesso problema.
A questo punto la domanda diventa: chi ha i mezzi, la pazienza e il movente per nascondere bene una backdoor? Non il ragazzino in cantina. Gli Stati. E la storia dimostra che non si tratta di paranoia.
Il KGB ci lavorava già quando i computer occupavano intere stanze. Nel 1945 alcuni scolari sovietici regalarono all’ambasciatore americano a Mosca un Grande Sigillo degli Stati Uniti intagliato nel legno. Dentro c’era un microfono passivo, progettato da Lev Termen, l’inventore del theremin, senza batterie né fili, che si attivava solo se illuminato da un fascio radio dall’esterno. Rimase appeso nello studio dell’ambasciatore fino al 1952. Negli anni Ottanta gli americani scoprirono, con l’operazione Gunman, che le macchine da scrivere elettriche IBM Selectric della stessa ambasciata erano state modificate per trasmettere tutto ciò che veniva battuto. In pratica era un keylogger hardware, in servizio da anni prima che qualcuno se ne accorgesse.
L’NSA non è stata da meno. Dai documenti diffusi da Edward Snowden nel 2013 emerse che l’agenzia aveva influenzato la standardizzazione di Dual_EC_DRBG, un generatore di numeri casuali certificato dal NIST e quindi usato in prodotti crittografici di tutto il mondo. Secondo un’inchiesta Reuters, RSA Security avrebbe ricevuto dieci milioni di dollari per renderlo l’algoritmo predefinito della sua libreria BSAFE. Ancora più audace fu l’operazione condotta con la CIA e il servizio segreto tedesco BND, svelata nel 2020 dal Washington Post e dalla ZDF: per decenni le due agenzie furono le proprietarie occulte di Crypto AG, l’azienda svizzera che vendeva macchine cifranti a oltre cento governi. I clienti pagavano per proteggere i propri segreti, e i servizi americani e tedeschi li leggevano. E per tornare alle banche: tra i materiali pubblicati nel 2017 dagli Shadow Brokers, gli stessi di EternalBlue, c’erano le prove che l’NSA era penetrata in EastNets, un fornitore di servizi per la rete SWIFT in Medio Oriente.
Gli eredi del KGB, nel frattempo, si sono aggiornati. Nel 2020 qualcuno compromise il processo di build di SolarWinds Orion, un software di monitoraggio di reti, inserendo una backdoor negli aggiornamenti ufficiali, firmati digitalmente dal produttore. Circa diciottomila clienti li installarono, comprese diverse agenzie federali americane. Il governo degli Stati Uniti ha attribuito l’operazione all’SVR, il servizio di intelligence estera russo nato dalle ceneri del Primo direttorato del KGB. Il caso Juniper è forse il più ironico. Nel 2015 l’azienda scoprì nel proprio sistema operativo ScreenOS del codice non autorizzato che, tra le altre cose, sostituiva una costante usata proprio da Dual_EC_DRBG. Qualcuno, insomma, aveva trovato la porta lasciata dall’NSA e ci aveva cambiato la serratura. Un’inchiesta di Bloomberg del 2021 ha attribuito l’intervento a un gruppo legato alla Cina.
Ma c’è un dettaglio che dovrebbe farci riflettere più di tutti gli altri. Nessuno di questi casi è venuto alla luce perché qualcuno ha letto il codice. Sono emersi grazie a fughe di documenti, a disertori, a giornalisti, a incidenti. E ogni volta, a posteriori, abbiamo scoperto che qualcuno sapeva da anni.
Probabilmente non sapremo mai se php2shell fosse un baco o una backdoor. Ma forse la domanda giusta è un’altra: chi altro lo sapeva, prima di noi? Perché la sicurezza non è una proprietà del codice, aperto o chiuso che sia.
È una proprietà della fiducia. E la fiducia, a differenza del codice, non si può ricompilare.
Uriel Fanelli
- Written using Wortwerk
- Fedi: @uriel@x.keinpfusch.net
- XMPP:
uriel@keinpfusch.net - Matrix: @uriel:chat.keinpfusch.net
- Email: blog@keinpfusch.net