【OpenSSL】heartbleed漏洞源码分析

前言

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

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

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

另外当你了解这个漏洞后,你就会更加的理智去面对网上的各类流言,你就会更加的有本身的判断力,如此简单的一个漏洞,为何一直都没曝光?为何如今才曝光?咱们的私密信息到底泄露了多少?web

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

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

heartbleed漏洞介绍

什么是heartbleed漏洞?你们从不一样的渠道可能都已经有所了解了,可是本文仍是须要再简要的描述一下,以帮助你们更好的理解这个漏洞。安全

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

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

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

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

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

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

如下是个人回复:

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

为此,我特意开辟了一个帖子来反省本身的错误,接受你们的批评,坚定下次予以改正。

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

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

相关文章
相关标签/搜索