【漏洞分析】一次因漏洞修补触发的漏洞—CVE-2016-6309漏洞详细分析

阅读量181046

|

发布时间 : 2016-12-20 10:51:55

https://p2.ssl.qhimg.com/t01ad650440dd9a83bc.png

作者:梁瘦叟

预估稿费:500RMB(不服你也来投稿啊!)

投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿

前言

openssl发布了一个安全级别为”严重”的UAF漏洞,该漏洞利用简单,只需要发一个tcp包就能触发漏洞,但后果严重,可能导致TLS相关的应用拒绝服务,甚至任意代码执行等后果。唯一的限制是该漏洞影响范围较小,仅影响1.1.0a版本的openssl,而该版本的openssl发布时间比较晚,实际使用的并不多。笔者对此次漏洞进行了一次详细分析,同时通过漏洞分析分享笔者关于安全的一些思考。


漏洞重现

此次漏洞仅影响版本为1.1.0.a的openssl,下面让我们一起来一步步重现此次漏洞。漏洞测试的系统为Ubuntu。如果不熟悉linux的朋友建议安装一个虚拟机进行测试。

第一步首先我们从github上下载源码并编译:

wget "https://github.com/openssl/openssl/archive/OpenSSL_1_1_0a.tar.gz"
tar -xf OpenSSL_1_1_0a.tar.gz
cd openssl-OpenSSL_1_1_0a
./config --debug
make -j4

如果编译成功,可以在apps目录下看到openssl执行程序。

在这里我们为了不影响系统原有的openssl,不执行 sudo make install命令,因此需要把生成的动态库文件libssl.so和libcrypto.so放到系统库目录下。

sudo cp ./libssl.so.1.1 /usr/local/lib
sudo cp ./libcrypto.so.1.1 /usr/local/lib

生成一张测试证书

./openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 –nodes

运行该命令后openssl会询问一些关于证书的相关信息,无视掉,直接一路enter就好了。

使用openssl的s_server子命令搭建SSL服务器,监听20443端口

./openssl s_server -key key.pem -cert cert.pem -accept 20443 –www

使用nc向openssl的本地20443端口发送异常的ssl握手包

nc localhost 20443 < send_content

openssl收到这一畸形的握手包后bang的一声down掉了!怎么样,利用是不是很简单呢?

send_content地址:

https://pan.baidu.com/s/1eRXpmgU

http://p0.qhimg.com/t01050b322f6d60d443.png

基础知识

接下来我们要对漏洞产生的原因和如何构造一个漏洞测试数据包进行学习,但是让我们首先来学习一些关于SSL的基础知识。

在漏洞重现中我们搭建了一个ssl服务器,下面我们打开wireshark进行抓包,捕获此次的SSL通信过程。在wireshark中设置过滤条件:tcp.port=20443,避免显示太多无用的通信包。

使用firefox与openssl的ssl服务器进行通信。在firefox的地址栏输入:

https://localhost:20443

此时firefox会提示你该网址不安全,不用理会这一提示,这是因为证书是我们为了测试生成的,不在firefox的可信根证书列表中。依次点击Advanced->Add Exception->Confirm Security Exception,确认安全例外网址https://localhost:20443。

http://p1.qhimg.com/t01e9cc3d0ec606da5b.png

这时回到wireshark的界面,可以看到wireshark已经抓到了本次ssl通信的数据包。

SSL通信的过程是这样的,首先客户端和服务器端经过三次握手建立TCP连接,然后客户端发送的第一个数据包通常被称为“client hello“,意思就是说client想要和server进行通信,首先要向服务器端say一下hello,这个hello包中包括了客户端需要交换的随机数,支持的加密算法等内容,但和本次漏洞相关的是SSL包的长度,就是图中标红的两个length,512和508,标明了SSL数据段的长度,正是因为openssl对长度的处理不当导致了此次漏洞。

http://p2.qhimg.com/t01bc3d4d475284a061.png


漏洞分析

目前,openssl已经发布了漏洞补丁,我们先来看看补丁(这里):

+static int grow_init_buf(SSL *s, size_t size) {
+
+    size_t msg_offset = (char *)s->init_msg - s->init_buf->data;
+
+    if (!BUF_MEM_grow_clean(s->init_buf, (int)size))
+        return 0;
+
+    if (size < msg_offset)
+        return 0;
+
+    s->init_msg = s->init_buf->data + msg_offset;
+
+    return 1;
+}
+
 /*
  * This function implements the sub-state machine when the message flow is in
  * MSG_FLOW_READING. The valid sub-states and transitions are:
@@ -545,9 +560,8 @@ static SUB_STATE_RETURN read_state_machine(SSL *s)
             /* dtls_get_message already did this */
             if (!SSL_IS_DTLS(s)
                     && s->s3->tmp.message_size > 0
-                    && !BUF_MEM_grow_clean(s->init_buf,
-                                           (int)s->s3->tmp.message_size
-                                           + SSL3_HM_HEADER_LENGTH)) {
+                    && !grow_init_buf(s, s->s3->tmp.message_size
+                                         + SSL3_HM_HEADER_LENGTH)) {
                 ssl3_send_(s, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
                 SSLerr(SSL_F_READ_STATE_MACHINE, ERR_R_BUF_LIB);
                 return SUB_STATE_ERROR;

上面以-开始的行意味着从源码中删除,以+号开始的意味着向源码中增加。

分析一下该补丁,补丁为BUF_MEM_grow_clean函数的调用增加了一层封装grow_init_buf。

接下来我们用GDB来实际调试一下:

gdb –args ./openssl s_server -key key.pem -cert cert.pem -accept 20443 –www

在补丁对应的行上下断点:

b statem.c:546

http://p7.qhimg.com/t0166e07dca5594dd24.png

朋友们可以手动跟一下函数运行的流程。对比一下正常的TLS握手包和畸形的TLS握手包对于openssl的运行流程有什么区别。

引发漏洞的根源在BUF_MEM_grow_clean函数中,该函数位于源码crypto/buffer/buffer.c文件中,我们来重点分析一下这个函数的流程。

在BUF_MEM_grow_clean函数中,有两个入参,第一个是openssl分配的结构,用于记录为此次clienthello包分配的内存的相关信息,第二个入参是数据包的长度,而这一长度是从我们传入的数据包中获得的,这也就意味着该参数是攻击者可控的。以下是BUF_MEM_grow_clean的代码。

size_t BUF_MEM_grow_clean(BUF_MEM *str, size_t len)
{
    char *ret;
    size_t n;
 
    if (str->length >= len) {             
        if (str->data != NULL)
            memset(&str->data[len], 0, str->length - len);
        str->length = len;
        return (len);
    }
    if (str->max >= len) {               
        memset(&str->data[str->length], 0, len - str->length);
        str->length = len;
        return (len);
    }
    /* This limit is sufficient to ensure (len+3)/3*4 < 2**31 */
    if (len > LIMIT_BEFORE_EXPANSION) {              
        BUFerr(BUF_F_BUF_MEM_GROW_CLEAN, ERR_R_MALLOC_FAILURE);
        return 0;
    }
    n = (len + 3) / 3 * 4;
    if ((str->flags & BUF_MEM_FLAG_SECURE))
        ret = sec_alloc_realloc(str, n);       
    else
        ret = OPENSSL_clear_realloc(str->data, str->max, n);
    if (ret == NULL) {
        BUFerr(BUF_F_BUF_MEM_GROW_CLEAN, ERR_R_MALLOC_FAILURE);
        len = 0;
    } else {
        str->data = ret;
        str->max = n;
        memset(&str->data[str->length], 0, len - str->length);
        str->length = len;
    }
    return (len);
}

通过使用GDB跟踪openssl对畸形TLS数据包的处理,该TLS握手包的的长度段的值必须同时不满足代码1,代码2,代码3的判断并进入代码4处。即漏洞被触发需要同时满足str->length < len,str->max < len,len < LIMIT_BEFORE_EXPANSION,三个条件。

其中str->length也是由攻击者传入的,

str->max和LIMIT_BEFORE_EXPANSION都是固定的。

str->max的值为21684(0x54b4)

LIMIT_BEFORE_EXPANSION的值定义在/crypto/buffer/buffer.c:19

#define LIMIT_BEFORE_EXPANSION 0x5ffffffc。

满足这三个条件后函数进入代码4处,使用realloc函数重新分配一块内存,而导致原先的str->data指针被free掉,成为野指针,而程序其他地方继续使用这一指针,就导致了Use After Free。

至此为止,漏洞的原理搞清楚了,那么如何构造畸形的TLS握手包呢?

首先使用wireshark导出正常的TLS握手包,将包头中的两个长度段分别改为0x4000, 0x5560。并在包尾填充相应长度的字符。很简单,是不是?

http://p4.qhimg.com/t016e29a9631d1a68f9.png


漏洞溯源

俗话说,”冤有头,债有主”,那么这次漏洞是如何出生的呢?openssl使用的代码管理工具是git,我们能够在github看到过往的历史提交记录,让我们来查查此次漏洞到底是如何产生的。根据上面的分析,我们知道漏洞是在statem.c文件中。经过一番搜索,最后找到这段代码的修改记录:

https://github.com/openssl/openssl/commit/c1ef7c971d0bbf117c3c80f65b5875e2e7b024b1#diff-03303953dad8b2c06464ec69a7414859

查看页面中的修改说明,openssl给TLS包分配内存的时机太早,如果有恶意攻击者发送大量恶意TLS包,可能导致openssl分配大量内存而导致拒绝服务漏洞。注意看说明结尾,此次漏洞是由360团队的shilei向openssl报告的,openssl开发团队收到这一漏洞报告后对相关文件进行了修改,并最终导致了此次拒绝服务攻击。总结起来就是360的一位安全研究员shi lei向openssl报告了CVE-2016-6307拒绝服务攻击漏洞,openssl对此进行了修改,并导致了CVE-2016-6309漏洞。

通过这一漏洞的分析,以下是笔者一些不成熟的关于软件安全的想法,请大家指正:

1.尽量不要让你的代码太复杂。我认为软件开发人员在修复一个bug的时候引入一个新bug的原因在于软件的复杂度已经超过了开发人员的驾驭能力。对于开发人员而言,太过复杂的代码容易出bug,这是常识。但是因为业务的各种变更,项目进度需要,历史原因等种种实际情况,很容易出现极其复杂的代码,并产生安全漏洞。因此,开发人员如果有多一些时间的话,希望能思考一下,你手头上正在开发或维护的代码,能否在不影响业务的基础上降低复杂度。

2.在给漏洞打补丁的同时,也可能产生新的漏洞。安全研究人员在挖掘漏洞的时候,可以试着从软件的补丁上考虑。

本文由梁瘦叟原创发布

转载,请参考转载声明,注明出处: https://www.anquanke.com/post/id/85158

安全客 - 有思想的安全新媒体

分享到:微信
+11赞
收藏
梁瘦叟
分享到:微信

发表评论

内容需知
  • 投稿须知
  • 转载须知
  • 官网QQ群8:819797106
  • 官网QQ群3:830462644(已满)
  • 官网QQ群2:814450983(已满)
  • 官网QQ群1:702511263(已满)
合作单位
  • 安全客
  • 安全客
Copyright © 北京奇虎科技有限公司 360网络攻防实验室 安全客 All Rights Reserved 京ICP备08010314号-66