Exim is an extremely popular mail server which makes up 60% of mail servers on the internet. Vulnerabilities discovered in this that allow for code execution (especially without auth) put a lot of companies and people at risk.
The Exim binary has a set-user-ID-as-root and has a user to do so called the exim user. The log directory belongs to the exim user but is written to by the Exim root binary. By creating a symlink or hardlink on this file, arbitrary content can be appended to a file, such as /etc/passwd to obtain root.
Exim has a spool directory which handles incoming mail, which is also owned by the Exim user. An attacker with the exim privileges can write arbitrary data into the spool directory. Although this seems fine, this desync creates a much larger attack surface, where Qualys shows an arbitrary file write primitive.
The flag -oP override_pid_file_path option allows to create an arbitrary file as root. However, we do not control the contents of the file. Still, could be used to turn off ASLR in SYS or something like that. An similar vulnerability exists within the same flag but for file deletion instead via a race condition between file validation and file deletion with a symlink.
In another local command, there is a heap buffer overflow in the deliver_selectstring_sender function. This appears to process a string via sprintf that can be a string of arbitrary length. The POC is literally just exim -S `perl -e 'print "A" x 128000'`.
For debugging and logging purposes Exim copies the current working directory into a heap-based buffer. This results from the name of the current directory being placed into a heap buffer via strncopy, securely. However, after this call, the pointer is incremented by the size of the string being copied, which could be larger than the allowed buffer size.
If name is blank for the -F flag is used, the parse_fix_phrase function will still write a NULL byte to the buffer, even though a buffer of size 0 was created.
When Exim receives mail, it creates two files: an input and a data file. These lines of text (such as in mail) are separated by a newline (\n). An attacker can send a local email with a newline character in it, changing the orientation and understanding of the file being processed. Turning this into code execution requires the injecting of an Exim configuration string. This type of bug happens twice in the report. The article has a lot more into how they exploited this technique though!
The Exim filter allows for the execution of configuration strings in the program. Using a CLI flag, these can be tested on the Exim binary, which is totally fine as the Exim user via a forked process on the main binary. Since the fork runs under a privileges context, the file descriptors are still present from the privileged binary! By having the ability to send arbitrary data to privileged pipes, there are several injection points that could allow for memory corruption or command injection into the binary, which are shown in the writeup. To fix this, the FDs need to be created with the closed-on-exec.
The authors found several integer overflows via bad string parsing in C. One of them is via the size of a growable string overflowing (took 5 days lolz), another is the amount of recipients on an email and finally is in an incoming mail via only sending newlines which leads to code execution.
While setting up an SMTP connection, there is a bug that leads to an out of bounds read. The index smtp_ch_index can be reset to 0 while later being processed as smtp_ch_index-1 in an array. This leads to an out of bounds read that is not super exploitable but interesting!
spool_read_header calls fgets to read lines from the spool header file. The first section is a very large buffer with a fixed size. Since the buffer is not enlarged, it is vulnerable to a truncation issue, since the call to fgets restricts the size. Using this, a newline injection vulnerability is possible, but much less versatile as before.
Exim makes good use of function pointers to abstract away the type of reading it should do. For instance, there is a TLS encrypted stream or a regular stream. When using the BDAT series of commands, another function pointer is added though, which should eventually be set back to the original one. In several cases bad code paths, it is possible to NOT get this function pointer reset, causing unintended consequences to happen. This only leads to a stack exhaustion via a re-entry problem.
The function smtp_refill is used to read input characters from an SMTP client; it calls smtp_getc to read individual characters from the buffer. The function smtp_ungetc pushes characters back into the buffer after being read by the reading function above. By swiftly closing a connection after no data has been written, a call to smtp_ungetc can be made with EOF (-1) that will write to the -1 index.
A use after free exists in the TLS functionality. A struct pointer is saved to a local stack to be used. However, at some point, the function smtp_setup_msg could be called, which frees all allocated POOL_MAIN memory. Since this local variable is not set to NULL, it creates a use after free vulnerability. This use after free is on a VERY powerful object, as it allows for a memory leak and a write-what-where primitive.
The DKIM (domainkeys identified mail) signatures have a fairly simple out of bounds read. An attacker controlled size controls the amount of data being compared, even though the actual size may be different. The bug is in a memcmp, making this a very weird thing to exploit.
This is a wonderful demonstration of the diversity of vulnerabilities that can be found in complex software. The exploit techniques are fascinating with detailed docs on how and why they did certain actions. The custom heap management system of Exim is extremely pwnable, but will likely never be fixed. Additionally, the cmd strings being executed is a favorite exploit technique of the authors. All of the bugs that are exploitable for LPE or RCE have amazing docs on how they did it.