Openfind Mail2000电子邮件系统RCE漏洞分析

iso60001  762天前

邮件系统作为大部分企业主要的信息传递方式,在各大组织都占有举足轻重的地位。攻击者一旦掌控了邮件服务器,不仅可以窃取邮件,还能为进一步的渗透做好准备。本篇文章将介绍在Openfind Mail2000这套软件上所发现的内存漏洞,以及相关的攻击手法。

此漏洞是在2018年被发现,已通报给Openfind并迅速被修补,相关用户也得到了更新帮助。

Openfind Mail2000

Mail2000 是由台湾地区厂商Openfind开发的简单易用的电子邮件系统,被广泛使用于政府机关、教育机构,如台北市教育局、中科院,以及台湾科技大学都是Mail2000的用户。常见登录界面如下所示:

22.png

这次所讲述的漏洞,便是从这个Web界面,攻陷整台服务器!

服务器架构

Mail2000提供的Web界面使用了CGI(Common Gateway Interface)技术来实现。而大多数Web服务器实现CGI的方式如下图:

33.png

首先由httpd接受客户端请求,然后根据对应的CGI路径,执行相对应的CGI文件。大多数的开发者会根据需求,将常见的共用功能编写成library,供CGI调用。

从底层来看,可发现虽然称为“Web服务器”,但仍有许多组件和二进制相关!例如httpd为了运行效率,多是由C/C++所编写,而其它像是库、扩充的模块、各页面的CGI也经常如此。因此,二进制相关的漏洞,便是我们这次的主体!

漏洞

这个漏洞位于Openfind中的libm2kc库中,其中包含了各种CGI通用函数,如参数解析和文件处理等等,而此次漏洞就发生在参数解析的部分。由于参数处理是很底层且基本的功能,因此影响的范围非常的大,就连Openfind的其它产品也同样受影响!

这个漏洞的触发条件如下:

  • 攻击者通过multipart形式发送HTTP的POST请求

  • POST请求传输的文件内容超过200项

multipart是HTTP协议中用来处理文件传输的一种格式,示例如下:

Content-Type: multipart/form-data; boundary=AaB03x 

--AaB03x

Content-Disposition: form-data; name="files"; filename="file1.txt"
 Content-Type: text/plain



... contents of file1.txt ...


--AaB03x--


而在libm2kc中,使用了数组来储存参数:

g_stCGIEnv.param_cnt = 0;
while(p=next_param())
{
  g_stCGIEnv.param[param_cnt] = p;
  g_stCGIEnv.param_cnt++;
}

这个数组为全局变量g_stCGIEnv中的param,在存入param数组时,代码并没有检查是否已超过声明的数组大小,造成了越界写入。

44.png

需要注意的是,param数组所储存的为指向字符串位置的指针,而非字符串本身。

struct param
{
    char *key;
    char *value;
    int flag;
};

因此当触发越界写入时,写入内存的值也是一个个指向字符串的指针,而被指向的字符串内容则是造成溢出的参数。

55.png

漏洞利用

要利用越界写入的漏洞,就要先了解利用这个溢出可以做什么,发生溢出的全局变量结构如下:

00000000 CGIEnv  struc ; (sizeof=0x6990, mappedto_95)
00000000 buf dd ?; offset
00000004 length  dd ?
00000008 field_8 dd 6144 dup(?)  ; offset
00006008 param_arr   param 200 dup(?)
00006968 file_vecdd ?; offset
0000696C vec_len dd ?
00006970 vec_cur_len dd ?
00006974 arg_cnt dd ?
00006978 field_6978  dd ?
0000697C errcode dd ?
00006980 method  dd ?
00006984 is_multipartdd ?
00006988 read_func   dd ?
0000698C field_698C  dd ?
00006990 CGIEnv  ends

溢出的数组为其中的param_arr,因此在其之后的变量都可能被覆盖。包括post_filesvec_lenvec_cur_lenarg_cnt等等。其中最吸引我注意的是file_vec这个变量,这是一个用来管理POST上传文件的vector,大部分的vector结构如下所示:

66.png

使用size记录数组的总长度,end记录目前空间已用到哪里,这样就可以在容量不够的时候进行扩充。因此我们若能利用漏洞覆盖vector的指针,就有可能成功利用漏洞!借由覆盖这个vector指针,我们可以达到伪造一个上传文件及其所有相关变量的效果,而这个上传文件的结构里包含了各种常见的文件相关变数,像是路径、文件名,和Linux中用来管理文件的FILE结构。而FILE结构便是此次攻击的关键!

FILE Structure Exploit

这次的攻击使用了FILE structure exploit,这是近几年刚发现的攻击手法,由angelboy在HITCON CMT公开

77.png

FILE结构是Linux中用来做文件处理的结构,像是STDINSTDOUTSTDERR,或者是调用fopen后回传的结构都属于FILE。而这个结构之所以能成为漏洞利用的突破口主要在于它所包含的vtable指针:

struct _IO_FILE_plus
{
  FILE file;
  const struct _IO_jump_t *vtable;
};

vtable中记录了各种函数指针,对应各种文件处理相关的功能:

struct _IO_jump_t
{
    JUMP_FIELD(size_t, __dummy);
    JUMP_FIELD(size_t, __dummy2);
    JUMP_FIELD(_IO_finish_t, __finish);
    /* ... */
    JUMP_FIELD(_IO_read_t, __read);
    JUMP_FIELD(_IO_write_t, __write);
    JUMP_FIELD(_IO_seek_t, __seek);
    JUMP_FIELD(_IO_close_t, __close);
    /* ... */
};

因此,如果我们可以篡改、伪造这个vtable,就可以在程序处理文件时候,劫持处理流程!至此,我们制定出以下的攻击步骤:

1.建立连接,调用CGI

2.使用大量参数,覆盖vector指针

3.伪造上传文件中的FILE*,指向伪造的FILE结构

4.在CGI流程中调用FILE相关的操作fread,fwrite,fclose, …

5.劫持程序流程

现在我们已经知道终点是调用一个FILE操作,那么就可以开始往回找哪个函数是CGI常用的FILE操作,又有哪一些CGI可以作为入口点,这样才能串起攻击链!我们首先对使用到POST file的相关函数进行研究,并选定了目标MCGI_VarClear()

MCGI_VarClear()在许多用到FILE的CGI中都有被调用,它用于在程序结束前将g_stCGIEnv清空,包括将动态配置的内存释放掉,以及将所有FILE关闭,也就是调用fclose(),这也意味着可以通过vtable进行劫持!我们可以使用这个越界写入漏洞覆盖file_vec,而指向的内容就是request请求的参数,这样便可以伪造为上传的文件!结构如下所示:

88.png

我们最终目标就是将FILE*指向伪造的结构,借由伪造的vtable劫持程序流程!因此,我们需要将FILE*这个指针指向一个内容可控的位置,但是其实我们并不知道该指到哪里去,因为Linux上的有一个内存防御机制-ASLR 。

Address Space Layout Randomization

ASLR使得每次程序在执行并载入内存时,会随机使用不同的位置,我们可以尝试使用cat /proc/self/maps观察每一次执行时的内存位置是否相同:

99.png

ASLR在大部分的环境中都是默认开启的,因此在编写exploit时,我们不知道该指到哪里。

而且这个机制在CGI的架构下会造成更大的阻碍,一般的服务器的攻击流程可能是这样:

100.png

貌似可以先窃取地址,再进一步攻击。但在CGI架构中却是这样:

110.png

在这个情况下,得到的地址是无法在后续攻击中使用的!一个CGI执行完就结束了,下一个请求又是全新的CGI!

为了应对这个问题,我们最后编写了两个exploit,攻击的手法根据CGI的二进制文件而有不同。

Post-Auth RCE - /cgi-bin/msg_read

第一个exploit的入口点是一个需要登入的页面,该程序较大、功能也较多。在这个exploit中,我们使用了堆喷射的手法来解决ASLR,也就是在堆中填入大量重复的数据,这样我们就有很高的机率可以猜到它的位置。

120.png

而喷射的内容就是大量伪造好的FILE结构,包含伪造的vtable。从这个二进制文件中,我们找到了一个十分实用的gadget:

xchg eax, esp; ret

这个gadget的作用在于,我们可以改变栈的位置,而刚好此时的eax指向内容是可控的,因此整个栈的内容都可以伪造,也就是说我们可以使用ROP(Return-oriented programming)来攻击!于是我们在伪造的vtable中设置了栈移动的gadget以及后续利用的ROP攻击链,进行ROP攻击!

130.png

现在可以拿shell了嘛?不,其实还有一个大问题,由于前面提到的防御机制ASLR,我们找不到system的位置!这个二进制文件本身提供的代码并不足以反弹一个shell,因此我们希望可以直接利用libc当中的system函数来达成目的,但正如前面所提到的,每次载入内存位置都是随机的,我们并不知道system的确切位置!

经过我们再次仔细研究,发现了一件非常特别的事,这个程序理论上是打开了NX,也就是可写区域不可执行的保护机制。

140.png

但是实际执行的时候,栈的执行权限没有被禁止!

150.png

不论原因为何,我们可以利用这个可执行区域,将shellcode放上去执行!

然而,这个攻击是需要登入的,对于追求完美的DEVCORE研究组来说,这并不足够!因此我们研究了其它的攻击路径!

Pre-Auth RCE - /cgi-bin/cgi_api

在搜索了所有CGI入口点以后,我们找到了一个不需要登入,同时又会呼叫MCGI_VarClear()的CGI——/cgi-bin/cgi_api。正如其名,它就是一只调用API的接口,因此程序本身非常的小,几乎是调用完库就结束了,所以也不再有和栈相关的gadget可以利用。

这时,由于我们已经得知栈是可执行的,因此其实我们是可以跳过ROP这个步骤,直接将shellcode放置在栈上。这里利用到一个CGI的特性——HTTP的相关变量会放在环境变量中,就像以下常见变量:

  • HTTP_HOST
  • REQUEST_METHOD
  • QUERY_STRING

而环境变量事实上就是被放置在栈的最末端,也就是可执行区域,因此我们只要伪造vtable,直接调用shellcode就可以了!

160.png

当然这时候同样出现了一个问题:我们仍旧没有栈的内存位置。这个时候有些人可能会陷入一个死胡同,觉得攻击就是要一次到位,像个狙击手一样一击必杀,但实际上可能是拿导弹枪把敌人炸飞:

170.png

换个角度思考,这个二进制是32 bits的,因此这个位置有1.5字节是随机的,总共有16的3次方个可能的组合,所以平均只要4096次请求就可以撞对一次!这对于现在的电脑、网络来说其实也就是几分钟之间的事情,因此直接暴力破解就可!于是我们最终的exploit流程就是:

1.发送POST请求至cgi_api

QUERY_STRING中放入shellcode

2.触发越界写入,覆盖file_vec

越界参数准备好伪造的FILEvtable

3.cgi_api结束前调用MCGI_VarClear

4.跳转至vtable上的shellcode位置,弹回shell

最后我们成功写出了不用认证的RCE攻击链,并且这个exploit不会因为版本不同而受影响!而在实际遇到的案例中也证明了这个exploit的通用性,我们曾在一次的演练当中借由Mail2000的这个1day作为突破口,成功获取目标的VPN数据,进一步往内网渗透!

漏洞修复

此漏洞已在2018/05/08 发布的Mail2000 V7 Patch 050版本中完成了修复。修复编号为OF-ISAC-18-002、OF-ISAC-18-003。

本文由白帽汇整理,不代表白帽汇任何观点和立场
来源:https://devco.re/blog/2019/12/23/how-binary-dog-survives-in-web-world/

最新评论

昵称
邮箱
提交评论