This is a writeup about a flaw that I found recently, and that
existed in multiple implementations of SMTP (Simple Mail Transfer
Protocol) over TLS (Transport Layer Security) including my Postfix
open source mailserver. I give an overview of the problem and its
impact, how to find out if a server is affected, fixes, and draw
lessons about where we can expect similar problems. A time line
is at the end.
For further reading:
http://www.postfix.org/CVE-2011-0411.html (extended writeup)
Problem overview and impact
The TLS protocol encrypts communication and protects it against
modification by other parties. This protection exists only if a)
software is free of flaws, and b) clients verify the server's TLS
certificate, so that there can be no "man in the middle" (servers
usually don't verify client certificates).
The problem discussed in this writeup is caused by a software flaw.
The flaw allows an attacker to inject client commands into an SMTP
session during the unprotected plaintext SMTP protocol phase (more
on that below), such that the server will execute those commands
during the SMTP-over-TLS protocol phase when all communication is
supposed to be protected.
The injected commands could be used to steal the victim's email or
SASL (Simple Authentication and Security Layer) username and password.
This is not as big a problem as it may appear to be. The reason
is that many SMTP client applications don't verify server TLS
certificates. These SMTP clients are always vulnerable to command
injection and other attacks. Their TLS sessions are only encrypted
but not protected.
A similar plaintext injection flaw may exist in the way SMTP clients
handle SMTP-over-TLS server responses, but its impact is less
interesting than the server-side flaw.
SMTP is not the only protocol with a mid-session switch from plaintext
to TLS. Other examples are POP3, IMAP, NNTP and FTP. Implementations
of these protocols may be affected by the same flaw as discussed here.
The problem is easy to demonstrate with a one-line change to the
OpenSSL s_client command source code (I would prefer scripting, but
having to install Perl CPAN modules and all their dependencies is
more work than downloading a .tar.gz file from openssl.org, adding
eight characters to one line, and doing "./config; make").
The OpenSSL s_client command can make a connection to servers that
support straight TLS, SMTP over TLS, or a handful other protocols
over TLS. The demonstration with SMTP over TLS involves a one-line
change in the OpenSSL s_client source code (with OpenSSL 1.0.0, at
line 1129 of file apps/s_client.c).
With this change, the s_client command sends the plaintext STARTTLS
command ("let's turn on TLS") immediately followed by an RSET command
(a relatively harmless protocol "reset"). Both commands are sent
as plaintext in the same TCP/IP packet, and arrive together at the
server. The "\r\n" are the carriage-return and newline characters;
these are necessary to terminate an SMTP command.
When an SMTP server has the plaintext injection flaw, it reads the
STARTTLS command first, switches to SMTP-over-TLS mode, and only
then the server reads the RSET command. Note, the RSET command was
transmitted during the plaintext SMTP phase when there is no
protection, but the server reads the command as if it was received
over the TLS-protected channel.
Thus, when the SMTP server has the flaw, the s_client command output
will show two "250" SMTP server responses instead of one. The first
"250" response is normal, and is present even when the server is
not flawed. The second "250" response is for the RSET command, and
indicates that the SMTP server has the plaintext injection flaw.
$ apps/openssl s_client -quiet -starttls smtp -connect server:port
[some server TLS certificate details omitted]
250 some text here <=== Normal response, also with "good" server.
250 more text here <=== RSET response, only with flawed server.
Anatomy of the flaw: it's all about the plumbing
Whether a program may have the plaintext injection flaw depends on
how it adjusts the plumbing, as it inserts the TLS protocol layer
in-between the SMTP protocol layer and the O/S TCP/IP protocol
layer. I illustrate this with examples from three open source MTAs:
Postfix, Sendmail and Exim. The diagram below is best viewed with
a fixed-width font, for example, from the Courier family.
Postfix MTA Sendmail MTA Exim MTA
before/after before/after before/after
switch to TLS switch to TLS switch to TLS
SMTP SMTP SMTP SMTP SMTP SMTP <= SMTP layer
|| || || || || ||
stream stream stream stream' || ||
buffers buffers buffers buffers' rw r'w' <= stream layer
rw r'w' rw r'w' || ||
|| || || || || ||
|| TLS || TLS || TLS <= TLS layer
|| || || || || ||
O/S O/S O/S O/S O/S O/S <= TCP/IP layer
As shown in the diagram, both Postfix and Sendmail use an application-
level stream abstraction, where each stream has properties such as
read/write buffers, read/write functions (indicated with rw), and
other properties that are omitted for brevity.
When Postfix switches to SMTP over TLS, it replaces the plaintext
read/write functions (rw) with the TLS read/write functions (r'w').
Postfix does not modify any of the other stream properties including
the read/write buffers. A patch for qmail that introduces TLS
support uses the same approach. This approach of replacing only
the stream read/write functions, but not the buffers or other stream
properties, can introduce the plaintext injection flaw.
When Sendmail switches to SMTP over TLS, it replaces the entire
stream, along with its read/write buffers and read/write functions.
Exim, on the other hand, does not seem to have a stream abstraction
like Postfix, Sendmail or qmail. Instead of replacing streams or
stream properties, Exim replaces plaintext read/write functions
with TLS read/write functions. Because of their program structure,
Sendmail and Exim didn't suffer from the plaintext injection flaw.
Fixing the problem
There are two solutions to address the flaw, and both solutions can
be used together.
- Report an error when unexpected plaintext is received after the
STARTTLS command. As documented in RFC 3207, STARTTLS must be
the last command in a pipelined group. If plaintext commands are
received after STARTTLS, then that is a protocol violation.
This measure can also be implemented outside the MTA, for example
in a protocol-aware firewall.
- If a program uses the same input buffer before and after the
switch to TLS, it should discard the contents of the input buffer,
just like it discards SMTP protocol information that it received
during the plaintext protocol phase.
This plaintext injection problem is likely to recur when some
development moves the plaintext-to-ciphertext switch outside the
application: for example, into the kernel, into the local hardware,
into a proxy, or into other infrastructure. This encourages
applications to use the same application-level streams and buffers
and read/write functions before and after the switch to ciphertext.
When this migration happens, plaintext injection becomes once more
Jan 5 2011: While finishing Postfix for its annual release, I found
and fixed this flaw in the SMTP server and client implementations,
where it had been sitting ever since TLS support was adopted.
Jan 6-10 2011: As we investigated the scope of the problem, Victor
Duchovni (co-developer) discovered that other implementations were
also affected including security providers and security appliances.
Jan 11 2011: Contact CERT/CC to help coordinate with the problem's
Mar 7 2011: Public announcement, and Postfix legacy release updates.