【OpenSSL】heartbleed漏洞源码分析

荒凉一梦 提交于 2019-12-04 07:02:53

前言

最近IT界最火的事情莫过于openssl的heartbleed漏洞,关于该漏洞带来的可怕后果本文就不再赘述了。作为一名程序员,如果我们仅仅只是看看或是抱着无所谓的态度去了解一下这个漏洞,那么我们实在太枉为程序员了。

发现一个漏洞,大家第一时间想到的可能就是如何快速的修复这个漏洞,但当我们了解这个漏洞是怎么回事的时候,一切都变得不是那么可怕,正如openssl的heartbleed漏洞。

通过本文您可以了解最近十分火爆的openssl的heartbleed漏洞到底是怎么回事,从根本上去了解这个漏洞,当您了解了这个漏洞的本质之后,您就会选择更淡然的方式去修复而不至于慌乱。或者说您已经修复了该漏洞,但是很好奇这到底是一个什么样的漏洞,为什么漏洞的名字会取得这么严重,“心脏出血”这是一个多么可怕的情形?

另外当你了解这个漏洞后,你就会更加的理智去面对网上的各种流言,你就会更加的有自己的判断力,如此简单的一个漏洞,为什么一直都没曝光?为什么现在才曝光?我们的私密信息到底泄露了多少?

网上可能针对该漏洞有很多很多非常详尽的分析,但为什么还要写本文?本文旨在用最简洁易懂的语言,描述清楚该漏洞是什么?看完前面的示例,再看后面具体的源码的时候,就会比较易懂。

技术博客不在于技术有多么新和多么深奥,关键在于如何以简单易懂的方式让更多的人了解这是怎么回事,正如openssl的heartbleed漏洞一样,虽然网上已经有了很多分析的非常不错的文章,而我更愿以一种简单的方式去让更多的人了解这个漏洞的本质。

heartbleed漏洞介绍

什么是heartbleed漏洞?大家从不同的渠道可能都已经有所了解了,但是本文还是需要再简要的描述一下,以帮助大家更好的理解这个漏洞。

当使用基于openssl通信的双方建立安全连接后,客户端需要不断的发送心跳信息到服务器,以确保服务器是可用的。

基本的流程是:客户端发送一段固定长度的字符串到服务器,服务器接收后,返回该固定长度的字符串。比如客户端发送"hello,world"字符串到服务器,服务器接受后,原样返回"hello,world"字符串,这样客户端就会认为openssl服务器是可用的。

我们假设客户端发送的心跳信息结构体定义为:

struct hb {
      int type;
      int length;
      unsigned char *data;                                                    
};

 

其中type为心跳的类型,length为data的大小,其中关于data字段的内容结构为:

type字段占一个字节,payload字段占两个字节,其余的为payload的具体内容,详情如下所示:

字节序号        备注

0                 type

1-2              data中具体的内容的大小为payload

3-len            具体的内容pl      

当服务器收到消息后,会对该消息进行解析,也就是对data中的字符串进行解析,通过解析第0位得到type,第1-2位得到payload,接着申请(1+2+payload)大小的内存,然后再将相应的数据拷贝到该新申请的内存中。

以下举个简单的示例来说明该问题,假如客户端发送的data数据为"006abcdef",那么服务器端解析可以得到type=0, payload=06, pl='abcdef',申请(1+2+6=9)大小的内存,然后再将type, payload, pl写到新申请的内存中。

如果大家都是老实人,那么上述流程不会出现任何问题。可是世界上总是存在着那么多不“安分”的人,他们会非常的不诚实,比如客户端发送的字符串“abcdef”明明只有6个,而我非得把payload设置为500,如果服务器傻不拉几的不做任何边界检查,直接申请(1+2+500)大小内存,而且更过分的是还把"abcdef********"所有的内容拷贝到新申请的内存处,并发回给客户端。

这样那些不安分的人就获得了服务器上很多非常敏感的信息,这些信息可能包括银行帐号信息,电子交易信息等等诸多安全信息。

至此,您可能对heartbleed漏洞有了一些初步的了解了,接下来我们看看它的源码长什么样子的。

heartbleed漏洞源码分析

上面我们已经简单的描述了heartbleed漏洞,您应该已经有了一个初步的了解了,接下来我们具体的来看一看openssl的heartbleed漏洞是什么样的。

我们直接来看一下他们最近修复提交的代码和之前代码的区别在什么地方就可以了。

--- a/ssl/d1_both.c
+++ b/ssl/d1_both.c
@@ -1459,26 +1459,36 @@ dtls1_process_heartbeat(SSL *s)
        unsigned int payload;
        unsigned int padding = 16; /* Use minimum padding */
 
-       /* Read type and payload length first */
-       hbtype = *p++;
-       n2s(p, payload);
-       pl = p;
-
        if (s->msg_callback)
                s->msg_callback(0, s->version, TLS1_RT_HEARTBEAT,
                        &s->s3->rrec.data[0], s->s3->rrec.length,
                        s, s->msg_callback_arg);
 
+       /* Read type and payload length first */
+       if (1 + 2 + 16 > s->s3->rrec.length)
+               return 0; /* silently discard */
+       hbtype = *p++;
+       n2s(p, payload);
+       if (1 + 2 + payload + 16 > s->s3->rrec.length)
+               return 0; /* silently discard per RFC 6520 sec. 4 */
+       pl = p;
+
        if (hbtype == TLS1_HB_REQUEST)
                {
                unsigned char *buffer, *bp;
+               unsigned int write_length = 1 /* heartbeat type */ +
+                                           2 /* heartbeat length */ +
+                                           payload + padding;
                int r;
 
+               if (write_length > SSL3_RT_MAX_PLAIN_LENGTH)
+                       return 0;
+
                /* Allocate memory for the response, size is 1 byte
                 * message type, plus 2 bytes payload length, plus
                 * payload, plus padding
                 */
-               buffer = OPENSSL_malloc(1 + 2 + payload + padding);
+               buffer = OPENSSL_malloc(write_length);
                bp = buffer;
 
                /* Enter response type, length and copy payload */
@@ -1489,11 +1499,11 @@ dtls1_process_heartbeat(SSL *s)
                /* Random padding */
                RAND_pseudo_bytes(bp, padding);
 
-               r = dtls1_write_bytes(s, TLS1_RT_HEARTBEAT, buffer, 3 + payload + padding);
+               r = dtls1_write_bytes(s, TLS1_RT_HEARTBEAT, buffer, write_length);
 
                if (r >= 0 && s->msg_callback)
                        s->msg_callback(1, s->version, TLS1_RT_HEARTBEAT,
-                               buffer, 3 + payload + padding,
+                               buffer, write_length,
                                s, s->msg_callback_arg);
 
                OPENSSL_free(buffer);

 

从上面的差异我们可以看到,服务器处理心跳原来的方式是首先直接解析type和payload,什么都不做任何的检查。

        unsigned int payload;
        unsigned int padding = 16; /* Use minimum padding */
 
-       /* Read type and payload length first */
-       hbtype = *p++;
-       n2s(p, payload);
-       pl = p;
-

 

接下来我们再看看修复之后他们是如何处理的:

我们之前介绍的示例代码和openssl的代码的data数据格式有一点差别就是openssl的data进行了16字节的数据对齐,其他格式一致。示例代码是为了让大家更好的理解原理,所以很多细节的东西就没有添加,避免由于复杂度过高而不易理解。

接下来我们来看一下openssl添加的两个最重要的判断条件:

  if (1 + 2 + 16 > s->s3->rrec.length)
              return 0; /* silently discard */

 

这个判断的目的是为了避免data的length为0这一特殊情况的处理;

if (1 + 2 + payload + 16 > s->s3->rrec.length)
              return 0; /* silently discard per RFC 6520 sec. 4 */

 

从这个判断条件我们可以看出,对payload的大小做了检查,如果超出了length就表示你可能是恶意攻击,直接返回0。

结论

从openssl的heartbleed漏洞我们可以看出,尽管已经被大家广泛使用的openssl技术,且应用于很多金融领域,但是这里面依然存在着很多致命的漏洞。从上面的分析,您或许可以得出为什么这次的漏洞会叫heartbleed漏洞了,确实太heart bleed了。

如果大家对openssl感兴趣的话,后续博文将继续深入分析其具体实现。

很多人在看完本文,了解漏洞的相关原理后,都很蠢蠢欲动,希望有攻击示例代码,这说明通过本文大家都已了解漏洞原理,也从侧面反应我写的还是比较简明易懂的。如果大家对攻击感兴趣的话,可直接在评论中加以回复,后续考虑创作相关博文。

引用

【1】http://blog.existentialize.com/diagnosis-of-the-openssl-heartbleed-bug.html

【2】http://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=96db9023b881d7cd9f379b0c154650d6c108e9a3

【3】http://jandan.net/2014/04/12/openssl-heartbleed.html

 

后记

在发表本文一天后,我将该链接发到知乎社区了,结果被强烈吐槽了。

http://www.zhihu.com/question/23353569/answer/24484500?group_id=239019798#comment-54509990

这篇文章真的不是标题党? 1、首先吐槽哪里简单易懂了?根本还是只有程序员能看 2、看完整篇文章后,哪里可以变得不用再恐慌?说得一点都没错,就是这么简单的漏洞,就是这么低级的未对数据做校验,但是这个低级的漏洞可以把所有内存信息包含但不限于用户账号密码证书私钥给刷出来。这篇文章“破”什么“立”什么?就敢说“一切都变得不是那么可怕”?作者原来都在看写什么信息?正确的信息不就是这样的么?不就是这些事实足够可怖么? 3、作者知道openssl技术到底被使用有多广泛么?他知道只有1%么?他知道淘宝不属于金融服务么?知道银行证券保险这三大金融行业不受此漏洞影响么?

不过说实话,真的非常感谢这位朋友,有时候文章的措辞还是需要多推敲推敲,而且有些东西只适合在程序员社区发表。

以下是我的回复:

非常感谢您的回复,这些建议对于我今后写博客具有非常重大的指导意义。需要指出的一点是,本文不是标题党,写作该博文的目的就是为了让更多的人了解这个漏洞。不过的确可能某些方面的表达确实值得斟酌,今后我会特别注意并完善的。最后再次感谢您的中肯回复。

为此,我特地开辟了一个帖子来检讨自己的错误,接受大家的批评,坚决下次予以改正。

帖子地址:http://www.oschina.net/question/271937_151118

如果您对算法或编程感兴趣,欢迎扫描下方二维码并关注公众号“算法与编程之美”,和您一起探索算法和编程的神秘之处,给您不一样的解题分析思路。

 

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!