报告编号:B6-2019-103101
报告来源:360-CERT
报告作者:360-CERT
更新日期:2019-10-31
0x00 漏洞背景
2019年9月6日18:00,exim发布exim-4.92.2版本修复了CVE-2019-15846,攻击者可以利用此漏洞远程获取root权限。漏洞由qualys发现并报告。
当exim支持TLS时,攻击者发送以'0'结束的SNI,此时string_unprinting函数调用string_interpret_escape函数处理转义序列,由于string_interpret_escape函数中没有处理'0'的情况,造成了越界读。qualys已经证实可以利用此漏洞远程获取root权限。
2019年10月8日,synacktiv发布POC以及漏洞分析,360CERT对此进行复现和分析。
影响版本
exim < 4.92.2版本
环境搭建
(1)poc 地址:https://github.com/synacktiv/Exim-CVE-2019-15846
将1i7Jgy-0002dD-Pb-D 和1i7Jgy-0002dD-Pb-H 放入/var/spool/exim/input文件夹下,需要root权限。
(2)启动exim:
sudo /usr/exim/bin/exim -bd -q30m -dd
exim会在启动过程中去读取配置文件,执行到string_unprinting()漏洞函数,所以不能在启动后用gdb附加,而且启动后的触发漏洞的进程也会退出。根据poc作者的操作是set follow-fork-mode child就可以附加到漏洞进程,笔者按该方法无法正常启动exim进行调试,所以换了个调试方法,在源码触发漏洞前加个等待读取的操作,再用gdb去附加漏洞进程。
/src/src/spool_in.c:
(3)附加漏洞进程:
(4)进行调试
0x01 漏洞分析
查找可利用的漏洞触发路径:
(1)大部分调用string_interpret_escape()的函数都对传入的字符串有限制。例如nextitem()(src/filter.c)检查了字符串缓冲区是否溢出。string_dequote()函数只从配置文件中获取字符串。
(2)tls_import_cert()->string_unprinting()->string_interpret_escape()
由于证书是pem格式,用Base64编码,所以不可能包含'0'序列
(3)src/spool_in.c
peerdn(src/spool_in.c)的使用并非默认配置,在Exim使用客户端证书验证时才会被调用
(4)最后关注到tls连接上,只要Exim支持tls连接,攻击者就可以发送sni,据此调用到string_unprinting() 和 string_interpret_escape() 函数
tls_in.sni = string_unprinting(string_copy(big_buffer + 9));
string_unprinting函数的作用是将输入缓冲区的内容(解析转义字符,如通过string_interpret_escape函数x62转成b)写入到输出缓冲区。
在string_unprinting中判断''进入string_interpret_escape流程,string_interpret_escape函数中没有对'0'的判断,可以继续读取字符串,并写入到输出缓存区中,因此造成越界读的同时也越界写了。过程如下图所示:
刚进入string_unprinting时:s = 0x1e16de8q = ss = 0x1e16df0p = 0x1e16deeoff = 0x6len = 0x8第一次memcpy:gdb-peda$ x/10gx 0x1e16de80x1e16de8: 0x005c666564636261 0x00006665646362610x1e16df8: 0x0000000000000000 0x00000000000000000x1e16e08: 0x0000000000000000 0x00000000000000000x1e16e18: 0x0000000000000000 0x00000000000000000x1e16e28: 0x0000000000000000 0x0000000000000000进入string_interpret_escape前p的值:gdb-peda$ p p$24 = (const uschar *) 0x1e16dee ""进入string_interpret_escape后p的值:gdb-peda$ p p$24 = (const uschar *) 0x1e16def ""
退出string_interpret_escape后p指针又自加了一次,所以一共自加两次,导致向前解析了'','0'两个字符,而'0'的下一个字符为刚刚memcpy的"abcdef",不为'0',所以while循环继续解析,导致第二次memcpy:
Guessed arguments:arg[0]: 0x1e16df7 --> 0x0 arg[1]: 0x1e16df0 --> 0x666564636261 ('abcdef')arg[2]: 0x6 arg[3]: 0x7 第二次memcpy后:gdb-peda$ x/10gx 0x1e16de80x1e16de8: 0x005c666564636261 0x61006665646362610x1e16df8: 0x0000006665646362 0x00000000000000000x1e16e08: 0x0000000000000000 0x00000000000000000x1e16e18: 0x0000000000000000 0x00000000000000000x1e16e28: 0x0000000000000000 0x0000000000000000
从越界读导致越界写。
exgen.py 构造的文件通过对堆的布局(需要在/var/spool/exim/input文件夹下放至少205个message-log文件),通过堆溢出将保存在堆中的文件名修改成../../../../../tmp/tote,并伪造sender_address,之后该字段保存的字符串会写入message-log文件(即../../../../../tmp/tote)中。
但该poc在测试环境中,堆无法布局成功(可能环境以及205个message-log文件不同),会覆盖top chunk,造成进程崩溃,但主进程会重新起进程。
使用exgen.py造成的堆布局(进入string_unprinting函数后)如下:
0x1210c10 0x2e000083 0x4010 Used None None0x1214c20 0x0 0x2020 Used None None0x1216c40 0x0 0x2ff0 Used None None0x1219c30 0x0 0x2020 Used None None0x121bc50 0x0 0x410 Used None Nonegdb-peda$ p s$6 = (uschar *) 0x1219ca0 'a' , "x00x00x00x00x00x00x00x00x00x20x00x00x00x00x00x00aaaaaaaaax2ex2ex2fx2e"...gdb-peda$ p len$14 = 0xfc8gdb-peda$ p yield_length $17 = {0x20, 0x1ae8, 0xffffffff}
之后堆溢出破坏了top chunk:
0x1210c10 0x2e000083 0x4010 Used None None0x1214c20 0x0 0x2020 Used None None0x1216c40 0x0 0x2ff0 Used None None0x1219c30 0x0 0x2020 Used None NoneCorrupt ?!
导致后面分配时错误,产生崩溃:
0x02 补丁分析
在string_interpret_escape函数中判断''后面是否为'0',如果是就不再自加一次,直接返回''所在的地址。退出string_interpret_escape后在string_unprinting自加一次p指针指向'0'的地址,while循环结束,不会造成越界读。
0x03 时间线
2019-09-06 exim发布新版本修复漏洞
2019-09-06 360CERT发布预警
2019-10-08 synacktiv发布poc
2019-10-31 360CERT对外发布漏洞分析报告
0x04 参考链接
- https://www.synacktiv.com/posts/exploit/scraps-of-notes-on-exploiting-exim-vulnerabilities.html
- http://exim.org/static/doc/security/CVE-2019-15846.txt
- https://git.exim.org/exim.git/blob/2600301ba6dbac5c9d640c87007a07ee6dcea1f4:/doc/doc-txt/cve-2019-15846/qualys.mbx
欢迎加入360-CERT团队,请投递简历到 caiyuguang[a_t]360.cn