CVE-2019-16928: Exim远程堆溢出漏洞PoC预警分析
2019-09-28 17:26

报告编号:B6-2019-092801

报告来源:360-CERT

报告作者:360-CERT

更新日期:2019-09-28

0x00 漏洞摘要

2019年9月28日,360-CERT监测到Exim官方发布了编号为CVE-2019-16928的远程堆溢出漏洞预警,补丁将于未来3天内正式发布,目前暂时没有缓解方案。

经360-CERT评估分析,该漏洞确实存在,存在远程利用的可行性,建议用户关注官方更新。

0x01 漏洞状态

影响版本

Exim 4.92 至 4.92.2 均受影响

修复方案

目前Exim已经在代码仓库 http://git.exim.org/exim.git 完成了更新。

正式版本在9月30号发布,预计修复版本为 4.92.3 ,各Linux发行版也正在进行修复测试中。

0x02 漏洞基本分析

官方在公告中表示,Exim源代码string.c文件中的string_vformat函数存在一处堆溢出漏洞,攻击者可以通过SMTP协议中的EHLO长字符来导致Exim的异常触发。

根据代码分析,和漏洞相关的string_vformat函数细节点主要如下:

  1. 是针对gstring类型的变长字符串进行格式化处理的函数。
    /* Growable-string */
    typedef struct gstring {
    int    size;        /* Current capacity of string memory */
    int    ptr;        /* Offset at which to append further chars */
    uschar * s;        /* The string memory */
    } gstring;
    
  2. 函数流程
gstring *
string_vformat(gstring * g, BOOL extend, const char *format, va_list ap)
{
        enum ltypes { L_NORMAL=1, L_SHORT=2, L_LONG=3, L_LONGLONG=4, L_LONGDOUBLE=5, L_SIZE=6 };

        int width, precision, off, lim;
        const char * fp = format;       /* Deliberately not unsigned */

        string_datestamp_offset = -1;   /* Datestamp not inserted */
        string_datestamp_length = 0;    /* Datestamp not inserted */
        string_datestamp_type = 0;      /* Datestamp not inserted */

#ifdef COMPILE_UTILITY
        assert(!extend);
        assert(g);
#else

        /* Ensure we have a string, to save on checking later */
        if (!g) g = string_get(16);
#endif  /*!COMPILE_UTILITY*/

        lim = g->size - 1;      /* leave one for a nul */
        off = g->ptr;           /* remember initial offset in gstring */

        /* Scan the format and handle the insertions */

        while (*fp)                                     /* 进入 format 的处理流程  */
        {
                int length = L_NORMAL;
                int *nptr;
                .......
                        case 's':
                        case 'S':                   /* Forces *lower* case */
                        case 'T':                   /* Forces *upper* case */
                                s = va_arg(ap, char *);

                                if (!s) s = null;
                                slen = Ustrlen(s);

INSERT_STRING:              /* Come to from %D or %M above */  /* 主要是在字符串的拼接方面出的问题 */

                                {
                                        BOOL truncated = FALSE;
                                        ***
                                       else if (g->ptr >= lim - width)
                                        {
                                                gstring_grow(g, g->ptr, width - (lim - g->ptr)); /* 实际要拼接的长度应该是 width 决定的,具体看后续的 sprintf  */
                                                lim = g->size - 1;
                                                gp = CS g->s + g->ptr;                          /* 拼接的起始位置是 g-s + g->ptr 的位置了 */
                                        }

                                        g->ptr += sprintf(gp, "%*.*s", width, precision, s); /* sprintf是实际拼接函数, 最长的长度有width 决定 */
                                        if (fp[-1] == 'S')
                                                while (*gp) { *gp = tolower(*gp); gp++; }
                                        else if (fp[-1] == 'T')
                                                while (*gp) { *gp = toupper(*gp); gp++; }

                                        if (truncated) return NULL;
                                        break;

                                }  
                                    .........
         }
  1. 实验

在ubuntu 14.04 的环境中,笔者并没有看到实际的崩溃出现,但是越界访问的情况是存在的。至于为啥没有崩溃出现,可以自行再看gstring的自定义内存管理策略。

3.1 更改了份源码打了下日志

1591               else if (g->ptr >= lim - width)
1592               {
1593                       gstring_grow(g, g->ptr, width - (lim - g->ptr));
1594                       printf( "cyg07 test: g->szie %u width %u lim %u g-ptr %u\n", g->size, width, lim,  g->ptr );
1595                       lim = g->size - 1;
1596                       gp = CS g->s + g->ptr;
1597               }
1598 
1599               const int t_len = sprintf(gp, "%*.*s", width, precision, s);
1600               printf( "cyg07 test: t_len %u precision %u g->size %u g->ptr %u\n", t_len, precision, g->size, g->ptr );
1601               g->ptr += t_len;
1602               if (fp[-1] == 'S')
1603                       while (*gp) { *gp = tolower(*gp); gp++; }

3.2 在telnet中提交EHLO

# telnet 127.0.0.1 25
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
220 Server-27eb9350 ESMTP Exim 4.92.2 Sat, 28 Sep 2019 16:58:43 +0800
EHLO xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx // 长度自己找
250-Server-27eb9350 Hello xxxxxxxxxxxxxx.....
250-SIZE 52428800
250-8BITMIME
250-PIPELINING
250-CHUNKING
250-PRDR
250 HELP

3.3 在exim服务端我们可以看到相关的log, 已经可以观测出在实际拼接后gstring实例的大小是 4090 + 26 ,超过了实例本身的4097大小范围了。

# /usr/exim/bin/exim -bdf -dd
cyg07 test: g->szie 4097 width 4090 lim 128 g-ptr 26
cyg07 test: t_len 4090 precision 4090 g->size 4097 g->ptr 26

0x03 影响面评估

根据360安全大脑的QUAKE测绘数据显示:

  • 全球有1,800,669处端口受影响
  • 中国地区有9,722处端口受影响

public_image

0x04 时间线

2019-09-28 Exim官方发布漏洞预警

2019-09-28 360-CERT发布漏洞PoC分析预警

0x05 参考链接

  1. https://seclists.org/oss-sec/2019/q3/253