CVE-2025-32433 nell'SSH di Erlang/OTP: anatomia di un pre-auth RCE "logico", LLM come aiuto all'exploit e dove nascondi Erlang senza saperlo
Il 16 aprile 2025, mentre il mondo della cybersecurity era ancora distratto dalla crisi MITRE del giorno precedente - il finanziamento del programma CVE stava scadendo e CISA aveva appena annunciato l'estensione d'emergenza - è uscita in sordina CVE-2025-32433 sul NIST NVD: un pre-authentication RCE nel server SSH di Erlang/OTP, CVSS 10.0, scoperta dai ricercatori del Ruhr University Bochum, gli stessi che l'anno prima avevano firmato Terrapin. Ho letto il post sulla mailing list alle 10 del mattino e mezz'ora dopo stavo già facendo il tour dei clienti che avrebbero potuto essere interessati. Il problema iniziale per me non era "come si patcha" - quello è banale, rebar3 upgrade o il package manager della distro - era "chi ha Erlang in produzione?". Il numero era più alto di quanto pensassi, e scriverlo qui è il primo contributo di questo articolo.
In dieci anni di consulenza mi sono abituato a pensare a Erlang come a un linguaggio di nicchia per telecom (Ericsson, WhatsApp pre-Meta). Invece nel 2025 Erlang è sotto il cofano di un numero sorprendente di componenti che le PMI italiane usano senza saperlo: RabbitMQ come message bus dell'e-commerce, CouchDB come document store, ejabberd come backbone di chat aziendali, CockroachDB in alcune componenti legacy, e una manciata di embedded device industriali. Ognuno di questi può esporre il servizio SSH di gestione di OTP se configurato in modo pigro, e fino ad aprile 2025 quella esposizione era equivalente a una backdoor root. Questo articolo anatomizza il bug, spiega perché un "safe language" come Erlang non ti protegge da questa classe di errori, racconta come un LLM è stato usato per generare il PoC in poche ore dal diff della patch, e consegna le cinque mitigation operative che applico ai clienti dal maggio 2025.
Anatomia del bug: la state machine SSH che accetta CHANNEL_REQUEST prima dell'autenticazione
Il server SSH di Erlang/OTP è implementato nel modulo ssh_connection_handler dell'applicazione ssh, distribuita come parte standard di OTP. La sua architettura è un classico esempio di state machine Erlang: un processo gen_statem che attraversa stati progressivi - hello, kexinit, userauth, connected - man mano che il protocollo SSH avanza. Il protocollo SSH userauth è specificato in RFC 4252, il transport layer in RFC 4253 e il connection protocol in RFC 4254, e la separazione concettuale è netta: finché la user authentication non è completata (SSH_MSG_USERAUTH_SUCCESS), nessun messaggio del connection layer - come CHANNEL_OPEN o CHANNEL_REQUEST - dovrebbe essere processato. Questo è il contratto del protocollo.
Il bug di CVE-2025-32433 è sottile e tutto in un punto: la state machine di OTP accettava e processava messaggi del connection layer anche negli stati pre-auth. Quando un client inviava SSH_MSG_CHANNEL_OPEN prima di completare la userauth, il gestore del messaggio non verificava lo stato corrente del processo e apriva il canale comunque; quando il client inviava subito dopo un SSH_MSG_CHANNEL_REQUEST di tipo exec con un comando arbitrario, il server lo eseguiva con i privilegi del processo Erlang - che nella maggior parte delle deployment è un utente di servizio dedicato, ma in alcune configurazioni embedded è addirittura root. Zero credenziali, zero autenticazione, RCE pre-auth. È il tipo di bug che quando lo vedi diagrammato su carta pensi "come è possibile che nessuno l'abbia notato prima". Il motivo è che il test della sicurezza dei protocolli state-based è intrinsecamente difficile: ogni transizione di stato ammessa è facile da testare, ma le transizioni non ammesse sono combinatorialmente esplose e quasi mai coperte dai test standard.
La fix, arrivata nelle versioni OTP-27.3.3, OTP-26.2.5.11 e OTP-25.3.2.20 (branch di supporto per versioni vecchie), aggiunge esplicitamente un guard nello state machine che rifiuta messaggi CHANNEL_* negli stati pre-authenticated e termina la connessione con SSH_DISCONNECT_PROTOCOL_ERROR. Un fix di due-tre righe per un bug che era rimasto dormiente in produzione per anni. Il riferimento ufficiale per la tua compliance documentation è l'entry CVE.org per CVE-2025-32433.
Dove si nasconde Erlang in una PMI italiana nel 2026
Qui c'è il vero lavoro operativo. Quando ho fatto il tour dei clienti il 16 aprile 2025, la domanda che ponevo a tutti era "avete Erlang installato?" e la risposta era quasi sempre "no, siamo PHP/Laravel/Node". La risposta vera era "sì, e molto di più di quanto pensi". Ecco la mappa dei componenti che ho effettivamente trovato installati sui server dei clienti, ognuno dei quali tira dentro erlang-base e spesso anche l'applicazione ssh:
- RabbitMQ. Il message broker AMQP più usato nelle architetture Laravel/Symfony che hanno bisogno di code affidabili. Installato su almeno 8 clienti su 30 che avevo al tempo. RabbitMQ è scritto in Erlang e girare in un nodo Erlang/OTP. Il servizio
rabbitmq-servernormalmente non espone il servizio SSH di OTP, ma l'applicazionesshè linkata e attiva nel runtime, e alcuni plugin di management opzionali l'abilitano. - CouchDB. Document database molto usato in progetti PouchDB/offline-first. Anche lui scritto in Erlang e linkato a OTP. Trovato installato su 2 clienti su progetti di field service mobile.
- ejabberd. Server XMPP/Jabber, spesso backbone di chat interne aziendali o di componenti di messaging clienti. Trovato su 1 cliente che aveva un CRM legacy con chat integrata.
- EMQX / VerneMQ. Broker MQTT per IoT, sempre più comuni nei progetti Industry 4.0 italiani. Entrambi sono scritti in Erlang/Elixir.
- CockroachDB legacy nodes. Un cliente aveva un CockroachDB che, in versioni vecchie, includeva componenti Erlang per parti della replication. Non più vero nelle versioni moderne, ma i server non aggiornati c'erano.
Il punto critico: quando apt install rabbitmq-server tira dentro erlang-base, sul server hai un runtime Erlang completo con l'applicazione ssh disponibile. Se la configurazione di RabbitMQ abilita esplicitamente il servizio SSH di management (raro ma non inesistente in alcuni tutorial tutorial online), o se qualcuno ha seguito una guida "come gestire RabbitMQ via SSH dentro Erlang shell", quella porta è aperta. Il check che faccio su ogni server cliente è un ss -tlnp | grep -E 'beam|erl' per vedere se ci sono porte in ascolto tenute aperte da processi Erlang. Il 16 aprile 2025 questo check mi ha rivelato due casi su trenta in cui c'era effettivamente un servizio Erlang SSH in ascolto, ignoto agli sviluppatori del cliente. Erano lì da mesi.
La lezione è che l'inventario software di una PMI non è mai completo quando ti fermi alle tecnologie del Dockerfile o del composer.json. Devi scendere al livello del sistema operativo e del package manager. Nel mio approccio all'architettura di cybersecurity per PMI questo passaggio fa parte del principio di defense in depth: conosci la tua superficie di attacco completa, non solo quella che hai scritto tu.
LLM come acceleratori dell'exploit development: dal diff al PoC in poche ore
C'è un aspetto della storia di CVE-2025-32433 che merita attenzione separata e che nel mio giro è stato quasi più discusso del bug stesso: il fatto che un proof-of-concept funzionante è stato prodotto in poche ore dal rilascio della patch, utilizzando un LLM (ChatGPT/Claude) come assistente per leggere il diff del commit di fix e scrivere codice Python di exploitation. La documentazione pubblica del processo è stata fatta da un ricercatore indipendente che ha descritto il workflow: prendere il diff del commit di fix dal repository erlang/otp su GitHub, passarlo all'LLM spiegando il contesto (SSH protocol, Erlang gen_statem), chiedere "qual è il bug che questa patch risolve?", poi "scrivi un PoC Python che sfrutti la vulnerabilità pre-patch". In meno di due ore l'LLM aveva prodotto un PoC funzionante che apriva un canale SSH pre-auth e scriveva un file sul filesystem del target.
Questo non è il primo caso di LLM usati come acceleratori di exploit - è iniziato nel 2023, ha avuto picchi nel 2024 - ma è uno dei più puliti come esempio pedagogico. Tre osservazioni operative:
- Il "time-to-exploit" dopo un disclosure si è contratto drasticamente. Storicamente, il gap fra pubblicazione di un CVE e comparsa di exploit pubblici era settimane o mesi. Con LLM usati come code generator guidati dal diff, quel gap è ore. Questo cambia la pianificazione del patching: la finestra fra "disclosure" e "exploitation in the wild" non è più "hai una settimana per patchare", è "hai ore, idealmente pre-disclosure tramite embargo".
- Il disclosure coordinato con pre-shipment patch è più importante che mai. Se il vendor pubblica il fix insieme al disclosure (classico responsible disclosure), e la patch è disponibile quando il mondo viene a sapere del bug, il gap LLM-assisted time-to-exploit non è un problema - puoi patchare prima. Se il disclosure arriva prima della patch (full disclosure, zero-day), il gap diventa un'arma nelle mani degli attaccanti.
- L'LLM è un accelerator simmetrico. Aiuta l'attaccante a scrivere l'exploit, sì, ma aiuta anche il defender a capire il bug dal diff, scrivere detection rule, generare test di regressione. La parte difficile è sapere quale delle due parti sta effettivamente usando l'accelerator in modo più efficace - e nel 2026 sempre più spesso è il difensore, perché il difensore parte da una posizione informativa migliore (ha accesso al codice interno, sa dove sono i componenti Erlang, conosce l'inventario).
Il contesto più ampio della crisi del sistema CVE di aprile 2025 rende tutto questo più complicato: se le tue pipeline di vulnerability scanning dipendono da un database centralizzato (NIST NVD) che ha fragilità strutturali, e il time-to-exploit è passato da settimane a ore, stai giocando a un gioco diverso da quello del 2020.
Perché "safe language" non ti salva dalle logic flaws
Uno dei commenti ricorrenti nella discussione post-disclosure è stato: "ma Erlang è un linguaggio funzionale con gestione automatica della memoria, non doveva essere immune a questa roba?". La risposta è: Erlang ti protegge da una classe specifica di vulnerabilità - memory corruption, buffer overflow, use-after-free, double free, data races. Tutte le cose che in C/C++ portano a metà dei CVE. Ma non ti protegge da logic flaws: bug in cui il codice fa esattamente quello che è scritto, e quello che è scritto è semanticamente sbagliato.
CVE-2025-32433 è una logic flaw pura. Il codice Erlang girava in modo corretto, senza memory leaks, senza crash, senza errori di tipo. Faceva esattamente quello che il programmatore aveva scritto: accettava messaggi CHANNEL_OPEN e li processava. Il bug era che il programmatore non aveva inserito il check di stato che serviva. Rust non ti aiuterebbe, Go non ti aiuterebbe, Haskell non ti aiuterebbe. Solo una combinazione di design rigoroso, code review guidata dal protocollo (non solo dal codice), test di conformance al protocollo, e fuzzing semantico che generi messaggi in stati non ammessi può catturarla.
Questo è il motivo per cui sono scettico quando sento i team parlare di migrazioni "da PHP a Go per sicurezza" come se cambiare linguaggio risolvesse i problemi di sicurezza. Cambiando linguaggio risolvi i problemi di memory safety (se li hai - e su PHP non ce li hai, perché PHP è già managed). Non risolvi i problemi di logic, autorizzazione, validazione, state handling. E quelli sono la maggioranza dei CVE applicativi dal 2020 in poi. Il punto complementare su come si traduce in pratica l'hardening applicativo è trattato nella mia checklist di hardening NIS2-ready per Laravel e Symfony: la maggior parte delle voci riguarda controlli logici, non memory safety.
Cinque mitigation operative che applico dal maggio 2025
Dopo il tour dei clienti del 16 aprile e le due settimane di remediation che sono seguite, ho consolidato cinque regole che applico a ogni nuovo progetto PMI che abbia almeno un componente Erlang/OTP nel suo stack:
1. Inventario software reale, non dichiarato. Alla prima settimana di audit faccio dpkg -l | grep -i erlang e ss -tlnp | grep beam su ogni server di produzione. Il cliente non deve dirmi che ha Erlang; me lo dice il sistema operativo.
- Erlang SSH management port mai esposto su internet pubblico, nemmeno dietro firewall "solo nostro IP". Se proprio serve accesso remoto alla shell Erlang, va dietro WireGuard o bastion host, come qualunque altra management interface. La stessa regola vale per qualunque porta di management, dal MySQL al Redis Insight, come spiego in dettaglio nel mio articolo su regreSSHion e l'hardening SSH post-incident.
- Disabilitazione esplicita del servizio
sshdi OTP quando non serve. RabbitMQ non ha bisogno che l'applicazionesshsia avviata per funzionare come message broker - basta configurarerabbitmq.confsenza i plugin di SSH management, e verificare conrabbitmq-plugins listcherabbitmq_shovel_managemente simili non siano attivi se non necessari. - Pipeline di patching accelerata per i package Erlang. Dependabot/renovate configurati per proporre upgrade di
erlang-*,rabbitmq-*,couchdb,ejabberdcon priorità "security" e auto-merge sulle patch versions dopo CI verde. Il tempo di reazione fra disclosure e patch deployato deve stare sotto le 24 ore sui componenti critici. - Monitoring dei tentativi di connessione SSH anomali sulla porta di default di Erlang (22 con banner
SSH-2.0-Erlang). Un alert su qualunque connessione SSH a un servizio Erlang da fuori della management VPN è un early warning di scan mirato. Lo integro con il resto del flusso di incident response NIS2-ready perché le prime 4 ore sono decisive.
CVE-2025-32433 non è stato il primo bug critico in un runtime "safe", e non sarà l'ultimo. La lezione sopra tutte le altre è che l'inventario delle tue dipendenze software deve essere fatto dal sistema operativo in giù, non dal composer.json in su. Quello che Laravel tira dentro come ecosistema è visibile al tuo team; quello che il tuo package manager tira dentro come transitivo è invisibile finché non esplode. Se stai gestendo un'infrastruttura PMI italiana e non hai mai fatto un inventario reale di cosa gira sui tuoi server - runtime Erlang inclusi, ma anche Python 2 residuo, Node embedded, Redis antichi, MySQL sulle versioni sbagliate - scopri come lavoro con i clienti sul tema vulnerability management: dieci anni di consulenza mi hanno insegnato che ogni infrastruttura PMI ha almeno un componente dimenticato di cui nessuno ricorda l'esistenza, ed è lì che arriva il prossimo CVE. Se vuoi una mappa completa dei componenti in esecuzione sui tuoi server e un piano di hardening prioritizzato per i più rischiosi, contattami per una consulenza: in due settimane di lavoro ti consegno inventario reale (con ss, dpkg, docker inspect), mapping CVE degli ultimi 24 mesi, e runbook di patching con tempistiche calibrate sulla criticità di ciascun componente.