Exim 4.9.1 Remote Command Execution

Qualys discovered a remote command execution vulnerability in Exim versions 4.87 to 4.91.

MD5 | 681c0119abff3127959534940a5b83d5


Qualys Security Advisory

The Return of the WIZard: RCE in Exim (CVE-2019-10149)


Local exploitation
Remote exploitation
- Non-default configurations
- Default configuration

Boromir: "What is this new devilry?"
Gandalf: "A Balrog. A demon of the Ancient World."
-- The Lord of the Rings: The Fellowship of the Ring


During a code review of the latest changes in the Exim mail server
(https://en.wikipedia.org/wiki/Exim), we discovered an RCE vulnerability
in versions 4.87 to 4.91 (inclusive). In this particular case, RCE means
Remote *Command* Execution, not Remote Code Execution: an attacker can
execute arbitrary commands with execv(), as root; no memory corruption
or ROP (Return-Oriented Programming) is involved.

This vulnerability is exploitable instantly by a local attacker (and by
a remote attacker in certain non-default configurations). To remotely
exploit this vulnerability in the default configuration, an attacker
must keep a connection to the vulnerable server open for 7 days (by
transmitting one byte every few minutes). However, because of the
extreme complexity of Exim's code, we cannot guarantee that this
exploitation method is unique; faster methods may exist.

Exim is vulnerable by default since version 4.87 (released on April 6,
2016), when #ifdef EXPERIMENTAL_EVENT became #ifndef DISABLE_EVENT; and
older versions may also be vulnerable if EXPERIMENTAL_EVENT was enabled
manually. Surprisingly, this vulnerability was fixed in version 4.92
(released on February 10, 2019):


but was not identified as a security vulnerability, and most operating
systems are therefore affected. For example, we exploit an up-to-date
Debian distribution (9.9) in this advisory.

Local exploitation

The vulnerable code is located in deliver_message():

6122 #ifndef DISABLE_EVENT
6123 if (process_recipients != RECIP_ACCEPT)
6124 {
6125 uschar * save_local = deliver_localpart;
6126 const uschar * save_domain = deliver_domain;
6128 deliver_localpart = expand_string(
6129 string_sprintf("${local_part:%s}", new->address));
6130 deliver_domain = expand_string(
6131 string_sprintf("${domain:%s}", new->address));
6133 (void) event_raise(event_action,
6134 US"msg:fail:internal", new->message);
6136 deliver_localpart = save_local;
6137 deliver_domain = save_domain;
6138 }
Related Posts