【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

若是您對算法或編程感興趣,歡迎掃描下方二維碼並關注公衆號「算法與編程之美」,和您一塊兒探索算法和編程的神祕之處,給您不同的解題分析思路。

相關文章
相關標籤/搜索