【OpenSSL】Heartbleed漏洞源码分析


OpenSSL?Heartbleed漏洞?还在为此而恐慌?淡定,淡定!当您了解了漏洞的原理之后,您就不会再为之此而恐惧,或者说您看完之后就会更加理智的去对待这个漏洞。 本文首先介绍了什么是Heartbleed漏洞,接着从openssl源码的角度分析了该漏洞,最后总结我们从中可以吸取什么经验教训。

OpenSSL TLS心跳读远程信息泄露漏洞 (CVE-2014-0160)

OpenSSL严重bug允许攻击者读取64k内存,Debian半小时修复

OpenSSL “heartbleed” 的安全漏洞

通过OpenSSL提供FTP+SSL/TLS认证功能,并实现安全数据传输

前言

最近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感兴趣的话,后续博文将继续深入分析其具体实现。

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

Heartbleed 的详细介绍:请点这里
Heartbleed 的下载地址:请点这里

相关内容