分类: 网络与安全
2014-08-08 13:44:34
原文地址:OpenSSL Heartbleed 漏洞的源码分析 作者:frankzfz
最近,网上爆出了OpenSSL的一个重大漏洞,正好工作中也有用到该开源软件,今天看了一下出问题版本的源代码,进参考网上的分析,自己也简单写了一下代码出现的问题,正好学习一下。
OpenSSL在实现TLS(TLS表示传输层安全(Transport Layer Security)的缩写)和DTLS(Datagram -TLS)的心跳处理逻辑时,存在编码缺陷。OpenSSL的心跳处理逻辑没有检测心跳包中的长度字段是否和后续的数据字段相符合,攻击者可以利用这点,构造异常的数据包,来获取心跳数据所在的内存区域的后续数据。这些数据中可能包含了证书私钥、用户名、用户密码、用户邮箱等敏感信息。该漏洞允许攻击者,从内存中读取多达64KB的数据。
该漏洞主要是内存泄露问题,而根本上是因为OpenSSL在处理心跳请求包时,没有对length字段(占2byte,可以标识的数据长度为64KB)和后续的data字段做合规检测。生成心跳响应包时,直接用了length对应的长度,从堆空间申请了内存,既便是真正的请求data数据远远小于length标识的长度。
该分析依据的openssl的版本号为:openssl-1.0.1c存在该漏洞的源文件有两个ssl/d1_both.c和ssl/t1_lib.c。心跳处理逻辑分别是dtls1_process_heartbeat和tls1_process_heartbeat两个函数。
点击(此处)折叠或打开
(1) 首先,根据收到的数据包,确定数据包的类型,这里处理的应该是客户端的Request类型的报文,TLS1_HB_REQUEST
unsigned char *p = &s->s3->rrec.data[0]//这里根据传递的参数获取到报文的类型。
其中SSL定义为typedef struct ssl_st SSL;在struct ssl_st结构体中有一个变量struct ssl3_state_st *s3; /* SSLv3 variables */在ssl3_state_st中有下面的定义SSL3_RECORD rrec; /* each decoded record goes in here */
结构体SSL3_RECORD定义如下:
点击(此处)折叠或打开
(1) 下面的代码主要是获取数据的长度,并把报文的类型赋值给hbtyes
/* Read type and payload length first */
hbtype = *p++;
n2s(p, payload);// 获取报文的长度
pl = p; //pl指向data部分
(2) 下面的代码是对收到的报文,进行类型的判断,如果是REQUEST报文,构造Response报文进行回复
if (hbtype == TLS1_HB_REQUEST)
{
unsigned char *buffer, *bp;
int r;
/* 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);
bp = buffer;
SSL的回复报文一般是客户端发送过来的数据内容和长度。这里SSL依据收到的报文长度进行动态内存的分配,这里的问题是:当客户端发送的Request报文的实际数据部分长度并不等于payload,而是实际数据的长度小于payload值。这里举个极限的例子,假设发送Request报文时payload填充的是65535,因为长度字段占用两个字节,最大可以是65535byte=64kB。这样在分配内存的时,payload的值等于65535,这里没有对数据包的实际长度进行验证,造成分配的内存大小大于客户端发送过来的数据大小。
buffer = OPENSSL_malloc(1 + 2 + payload + padding); 这段内存区域最大为:65535 + 1 + 2 + 16
(3) 下面的代码对SSL的Request报文进行回复,并填充Response报文中的各个字段。
*bp++ = TLS1_HB_RESPONSE;//填充类型
s2n(payload, bp);//这里填充request报文中的长度信息
memcpy(bp, pl, payload);//这里根据request报文中的长度,填充Response中的数据部分。
bp += payload;
/* Random padding */
RAND_pseudo_bytes(bp, padding);
宏s2n与宏n2s干的事情正好相反:s2n读入一个16 bit长的值,然后将它存成双字节值,所以s2n会将与请求的心跳包载荷长度相同的长度值存入变量payload。
在正常的情况下,response报文中的data就是request报文中的data数据,但是在异常情况下,payload的长度远大于实际数据的长度,这样就会发生内存的越界访问,但这种越界访问并不会直接导致程序异常,(因为这里直接memcpy后,服务器端并没有使用copy后的数据,而只是简单的进行了回复报文的填充,如果服务端使用了copy的数据也许就可能发现问题)这里使用了memcpy函数,该函数会直接根据长度把内存中数据复制给另一个变量。这样就给恶意的程序留下了后门,当恶意程序给data的长度变量赋值为65535时,就可以把内存中64KB的内存数据通过Response报文发送给客户端,这样客户端程序就可以获取到一些敏感数据。
很多人都有个疑问这64KB的内存数据是什么,对于该问题应该没有人可以正确回答上来,这块内存可能只是一些无用的垃圾数据,也可能存储着银行卡号和QQ号,密码等。只要写个脚本不停的读取64KB的内存,然后对获取到的数据进行分析,总能发现一些有用的信息,这并不需要花费太多的精力,再者,这种类型的心跳包会不停的发送,获取内存数据也很快。
既然这种漏洞已经出现了,openssl官方给出了补丁,我们也可以看看他们是如何修补该问题的。
点击(此处)折叠或打开
通过修复代码我们可以看到,其实就是添加了下面的两个重要的判断:
if (1 + 2 + payload + 16 > s->s3->rrec.length)
+ return 0; /* silently discard per RFC 6520 sec. 4 */
这里主要对如果发送过来的Request数据包长度为0的话,直接返回。
if (1 + 2 + payload + 16 > s->s3->rrec.length)
+ return 0; /* silently discard per RFC 6520 sec. 4 */
从这个判断条件可以看出,对客户发送过来的数据长度进行了判断,如果数据长度大于length就认为是非法的。
PS:对于该漏洞是否可能造成这么严重的后果,在网上也有不同的声音,下面是其中一个外国的软件工程师进行的分析:
说实话,我对发现了OpenSSL“心脏出血”漏洞的那些人的声明感到吃惊。当我听到他们的声明时,我认为64 KB数据根本不足以推算出像私钥一类的数据。至少在x86上,堆是向高地址增长的,所以我认为对指针pl的读取只能读到新分配的内存区域,例如指针bp指向的区域。存储私钥和其它信息的内存区域的分配早于对指针pl指向的内存区域的分配,所以攻击者是无法读到那些敏感数据的。当然,考虑到现代malloc的各种神奇实现,我的推断并不总是成立的。
当然,你也没办法读取其它进程的数据,所以“重要的商业文档”必须位于当前进程的内存区域中、小于64 KB,并且刚好位于指针pl指向的内存块附近。
参考文献:
http://netsecurity.51cto.com/art/201404/435075.htm