# STARTTLS Stripping: Why Email Should Use Implicit TLS
Table of Contents
When you set up a mail account you almost always see a choice like this: "SSL/TLS" or "STARTTLS". They look like two equivalent ways of encrypting the connection, and in a perfect world they are. In the real world they are not: with STARTTLS the connection starts in plaintext and switches to encryption only after a negotiation, and that negotiation can be sabotaged. The attack is called STARTTLS stripping and it makes passwords and emails travel in the clear without anyone noticing.
It affects all three mail protocols: SMTP (sending), IMAP and POP3 (reading). In this article we look at how the attack works, why implicit TLS (encryption from the very first byte) avoids it by design, and which configuration mistakes, in PHP sites and scripts, turn an "encrypted connection" into one that is encrypted only when things go well.
A note on names, because they confuse even experienced people. TLS "from the start" is called implicit (implicit TLS): the port is dedicated to encryption and the first packet is already a TLS handshake. STARTTLS is called explicit TLS: the client connects in plaintext and then asks to upgrade. In mail clients the "SSL/TLS" option almost always means the implicit mode. The word "SSL" is a historical leftover: the old SSL protocols have been dead for years and today you negotiate TLS 1.2 or 1.3.
How STARTTLS works (and where it is weak)
With STARTTLS the client opens a normal connection, the server states what it can do, and if STARTTLS is among the capabilities the client asks to upgrade. For SMTP, on port 587, it looks like this:
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 ... the TLS handshake starts here, everything after is encrypted ...Everything above the dots travels in plaintext, so an attacker in the middle (a compromised router, a public Wi-Fi, a network appliance at an ISP) can modify it. Their job is simple: delete the 250-STARTTLS line from the server's reply, for example by replacing it with meaningless characters of the same length. The client sees a server that cannot encrypt and, if configured permissively, carries on anyway:
S: 220 mail.example.com ESMTPC: EHLO client.example.orgS: 250-mail.example.comS: 250-XXXXXXXA <- was "250-STARTTLS", wiped in transitS: 250-AUTH PLAIN LOGINS: 250 SIZE 52428800C: AUTH PLAIN AHV0ZW50ZQBwYXNzd29yZA== <- username and password, in the clearThat Base64 string is not encryption: one command decodes it, and it contains the username and password. The separator is not : (that is HTTP Basic): in AUTH PLAIN (RFC 4616) the fields are separated by a NUL byte, so the decoded string is \0username\0password. Decoding the string above with echo AHV0ZW50ZQBwYXNzd29yZA== | base64 -d gives exactly utente and password split by two NUL bytes, invisible on screen. The client showed no error, the mail went out, everything seems to work. The attack leaves nothing visible to the user.
The same trick works on the other protocols. In IMAP the server advertises STARTTLS among its capabilities, in POP3 the command is called STLS. It is enough to make the client believe they are missing, or to make the server answer with an error to the command that starts encryption:
C: STLSS: -ERR not supported <- forged by the man in the middleC: USER marioC: PASS my-password <- the permissive client carries on in plaintextThis is not just theory. The study Neither Snow Nor Rain Nor MITM, presented in 2015, measured the phenomenon on real traffic and reports evidence of downgrades affecting messages destined for Gmail in several countries.
RFC 2595, which describes STARTTLS for IMAP and POP3, puts it in writing too: a man-in-the-middle attacker can remove STARTTLS from the server's capability list.
The real problem: clients that do not insist on encryption
The crux is this: STARTTLS is a request, not an obligation. If the client is set to use it "when available" (opportunistic TLS), a server that says "I can't do it" is accepted. It is the default behaviour of PHPMailer and Symfony Mailer (see below), and it is what the attacker exploits. A site or script that sends mail automatically shows nothing to anyone: if the password goes out in the clear, no error appears.
PHPMailer (used by WordPress and countless back-office tools) has SMTPAutoTLS = true by default: if SMTPSecure is empty, it encrypts only when the server says it can, otherwise it carries on in plaintext. The typical snippet "that works" is this:
$mail->isSMTP();$mail->Host = 'mail.example.com';$mail->Port = 587;$mail->SMTPAuth = true;$mail->Username = 'noreply@example.com';$mail->Password = $password;// SMTPSecure not set: opportunistic encryption, strippableSymfony Mailer behaves the same way with the smtp:// scheme: the documentation says it uses encryption when the OpenSSL extension is enabled and the server supports STARTTLS, and in the code the automatic mode (auto_tls) is on by default. If the server does not advertise it, the mail goes out in plaintext. Laravel is built on Symfony Mailer: in version 12's MailManager the scheme is the one set in scheme (the MAIL_SCHEME variable) and, if it is missing, it is smtps on port 465 and smtp on any other port. A typical .env configuration is therefore:
MAIL_MAILER=smtpMAIL_HOST=mail.example.comMAIL_PORT=587 # smtp scheme: opportunistic STARTTLSIn Python too, certificate verification is not automatic: in the CPython source, if you do not pass an SSL context to smtplib.SMTP.starttls(), smtplib.SMTP_SSL or imaplib.IMAP4_SSL, one is created that verifies neither the certificate nor the hostname (ssl._create_stdlib_context is an alias of _create_unverified_context). Another classic mistake is turning off verification to "make it work" with a server whose certificate is expired or self-signed (/novalidate-cert in PHP IMAP, which validates certificates by default; verify_peer=0 in Symfony; SMTPOptions with verify_peer => false in PHPMailer). At that point encryption is there, but whoever sits in the middle can present any certificate they like: the connection is encrypted towards the attacker.
Why implicit TLS is better
With implicit TLS the client connects to a dedicated port (465 for sending, 993 for IMAP, 995 for POP3) and the very first thing it exchanges is the TLS handshake. There is no plaintext phase to tamper with and no STARTTLS capability to delete: if TLS does not start, the connection drops and that is it. The client cannot "slip" into plaintext even if it wanted to.
There are other advantages that matter in practice:
- Less complexity, fewer bugs. STARTTLS requires handling the switch from plaintext to encrypted within the same data stream. A whole family of vulnerabilities was born here: the STARTTLS command injection (for example CVE-2011-0411 in Postfix) made the server process, as if it were inside the encrypted session, a command sent in plaintext. RFC 8314 cites it (CERT VU#555316) and notes that the error was made independently by multiple implementers, which suggests a less secure architecture than implicit TLS. In 2021 the study Why TLS is better without STARTTLS (USENIX Security) found more than 40 vulnerabilities in STARTTLS implementations and about 320,000 vulnerable mail servers.
- It is what the standard recommends. RFC 8314 (2018) names implicit TLS as the preferred way to submit and read mail, asks servers to offer it and to phase out cleartext as soon as practicable, and asks clients not to send credentials unless the session is properly encrypted and to validate the certificate.
- Port 465 is official again. Historically it had been registered as "smtps", then the registration was revoked; RFC 8314 assigned it again, under the name
submissions, to mail submission over TLS.
The RFC is honest on one point: if both sides are implemented correctly and the client insists on encryption, there is no significant difference between STARTTLS on 587 and implicit TLS on 465. The problem is that "if implemented correctly" is a fragile condition, and implicit TLS does not need it.
How to configure it properly, client side
The rule is the same in every language: implicit TLS port, verified certificate, no fallback.
PHPMailer, SMTP on 465:
$mail->isSMTP();$mail->Host = 'mail.example.com';$mail->Port = 465;$mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS; // implicit TLS$mail->SMTPAutoTLS = false;$mail->SMTPAuth = true;Laravel 12, smtps scheme (implicit TLS) instead of smtp. On port 465 it picks it by itself, but it is better to write it explicitly:
MAIL_MAILER=smtpMAIL_HOST=mail.example.comMAIL_PORT=465MAIL_SCHEME=smtpsIf for some reason you have to stay on STARTTLS (587), with Symfony Mailer you should at least enforce require_tls: if the server does not offer encryption, sending fails with an exception instead of going out in plaintext.
smtp://user:password@mail.example.com:587?require_tls=truePython, IMAP and SMTP with a context that verifies certificate and hostname (ssl.create_default_context() sets CERT_REQUIRED and check_hostname):
import imaplib, smtplib, ssl
ctx = ssl.create_default_context() # verifies chain and hostname
imap = imaplib.IMAP4_SSL("mail.example.com", 993, ssl_context=ctx)smtp = smtplib.SMTP_SSL("mail.example.com", 465, context=ctx)With PHP IMAP the /ssl flag uses TLS from the start, while /tls forces STARTTLS and rejects servers that do not support it; certificate validation is on by default and /novalidate-cert turns it off. For mail programs the rule is the same: pick the "SSL/TLS" option with ports 993, 995 and 465.
How to configure it properly, server side
The server must stop accepting plaintext, otherwise any misconfigured client stays vulnerable. Postfix, submissions service in master.cf:
submissions inet n - n - - smtpd -o smtpd_tls_wrappermode=yes -o smtpd_sasl_auth_enable=yesThis is the example from the Postfix documentation: in wrapper mode the server always uses TLS instead of offering STARTTLS. If you also keep 587, the documentation describes smtpd_tls_security_level = encrypt (the server announces STARTTLS and accepts no mail without encryption; the documentation advises against it for a public MX server, so use it only on the submission service) and smtpd_tls_auth_only = yes (authentication is offered only after STARTTLS). In Dovecot, ssl = required makes any authentication attempt fail before TLS is on, and you can leave only the implicit TLS ports enabled, because port = 0 disables a listener:
ssl = required
service imap-login { inet_listener imap { port = 0 } # plain IMAP/STARTTLS off inet_listener imaps { port = 993 ssl = yes }}service pop3-login { inet_listener pop3 { port = 0 } inet_listener pop3s { port = 995 ssl = yes }}Turning off ports 143 and 110 can break old clients: before doing it, read the logs to see who still uses them and update their settings.
Port 25 is a different story
All of this concerns mail that a user or application sends to their own server or reads from their own server. Between different mail servers (port 25) implicit TLS is not practical: RFC 8314 observes that the MX infrastructure has no way to specify a different port. Encryption therefore stays opportunistic, and RFC 7672 recalls that the server's indication of TLS support can easily be suppressed by a man-in-the-middle attacker. The stripping risk remains here, and there are dedicated countermeasures: MTA-STS (RFC 8461) and DANE (RFC 7672) let the receiving domain declare "you reach me only encrypted". They are the natural complement to SPF, DKIM and DMARC: those authenticate the sender, these protect the path.
How to check that your server holds up
With openssl you can see what a server offers in a few seconds. A check on the implicit mode:
openssl s_client -connect mail.example.com:465 -briefopenssl s_client -connect mail.example.com:993 -briefAnd on the STARTTLS mode, to see what the server announces (-starttls accepts smtp, pop3 and 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 143The last command queries port 143 without TLS. RFC 2595 allows an IMAP server to advertise LOGINDISABLED, meaning plaintext LOGIN is off; the same RFC warns that this only protects against passive eavesdropping, because an active attacker can remove the announcement. The test that matters is trying an authentication before STARTTLS: a properly configured server refuses it (with Dovecot, ssl = required makes authentication fail before TLS). If it accepts, a badly configured client can send it credentials in the clear, and an attacker can force it to. If you also manage TLS certificates heading to 47 days you have another reason to check: an expired certificate on the mail server is the most common excuse for disabling verification on the client.
The most frequent mistakes, in short
- "It works, so it is encrypted." Not true: opportunistic STARTTLS works in plaintext too, and the mail still arrives.
- Certificate verification disabled because the certificate is expired or self-signed. You fix the certificate, not the verification.
- Reused credentials across mail, control panel and other services: if they went through in plaintext once, they must be changed. A leaked password is ideal material for brute force attacks.
- Ports 143/110 left open "just in case", with plaintext still allowed.
- No monitoring: an application sending in plaintext because of a configuration error raises no alarm. RFC 8314 recommends that servers record the negotiated cipher suite in the
Receivedheader and in connection and authentication logs: checking them periodically shows who connects without TLS.
In short
STARTTLS stripping is not a sophisticated attack: it is enough to delete one line from a conversation that starts in plaintext. It works because many clients, including PHPMailer, Symfony Mailer and many script libraries, treat encryption as an option to use "if it is there". Implicit TLS on ports 465, 993 and 995 removes the problem at the root: either the connection is encrypted or it does not exist. When you cannot give up STARTTLS, you must at least enforce it (require_tls, smtpd_tls_auth_only, ssl = required) and verify the certificate. And on the path between servers, where implicit TLS does not exist, you need MTA-STS or DANE.
Sources
- 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
- Dovecot documentation - SSL
- Postfix documentation - TLS_README
- PHP documentation - 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 - SMTPSecure and SMTPAutoTLS properties
