解析OpenSSL程序概念及震驚業(yè)界的“心臟出血”漏洞

OpenSSL的各種概念解析:
公鑰/私鑰/簽名/驗(yàn)證簽名/加密/解密/非對(duì)稱(chēng)加密
我們一般的加密是用一個(gè)密碼加密文件,然后解密也用同樣的密碼.這很好理解,這個(gè)是對(duì)稱(chēng)加密.而有些加密時(shí),加密用的一個(gè)密碼,而解密用另外一組密碼,這個(gè)叫非對(duì)稱(chēng)加密,意思就是加密解密的密碼不一樣.初次接觸的人恐怕無(wú)論如何都理解不了.其實(shí)這是數(shù)學(xué)上的一個(gè)素?cái)?shù)積求因子的原理的應(yīng)用,如果你一定要搞懂,百度有大把大把的資料可以看,其結(jié)果就是用這一組密鑰中的一個(gè)來(lái)加密數(shù)據(jù),可以用另一個(gè)解開(kāi).是的沒(méi)錯(cuò),公鑰和私鑰都可以用來(lái)加密數(shù)據(jù),相反用另一個(gè)解開(kāi),公鑰加密數(shù)據(jù),然后私鑰解密的情況被稱(chēng)為加密解密,私鑰加密數(shù)據(jù),公鑰解密一般被稱(chēng)為簽名和驗(yàn)證簽名.
因?yàn)楣€加密的數(shù)據(jù)只有它相對(duì)應(yīng)的私鑰可以解開(kāi),所以你可以把公鑰給人和人,讓他加密他想要傳送給你的數(shù)據(jù),這個(gè)數(shù)據(jù)只有到了有私鑰的你這里,才可以解開(kāi)成有用的數(shù)據(jù),其他人就是得到了,也看懂內(nèi)容.同理,如果你用你的私鑰對(duì)數(shù)據(jù)進(jìn)行簽名,那這個(gè)數(shù)據(jù)就只有配對(duì)的公鑰可以解開(kāi),有這個(gè)私鑰的只有你,所以如果配對(duì)的公鑰解開(kāi)了數(shù)據(jù),就說(shuō)明這數(shù)據(jù)是你發(fā)的,相反,則不是.這個(gè)被稱(chēng)為簽名.
實(shí)際應(yīng)用中,一般都是和對(duì)方交換公鑰,然后你要發(fā)給對(duì)方的數(shù)據(jù),用他的公鑰加密,他得到后用他的私鑰解密,他要發(fā)給你的數(shù)據(jù),用你的公鑰加密,你得到后用你的私鑰解密,這樣最大程度保證了安全性.
RSA/DSA/SHA/MD5
非對(duì)稱(chēng)加密的算法有很多,比較著名的有RSA/DSA ,不同的是RSA可以用于加/解密,也可以用于簽名驗(yàn)簽,DSA則只能用于簽名.至于SHA則是一種和md5相同的算法,它不是用于加密解密或者簽名的,它被稱(chēng)為摘要算法.就是通過(guò)一種算法,依據(jù)數(shù)據(jù)內(nèi)容生成一種固定長(zhǎng)度的摘要,這串摘要值與原數(shù)據(jù)存在對(duì)應(yīng)關(guān)系,就是原數(shù)據(jù)會(huì)生成這個(gè)摘要,但是,這個(gè)摘要是不能還原成原數(shù)據(jù)的,嗯....,正常情況下是這樣的,這個(gè)算法起的作用就是,如果你把原數(shù)據(jù)修改一點(diǎn)點(diǎn),那么生成的摘要都會(huì)不同,傳輸過(guò)程中把原數(shù)據(jù)給你再給你一個(gè)摘要,你把得到的原數(shù)據(jù)同樣做一次摘要算法,與給你的摘要相比較就可以知道這個(gè)數(shù)據(jù)有沒(méi)有在傳輸過(guò)程中被修改了.
實(shí)際應(yīng)用過(guò)程中,因?yàn)樾枰用艿臄?shù)據(jù)可能會(huì)很大,進(jìn)行加密費(fèi)時(shí)費(fèi)力,所以一般都會(huì)把原數(shù)據(jù)先進(jìn)行摘要,然后對(duì)這個(gè)摘要值進(jìn)行加密,將原數(shù)據(jù)的明文和加密后的摘要值一起傳給你.這樣你解開(kāi)加密后的摘要值,再和你得到的數(shù)據(jù)進(jìn)行的摘要值對(duì)應(yīng)一下就可以知道數(shù)據(jù)有沒(méi)有被修改了,而且,因?yàn)樗借€只有你有,只有你能解密摘要值,所以別人就算把原數(shù)據(jù)做了修改,然后生成一個(gè)假的摘要給你也是不行的,你這邊用密鑰也根本解不開(kāi).
CA/PEM/DER/X509/PKCS
一般的公鑰不會(huì)用明文傳輸給別人的,正常情況下都會(huì)生成一個(gè)文件,這個(gè)文件就是公鑰文件,然后這個(gè)文件可以交給其他人用于加密,但是傳輸過(guò)程中如果有人惡意破壞,將你的公鑰換成了他的公鑰,然后得到公鑰的一方加密數(shù)據(jù),不是他就可以用他自己的密鑰解密看到數(shù)據(jù)了嗎,為了解決這個(gè)問(wèn)題,需要一個(gè)公證方來(lái)做這個(gè)事,任何人都可以找它來(lái)確認(rèn)公鑰是誰(shuí)發(fā)的.這就是CA,CA確認(rèn)公鑰的原理也很簡(jiǎn)單,它將它自己的公鑰發(fā)布給所有人,然后一個(gè)想要發(fā)布自己公鑰的人可以將自己的公鑰和一些身份信息發(fā)給CA,CA用自己的密鑰進(jìn)行加密,這里也可以稱(chēng)為簽名.然后這個(gè)包含了你的公鑰和你的信息的文件就可以稱(chēng)為證書(shū)文件了.這樣一來(lái)所有得到一些公鑰文件的人,通過(guò)CA的公鑰解密了文件,如果正常解密那么機(jī)密后里面的信息一定是真的,因?yàn)榧用芊街豢赡苁荂A,其他人沒(méi)它的密鑰啊.這樣你解開(kāi)公鑰文件,看看里面的信息就知道這個(gè)是不是那個(gè)你需要用來(lái)加密的公鑰了.
實(shí)際應(yīng)用中,一般人都不會(huì)找CA去簽名,因?yàn)槟鞘鞘斟X(qián)的,所以可以自己做一個(gè)自簽名的證書(shū)文件,就是自己生成一對(duì)密鑰,然后再用自己生成的另外一對(duì)密鑰對(duì)這對(duì)密鑰進(jìn)行簽名,這個(gè)只用于真正需要簽名證書(shū)的人,普通的加密解密數(shù)據(jù),直接用公鑰和私鑰來(lái)做就可以了.
密鑰文件的格式用OpenSSL生成的就只有PEM和DER兩種格式,PEM的是將密鑰用base64編碼表示出來(lái)的,直接打開(kāi)你能看到一串的英文字母,DER格式是二進(jìn)制的密鑰文件,直接打開(kāi),你可以看到........你什么也看不懂!.X509是通用的證書(shū)文件格式定義.pkcs的一系列標(biāo)準(zhǔn)是指定的存放密鑰的文件標(biāo)準(zhǔn),你只要知道PEM DER X509 PKCS這幾種格式是可以互相轉(zhuǎn)化的.
心臟出血的OpenSSL
去年,OpenSSL爆出史上最嚴(yán)重的安全漏洞,此漏洞在黑客社區(qū)中被命名為“心臟出血”漏洞。360網(wǎng)站衛(wèi)士安全團(tuán)隊(duì)對(duì)該漏洞分析發(fā)現(xiàn),該漏洞不僅是涉及到https開(kāi)頭的網(wǎng)址,還包含間接使用了OpenSSL代碼的產(chǎn)品和服務(wù),比如,VPN、郵件系統(tǒng)、FTP工具等產(chǎn)品和服務(wù),甚至可能會(huì)涉及到其他一些安全設(shè)施的源代碼。
受影響版本
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等版本。
漏洞描述
OpenSSL在實(shí)現(xiàn)TLS和DTLS的心跳處理邏輯時(shí),存在編碼缺陷。OpenSSL的心跳處理邏輯沒(méi)有檢測(cè)心跳包中的長(zhǎng)度字段是否和后續(xù)的數(shù)據(jù)字段相符合,攻擊者可以利用這點(diǎn),構(gòu)造異常的數(shù)據(jù)包,來(lái)獲取心跳數(shù)據(jù)所在的內(nèi)存區(qū)域的后續(xù)數(shù)據(jù)。這些數(shù)據(jù)中可能包含了證書(shū)私鑰、用戶(hù)名、用戶(hù)密碼、用戶(hù)郵箱等敏感信息。該漏洞允許攻擊者,從內(nèi)存中讀取多達(dá)64KB的數(shù)據(jù)。
前幾日的漏洞分析文章主要聚焦在開(kāi)啟HTTPS的網(wǎng)站上,普通網(wǎng)民可能認(rèn)為只有網(wǎng)站自身業(yè)務(wù)會(huì)受到這個(gè)漏洞的影響。從360網(wǎng)站衛(wèi)士Openssl心血漏洞在線(xiàn)檢測(cè)平臺(tái)(wangzhan.#/heartbleed)的監(jiān)控?cái)?shù)據(jù)得知,心血漏洞的輻射范圍已經(jīng)從開(kāi)啟HTTPS的網(wǎng)站延伸到了VPN系統(tǒng)和郵件系統(tǒng),目前共發(fā)現(xiàn)國(guó)內(nèi)共有251個(gè)VPN系統(tǒng)和725個(gè)郵件系統(tǒng)同樣存在漏洞,其中不乏很多政府網(wǎng)站、重點(diǎn)高校和相關(guān)安全廠(chǎng)商。
為了更好讓大家明白,Openssl心血漏洞到底是哪個(gè)環(huán)節(jié)出了問(wèn)題,我們利用OpenSSL lib庫(kù)編寫(xiě)了一個(gè)不依賴(lài)與任何業(yè)務(wù)的獨(dú)立server程序,來(lái)一步步實(shí)際調(diào)試一遍代碼,以此證明不僅是https的網(wǎng)站有問(wèn)題,只要使用了存在該漏洞的OpenSSL libssl.so庫(kù)的應(yīng)用程序都存在安全漏洞!
測(cè)試環(huán)境
OS:CentOS release 6.4 (Final)
OpenSSL: Version 1.0.1f(沒(méi)有打開(kāi)OPENSSL_NO_HEARTBEATS編譯選項(xiàng))
編寫(xiě)Server程序:監(jiān)聽(tīng)端口9876
漏洞測(cè)試
利用網(wǎng)上python驗(yàn)證腳本(https://gist.github.com/RixTox/10222402)進(jìn)行測(cè)試
構(gòu)造異常heartbeat數(shù)據(jù)包,主要添加異常的length字段值。
測(cè)試一:
藍(lán)色的01表示的是心跳包的類(lèi)型為request方向。對(duì)應(yīng)源代碼中就是#define TLS1_HB_REQUEST 1
紅色的20 00表示的心跳請(qǐng)求包的length字段,占兩個(gè)字節(jié),對(duì)應(yīng)的長(zhǎng)度值為8192。
HeartBeat Response包

藍(lán)色的02表示的是心跳包的類(lèi)型為response方向。
對(duì)應(yīng)源代碼中就是#define TLS1_HB_RESPONSE 2
紅色的20 00表示的心跳響應(yīng)包的length字段,占兩個(gè)字節(jié),對(duì)應(yīng)的長(zhǎng)度值為8192。和request包的length值一樣。
綠色部分就是非法獲取到的越界數(shù)據(jù)(可能包含用戶(hù)名、密碼、郵件、內(nèi)網(wǎng)IP等敏感信息)。
測(cè)試二:
在測(cè)試一的基礎(chǔ)上,修改了request心跳包的length字段的值,從20 00 修改到 30 00
HeartBeat Requst包
30 00兩個(gè)字節(jié)對(duì)應(yīng)的長(zhǎng)度為12288(8192+4096)
HeartBeat Response包
[root@server test]# python ssltest.py 127.0.0.1 -p 9876 > 1</p> <p>Sending heartbeat request…</p> <p>… received message: type = 24, ver = 0302, length = 12307</p> <p>Received heartbeat response:</p> <p>WARNING: server returned more data than it should – server is vulnerable!</p> <p>Received heartbeat response:</p> <p>0000: 02 30 00 D8 03 02 53 43 5B 90 9D 9B 72 0B BC 0C .0….SC[...r...</p> <p>0010: BC 2B 92 A8 48 97 CF BD 39 04 CC 16 0A 85 03 90 .+..H...9.......</p> <p>0020: 9F 77 04 33 D4 DE 00 00 66 C0 14 C0 0A C0 22 C0 .w.3....f.....".</p> <p>0030: 21 00 39 00 38 00 88 00 87 C0 0F C0 05 00 35 00 !.9.8.........5.
兩個(gè)測(cè)試用例中,response的length長(zhǎng)度值總是比request的長(zhǎng)度多出來(lái)了19個(gè)byte,為什么?
因?yàn)?,TLS和DTLS在處理類(lèi)型為T(mén)LS1_HB_REQUEST的心跳請(qǐng)求包邏輯中,在從堆空間上申請(qǐng)內(nèi)存大小時(shí),有4部分決定type+length+request的數(shù)據(jù)長(zhǎng)度+pad,其中type,length,pad字段分為占1byte,2byte,16byte,所以response的數(shù)據(jù)總是比request的多出來(lái)19byte。
源碼分析
概要說(shuō)明
該漏洞主要是內(nèi)存泄露問(wèn)題,而根本上是因?yàn)镺penSSL在處理心跳請(qǐng)求包時(shí),沒(méi)有對(duì)length字段(占2byte,可以標(biāo)識(shí)的數(shù)據(jù)長(zhǎng)度為64KB)和后續(xù)的data字段做合規(guī)檢測(cè)。生成心跳響應(yīng)包時(shí),直接用了length對(duì)應(yīng)的長(zhǎng)度,從堆空間申請(qǐng)了內(nèi)存,既便是真正的請(qǐng)求data數(shù)據(jù)遠(yuǎn)遠(yuǎn)小于length標(biāo)識(shí)的長(zhǎng)度。
相關(guān)解析源碼說(shuō)明
存在該漏洞的源文件有兩個(gè)ssl/d1_both.c和ssl/t1_lib.c。
心跳處理邏輯分別是dtls1_process_heartbeat和tls1_process_heartbeat兩個(gè)函數(shù)。
dtls1_process_heartbeat函數(shù)處理邏輯:
Step1.獲取心跳請(qǐng)求包對(duì)應(yīng)的SSLv3記錄中數(shù)據(jù)指針字段,指向request的請(qǐng)求數(shù)據(jù)部分。
unsigned char *p = &s->s3->rrec.data[0];
record記錄的數(shù)據(jù)格式應(yīng)該包含了三個(gè)字段:type, length, data;分別占1byte,2byte,length的實(shí)際值。
Step2.
/* Read type and payload length first */
hbtype = *p++;
n2s(p, payload);
pl = p;
做了兩件事,獲取了type類(lèi)型以及l(fā)ength字段的值(存放到payload中),然后將pl指向真正的data數(shù)據(jù)。
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;
悲劇開(kāi)始上演了。沒(méi)有判斷請(qǐng)求記錄中的真正數(shù)據(jù)長(zhǎng)度,直接用length字段的值來(lái)申請(qǐng)空間。對(duì)應(yīng)于測(cè)試一中的異常數(shù)據(jù)包的話(huà),buffer申請(qǐng)的內(nèi)存大小就是8211byte。但是實(shí)際應(yīng)該申請(qǐng)的大小僅僅就幾個(gè)字節(jié)。
Step4.
/* Enter response type, length and copy payload */
*bp++ = TLS1_HB_RESPONSE;
s2n(payload, bp);
memcpy(bp, pl, payload);
bp += payload;
悲劇形成了。填充響應(yīng)記錄,第一個(gè)字節(jié)填充類(lèi)型,第二、三個(gè)字節(jié)填充request記錄中l(wèi)ength的值,緊接著,將request的data填充為響應(yīng)的data數(shù)據(jù)。異常情況下,payload對(duì)應(yīng)的長(zhǎng)度遠(yuǎn)遠(yuǎn)大于真正應(yīng)該使用的合法的data數(shù)據(jù)長(zhǎng)度,這樣,就導(dǎo)致了非法越界訪(fǎng)問(wèn)相鄰內(nèi)存空間的數(shù)據(jù)。
tls1_process_heartbeat函數(shù)的處理邏輯和dtls1_process_heartbeat一樣,此處就不再做詳細(xì)分析了。
附:ssl_server.c
編譯方式(請(qǐng)根據(jù)實(shí)際環(huán)境自行修改相關(guān)路徑)
該代碼是文中用于調(diào)試存在漏洞的libssl.so庫(kù)的server端,供對(duì)該漏洞感興趣的安全研究人員、安全愛(ài)好者們自行后續(xù)調(diào)試。希望這段獨(dú)立的代碼能讓大家意識(shí)到這個(gè)漏洞持續(xù)的高等級(jí)威脅:截至目前,心血漏洞僅僅是剛開(kāi)始出血,避免這個(gè)漏洞引起互聯(lián)網(wǎng)業(yè)務(wù)大血崩此刻就要開(kāi)始更多的行動(dòng)了!
- #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;
- }
相關(guān)文章
openssl心臟出血漏洞來(lái)襲 openssl心臟出血漏洞防治方法
openssl心臟出血漏洞來(lái)襲怎么辦?下面就和小編一起來(lái)看看openssl心臟出血漏洞防治方法吧2014-04-09- OpenSSL重大安全漏洞可致用戶(hù)網(wǎng)購(gòu)支付密碼泄露,感興趣的朋友就和小編一起來(lái)看看openssl漏洞詳情吧2014-04-09