# STARTTLS stripping: perché la posta va cifrata con TLS implicito
Table of Contents
Quando si configura un account di posta compare quasi sempre una scelta del genere: "SSL/TLS" oppure "STARTTLS". Sembrano due modi equivalenti di cifrare la connessione, e in un mondo perfetto lo sono. Nel mondo reale no: con STARTTLS la connessione nasce in chiaro e passa alla cifratura solo dopo una trattativa, e quella trattativa si può sabotare. L'attacco si chiama STARTTLS stripping ("rimozione" di STARTTLS) e fa viaggiare in chiaro password ed email senza che nessuno se ne accorga.
Vale per i tre protocolli della posta: SMTP (invio), IMAP e POP3 (lettura). In questo articolo vediamo come funziona l'attacco, perché il TLS implicito (la cifratura che parte dal primo byte) lo evita per costruzione e quali errori di configurazione, nei siti PHP e negli script, trasformano una "connessione cifrata" in una connessione cifrata solo quando va bene.
Una nota sui nomi, perché fanno confusione anche ai tecnici. Il TLS "da subito" si chiama implicito (implicit TLS): la porta è dedicata alla cifratura e il primo pacchetto è già una stretta di mano TLS. STARTTLS si chiama invece TLS esplicito: il client si collega in chiaro e chiede lui di passare alla cifratura. Nei programmi di posta la voce "SSL/TLS" indica quasi sempre la modalità implicita. Il nome "SSL" è un residuo storico: i vecchi protocolli SSL sono morti da anni e oggi si negozia TLS 1.2 o 1.3.
Come funziona STARTTLS (e dov'è il punto debole)
Con STARTTLS il client apre una connessione normale, il server dichiara cosa sa fare, e se tra le capacità c'è STARTTLS il client chiede di passare alla cifratura. Per SMTP, sulla porta 587, assomiglia a questo:
S: 220 mail.example.com ESMTPC: EHLO client.example.orgS: 250-mail.example.comS: 250-STARTTLSS: 250-AUTH PLAIN LOGINS: 250 SIZE 52428800C: STARTTLSS: 220 2.0.0 Ready to start TLS ... da qui parte la stretta di mano TLS, tutto il resto è cifrato ...Tutto ciò che sta sopra la riga dei puntini viaggia in chiaro, e quindi un attaccante in mezzo alla comunicazione (un router compromesso, una Wi-Fi pubblica, un apparato di rete di un provider) può modificarlo. Il suo lavoro è semplice: cancella la riga 250-STARTTLS dalla risposta del server, per esempio sostituendola con caratteri senza significato della stessa lunghezza. Il client vede un server che non sa cifrare e, se è configurato in modo permissivo, prosegue lo stesso:
S: 220 mail.example.com ESMTPC: EHLO client.example.orgS: 250-mail.example.comS: 250-XXXXXXXA <- era "250-STARTTLS", cancellato in transitoS: 250-AUTH PLAIN LOGINS: 250 SIZE 52428800C: AUTH PLAIN AHV0ZW50ZQBwYXNzd29yZA== <- utente e password, in chiaroQuella stringa Base64 non è cifratura: si decodifica con un comando, e contiene utente e password. Il separatore non è il : (quello è di HTTP Basic): in AUTH PLAIN (RFC 4616) i campi sono separati da un byte nullo, quindi la stringa decodificata è \0utente\0password. Il Base64 di prima, decodificato con echo AHV0ZW50ZQBwYXNzd29yZA== | base64 -d, dà esattamente utente e password divisi da due byte nulli, invisibili a video. Il client non ha mostrato nessun errore, la posta è stata inviata, tutto sembra funzionare. L'attacco non lascia tracce visibili all'utente.
Lo stesso trucco funziona sugli altri protocolli. In IMAP il server annuncia STARTTLS nelle sue capacità, in POP3 il comando si chiama STLS. Basta far credere al client che non ci siano, oppure far rispondere un errore al comando di avvio della cifratura:
C: STLSS: -ERR not supported <- risposta falsificata da chi sta in mezzoC: USER marioC: PASS la-mia-password <- il client, permissivo, continua in chiaroNon è solo teoria. Lo studio Neither Snow Nor Rain Nor MITM, presentato nel 2015, ha misurato il fenomeno sul traffico reale e riporta prove di declassamenti a danno di messaggi destinati a Gmail in diversi paesi.
Anche la RFC 2595, che descrive STARTTLS per IMAP e POP3, lo mette per iscritto: un attaccante in mezzo può togliere STARTTLS dall'elenco delle capacità del server.
Il vero problema: i client che non pretendono la cifratura
Il nodo è questo: STARTTLS è una richiesta, non un obbligo. Se il client è configurato per usarlo "quando disponibile" (opportunistic TLS), un server che dice "non so farlo" viene accettato. È il comportamento predefinito di PHPMailer e di Symfony Mailer (vedi sotto), ed è quello che l'attaccante sfrutta. Un sito o uno script che spedisce posta in automatico non mostra nulla a nessuno: se la password passa in chiaro, non compare nessun errore.
PHPMailer (usato da WordPress e da moltissimi gestionali) ha SMTPAutoTLS = true come predefinito: se SMTPSecure è vuoto, cifra solo quando il server dichiara di poterlo fare, altrimenti prosegue in chiaro. Il tipico snippet "che funziona" è questo:
$mail->isSMTP();$mail->Host = 'mail.example.com';$mail->Port = 587;$mail->SMTPAuth = true;$mail->Username = 'noreply@example.com';$mail->Password = $password;// SMTPSecure non impostato: cifratura opportunistica, strippabileSymfony Mailer ragiona allo stesso modo con lo schema smtp://: la documentazione dice che usa la cifratura quando l'estensione OpenSSL è attiva e il server supporta STARTTLS, e nel codice la modalità automatica (auto_tls) è attiva per default. Se il server non lo dichiara, la mail parte in chiaro. Laravel è costruito su Symfony Mailer: nel MailManager della versione 12 lo schema è quello indicato in scheme (variabile MAIL_SCHEME) e, se manca, vale smtps sulla porta 465 e smtp su qualsiasi altra porta. Una configurazione tipica in .env è quindi questa:
MAIL_MAILER=smtpMAIL_HOST=mail.example.comMAIL_PORT=587 # schema smtp: STARTTLS opportunisticoAnche in Python la verifica del certificato non è automatica: nel sorgente di CPython, se a smtplib.SMTP.starttls(), smtplib.SMTP_SSL o imaplib.IMAP4_SSL non si passa un contesto SSL, ne viene creato uno senza verifica del certificato né del nome host (ssl._create_stdlib_context è un alias di _create_unverified_context). Un altro errore classico è disattivare la verifica per "far funzionare" un server con certificato scaduto o autofirmato (/novalidate-cert in PHP IMAP, che per default invece valida i certificati; verify_peer=0 in Symfony; SMTPOptions con verify_peer => false in PHPMailer). A quel punto la cifratura c'è, ma chi sta in mezzo può presentare un proprio certificato qualsiasi: la connessione è cifrata verso l'attaccante.
Perché il TLS implicito è meglio
Con il TLS implicito il client si collega a una porta dedicata (465 per l'invio, 993 per IMAP, 995 per POP3) e la prima cosa che scambia è la stretta di mano TLS. Non esiste una fase in chiaro da manomettere e non esiste una capacità STARTTLS da cancellare: se il TLS non parte, la connessione cade e basta. Il client non può "scivolare" sul testo in chiaro nemmeno se lo volesse.
Ci sono altri vantaggi che contano nella pratica:
- Meno complessità, meno bug. STARTTLS richiede di gestire il passaggio da testo a cifrato nello stesso flusso di dati. Qui è nata una famiglia di vulnerabilità: il command injection su STARTTLS (per esempio CVE-2011-0411 in Postfix) permetteva di far elaborare al server, come se fosse dentro la sessione cifrata, un comando inviato in chiaro. La RFC 8314 lo cita (CERT VU#555316) e nota che l'errore è stato commesso in modo indipendente da più implementatori, il che suggerisce un'architettura meno sicura del TLS implicito. Nel 2021 lo studio Why TLS is better without STARTTLS (USENIX Security) ha trovato oltre 40 vulnerabilità nelle implementazioni STARTTLS e circa 320.000 server di posta vulnerabili.
- È ciò che raccomanda lo standard. La RFC 8314 (2018) indica il TLS implicito come metodo preferito per invio e lettura della posta, chiede ai server di offrirlo e di eliminare il chiaro appena possibile, e ai client di non inviare credenziali se la sessione non è cifrata correttamente e di validare il certificato.
- La porta 465 è tornata ufficiale. Storicamente era stata registrata come "smtps", poi la registrazione era stata revocata; la RFC 8314 l'ha assegnata di nuovo, con il nome
submissions, all'invio di posta su TLS.
La RFC è onesta su un punto: se entrambi i lati sono implementati bene e il client pretende la cifratura, non c'è una differenza significativa tra STARTTLS sulla 587 e TLS implicito sulla 465. Il problema è che "se implementati bene" è una condizione fragile, e il TLS implicito non ne ha bisogno.
Come si configura bene, lato client
La regola vale per ogni linguaggio: porta con TLS implicito, certificato verificato, nessun ripiego.
PHPMailer, SMTP su 465:
$mail->isSMTP();$mail->Host = 'mail.example.com';$mail->Port = 465;$mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS; // TLS implicito$mail->SMTPAutoTLS = false;$mail->SMTPAuth = true;Laravel 12, schema smtps (TLS implicito) al posto di smtp. Sulla porta 465 lo sceglie da solo, ma è meglio scriverlo esplicitamente:
MAIL_MAILER=smtpMAIL_HOST=mail.example.comMAIL_PORT=465MAIL_SCHEME=smtpsSe per qualche motivo si deve restare su STARTTLS (587), con Symfony Mailer si impone almeno require_tls: se il server non offre la cifratura, la spedizione fallisce con un'eccezione invece di partire in chiaro.
smtp://utente:password@mail.example.com:587?require_tls=truePython, IMAP e SMTP con un contesto che verifica certificato e nome host (ssl.create_default_context() imposta CERT_REQUIRED e check_hostname):
import imaplib, smtplib, ssl
ctx = ssl.create_default_context() # verifica catena e nome host
imap = imaplib.IMAP4_SSL("mail.example.com", 993, ssl_context=ctx)smtp = smtplib.SMTP_SSL("mail.example.com", 465, context=ctx)Per PHP IMAP il flag /ssl usa il TLS fin dall'inizio, mentre /tls impone STARTTLS e rifiuta i server che non lo supportano; la validazione del certificato è attiva per default e /novalidate-cert la disattiva. Per i programmi di posta la regola è la stessa: scegliere la voce "SSL/TLS" con le porte 993, 995 e 465.
Come si configura bene, lato server
Il server deve smettere di accettare il chiaro, altrimenti qualsiasi client mal configurato resta vulnerabile. Postfix, servizio submissions in master.cf:
submissions inet n - n - - smtpd -o smtpd_tls_wrappermode=yes -o smtpd_sasl_auth_enable=yesÈ l'esempio della documentazione di Postfix: in wrapper mode il server usa sempre TLS invece di offrire STARTTLS. Se si tiene anche la 587, la documentazione descrive smtpd_tls_security_level = encrypt (il server annuncia STARTTLS e non accetta posta senza cifratura; la documentazione lo sconsiglia per un server MX pubblico, quindi va usato solo sul servizio di invio) e smtpd_tls_auth_only = yes (l'autenticazione viene offerta solo dopo STARTTLS). In Dovecot, ssl = required fa fallire qualsiasi autenticazione prima dell'attivazione di TLS, e si possono lasciare attive solo le porte con TLS implicito, perché port = 0 disattiva un listener:
ssl = required
service imap-login { inet_listener imap { port = 0 } # IMAP in chiaro/STARTTLS spento inet_listener imaps { port = 993 ssl = yes }}service pop3-login { inet_listener pop3 { port = 0 } inet_listener pop3s { port = 995 ssl = yes }}Spegnere le porte 143 e 110 può rompere client vecchi: prima di farlo conviene leggere nei log chi le usa ancora e aggiornare le loro configurazioni.
La porta 25 è un'altra storia
Tutto questo riguarda la posta che un utente o un'applicazione invia al proprio server o legge dal proprio server. Tra server di posta diversi (porta 25) il TLS implicito non è praticabile: la RFC 8314 osserva che l'infrastruttura MX non ha modo di indicare una porta diversa. La cifratura resta quindi opportunistica, e la RFC 7672 ricorda che l'indicazione di supporto TLS del server può essere soppressa con facilità da un attaccante in mezzo. Il rischio di stripping resta, ed esistono contromisure dedicate: MTA-STS (RFC 8461) e DANE (RFC 7672) permettono al dominio destinatario di dichiarare "da me si arriva solo cifrati". Sono il complemento naturale a SPF, DKIM e DMARC: quelli autenticano chi scrive, queste proteggono il percorso.
Come verificare che il tuo server regga
Con openssl si vede in pochi secondi cosa offre un server. Un controllo sulla modalità implicita:
openssl s_client -connect mail.example.com:465 -briefopenssl s_client -connect mail.example.com:993 -briefE sulla modalità STARTTLS, per vedere cosa annuncia il server (-starttls accetta smtp, pop3 e imap):
openssl s_client -starttls smtp -connect mail.example.com:587 -crlfopenssl s_client -starttls imap -connect mail.example.com:143printf 'a CAPABILITY\r\n' | nc mail.example.com 143L'ultimo comando interroga la porta 143 senza TLS. La RFC 2595 prevede che un server IMAP possa annunciare LOGINDISABLED, cioè che il comando LOGIN in chiaro è spento; la stessa RFC avverte che protegge solo dall'ascolto passivo, perché un attaccante attivo può togliere l'annuncio. Il test che conta è provare un'autenticazione prima di STARTTLS: un server configurato bene la rifiuta (con Dovecot, ssl = required fa fallire l'autenticazione prima di TLS). Se accetta, un client configurato male può mandargli le credenziali in chiaro, e un attaccante può costringerlo a farlo. Chi gestisce anche i certificati TLS che stanno scendendo a 47 giorni ha un altro motivo per controllare: un certificato scaduto sul server di posta è la scusa più comune per disattivare la verifica sul client.
Gli errori più frequenti, in breve
- "Funziona, quindi è cifrato." Non è vero: STARTTLS opportunistico funziona anche in chiaro, e la posta arriva comunque.
- Verifica del certificato disattivata perché scaduto o autofirmato. Si ripara il certificato, non la verifica.
- Credenziali riusate tra posta, pannello e altri servizi: se transitano in chiaro una volta, vanno cambiate. Una password trapelata è il materiale ideale per gli attacchi a forza bruta.
- Porte 143/110 lasciate aperte "per sicurezza", con il chiaro ancora ammesso.
- Nessun monitoraggio: un'applicazione che passa in chiaro per un errore di configurazione non produce nessun allarme. La RFC 8314 raccomanda ai server di registrare la suite di cifratura negoziata nell'intestazione
Receivede nei log di connessione e autenticazione: controllarli periodicamente permette di scoprire chi si collega senza TLS.
In sintesi
Lo STARTTLS stripping non è un attacco sofisticato: basta cancellare una riga da una conversazione che nasce in chiaro. Funziona perché molti client, tra cui PHPMailer, Symfony Mailer e tante librerie di script, considerano la cifratura un'opzione da usare "se c'è". Il TLS implicito sulle porte 465, 993 e 995 elimina il problema alla radice: o la connessione è cifrata, o non c'è. Quando non si può abbandonare STARTTLS, bisogna almeno imporlo (require_tls, smtpd_tls_auth_only, ssl = required) e verificare il certificato. E sul percorso tra server, dove il TLS implicito non c'è, servono MTA-STS o DANE.
Fonti
- RFC 8314 - Cleartext Considered Obsolete: Use of TLS for Email Submission and Access
- RFC 3207 - SMTP Service Extension for Secure SMTP over TLS
- RFC 4616 - The PLAIN Simple Authentication and Security Layer (SASL) Mechanism
- CVE-2011-0411 - Postfix STARTTLS
- Documentazione Dovecot - SSL
- Documentazione Postfix - TLS_README
- Documentazione PHP - imap_open
- RFC 2595 - Using TLS with IMAP, POP3 and ACAP
- RFC 8461 - SMTP MTA Strict Transport Security (MTA-STS)
- RFC 7672 - SMTP Security via Opportunistic DANE TLS
- Why TLS is better without STARTTLS (USENIX Security 2021)
- Neither Snow Nor Rain Nor MITM: An Empirical Analysis of Email Delivery Security (IMC 2015)
- Symfony Mailer - Transport Setup
- PHPMailer - proprietà SMTPSecure e SMTPAutoTLS
