Rework meterpreter SSL & pass datastore to handle_connection()

This allows HandlerSSLCert to be used to pass a SSL certificate into the Meterpreter handler. The datastore has to be passed into handle_connection() for this to work, as SSL needs to be initialized on Session.new. This still doesn't pass the datastore into Meterpreter directly, but allows the Session::Meterpreter code to extract and pass down the :ssl_cert option if it was specified. This also fixes SSL certificate caching by expiring the cached cert from the class variables if the configuration has changed. A final change is to create a new SSL SessionID for each connection versus reusing the SSL context, which is incorrect and may lead to problems in the future (if not already).
bug/bundler_fix
HD Moore 2014-11-22 15:35:00 -06:00
parent b34ddbdfff
commit 673e21cfaf
8 changed files with 83 additions and 66 deletions

View File

@ -30,10 +30,12 @@ class Meterpreter < Rex::Post::Meterpreter::Client
include Msf::Session::Scriptable
# Override for server implementations that can't do ssl
# Override for server implementations that can't do SSL
def supports_ssl?
true
end
# Override for server implementations that can't do zlib
def supports_zlib?
true
end
@ -49,11 +51,24 @@ class Meterpreter < Rex::Post::Meterpreter::Client
:ssl => supports_ssl?,
:zlib => supports_zlib?
}
# The caller didn't request to skip ssl, so make sure we support it
if not opts[:skip_ssl]
# the caller didn't request to skip ssl, so make sure we support it
opts.merge!(:skip_ssl => (not supports_ssl?))
end
#
# Parse options passed in via the datastore
#
# Extract the HandlerSSLCert option if specified by the user
if opts[:datastore] and opts[:datastore]['HandlerSSLCert']
opts[:ssl_cert] = opts[:datastore]['HandlerSSLCert']
end
# Don't pass the datastore into the init_meterpreter method
opts.delete(:datastore)
#
# Initialize the meterpreter client
#

View File

@ -15,7 +15,8 @@ module MeterpreterOptions
OptString.new('InitialAutoRunScript', [false, "An initial script to run on session creation (before AutoRunScript)", '']),
OptString.new('AutoRunScript', [false, "A script to run automatically on session creation.", '']),
OptBool.new('AutoSystemInfo', [true, "Automatically capture system information on initialization.", true]),
OptBool.new('EnableUnicodeEncoding', [true, "Automatically encode UTF-8 strings as hexadecimal", true])
OptBool.new('EnableUnicodeEncoding', [true, "Automatically encode UTF-8 strings as hexadecimal", true]),
OptPath.new('HandlerSSLCert', [false, "Path to a SSL certificate in unified PEM format, ignored for HTTP transports"])
], self.class)
end

View File

@ -146,7 +146,7 @@ module BindTcp
# to implement the Stream interface.
conn_threads << framework.threads.spawn("BindTcpHandlerSession", false, client) { |client_copy|
begin
handle_connection(wrap_aes_socket(client_copy))
handle_connection(wrap_aes_socket(client_copy), { datastore: datastore })
rescue
elog("Exception raised from BindTcp.handle_connection: #{$!}")
end

View File

@ -55,7 +55,7 @@ module FindPort
# If this is a multi-stage payload, then we just need to blindly
# transmit the stage and create the session, hoping that it works.
if (self.payload_type != Msf::Payload::Type::Single)
handle_connection(sock)
handle_connection(sock, { datastore: datastore })
# Otherwise, check to see if we found a session. We really need
# to improve this, as we could create a session when the exploit
# really didn't succeed.

View File

@ -163,10 +163,10 @@ module ReverseTcp
begin
if datastore['ReverseListenerThreaded']
self.conn_threads << framework.threads.spawn("ReverseTcpHandlerSession-#{local_port}-#{client.peerhost}", false, client) { | client_copy|
handle_connection(wrap_aes_socket(client_copy))
handle_connection(wrap_aes_socket(client_copy), { datastore: datastore })
}
else
handle_connection(wrap_aes_socket(client))
handle_connection(wrap_aes_socket(client), { datastore: datastore })
end
rescue ::Exception
elog("Exception raised from handle_connection: #{$!.class}: #{$!}\n\n#{$@.join("\n")}")

View File

@ -120,7 +120,7 @@ module ReverseTcpDouble
begin
sock_inp, sock_out = detect_input_output(client_a_copy, client_b_copy)
chan = TcpReverseDoubleSessionChannel.new(framework, sock_inp, sock_out)
handle_connection(chan.lsock)
handle_connection(chan.lsock, { datastore: datastore })
rescue
elog("Exception raised from handle_connection: #{$!}\n\n#{$@.join("\n")}")
end

View File

@ -121,7 +121,7 @@ module ReverseTcpDoubleSSL
begin
sock_inp, sock_out = detect_input_output(client_a_copy, client_b_copy)
chan = TcpReverseDoubleSSLSessionChannel.new(framework, sock_inp, sock_out)
handle_connection(chan.lsock)
handle_connection(chan.lsock, { datastore: datastore })
rescue
elog("Exception raised from handle_connection: #{$!}\n\n#{$@.join("\n")}")
end

View File

@ -42,9 +42,14 @@ class Client
@@ext_hash = {}
#
# Cached SSL certificate (required to scale)
# Cached SSL context (required to scale)
#
@@ssl_ctx = nil
@@ssl_cert_info = nil
#
# Cached SSL certificate
#
@@ssl_cached_cert = nil
#
# Mutex to synchronize class-wide operations
@ -116,9 +121,21 @@ class Client
self.response_timeout = opts[:timeout] || self.class.default_timeout
self.send_keepalives = true
# TODO: Clarify why we don't allow unicode to be set in initial options
# self.encode_unicode = opts.has_key?(:encode_unicode) ? opts[:encode_unicode] : true
self.encode_unicode = false
# The SSL certificate is being passed down as a file path
if opts[:ssl_cert]
if ! File.exists? opts[:ssl_cert]
elog("SSL certificate at #{opts[:ssl_cert]} does not exist and will be ignored")
else
# Load the certificate the same way that SslTcpServer does it
self.ssl_cert = ::File.read(opts[:ssl_cert])
end
end
if opts[:passive_dispatcher]
initialize_passive_dispatcher
@ -200,68 +217,48 @@ class Client
end
def generate_ssl_context
# Initialize a null context
ctx = nil
# Synchronize to prevent race conditions
@@ssl_mutex.synchronize do
if not @@ssl_ctx
# If the user specified a certificate and its not the cached one, delete the cached info
if self.ssl_cert && self.ssl_cert != @@ssl_cached_cert
@ssl_ctx = nil
end
# If the user did not specify a certificate and we have cached one, delete the cached info
if ! self.ssl_cert && @@ssl_cached_cert
@@ssl_cert_info = nil
end
unless @@ssl_cert_info
# If no certificate was specified, generate one
unless self.ssl_cert
wlog("Generating SSL certificate for Meterpreter sessions")
key = OpenSSL::PKey::RSA.new(1024){ }
cert = OpenSSL::X509::Certificate.new
cert.version = 2
cert.serial = rand(0xFFFFFFFF)
# Depending on how the socket was created, getsockname will
# return either a struct sockaddr as a String (the default ruby
# Socket behavior) or an Array (the extend'd Rex::Socket::Tcp
# behavior). Avoid the ambiguity by always picking a random
# hostname. See #7350.
subject_cn = Rex::Text.rand_hostname
subject = OpenSSL::X509::Name.new([
["C","US"],
['ST', Rex::Text.rand_state()],
["L", Rex::Text.rand_text_alpha(rand(20) + 10)],
["O", Rex::Text.rand_text_alpha(rand(20) + 10)],
["CN", subject_cn],
])
issuer = OpenSSL::X509::Name.new([
["C","US"],
['ST', Rex::Text.rand_state()],
["L", Rex::Text.rand_text_alpha(rand(20) + 10)],
["O", Rex::Text.rand_text_alpha(rand(20) + 10)],
["CN", Rex::Text.rand_text_alpha(rand(20) + 10)],
])
cert.subject = subject
cert.issuer = issuer
cert.not_before = Time.now - (3600 * 365) + rand(3600 * 14)
cert.not_after = Time.now + (3600 * 365) + rand(3600 * 14)
cert.public_key = key.public_key
ef = OpenSSL::X509::ExtensionFactory.new(nil,cert)
cert.extensions = [
ef.create_extension("basicConstraints","CA:FALSE"),
ef.create_extension("subjectKeyIdentifier","hash"),
ef.create_extension("extendedKeyUsage","serverAuth"),
ef.create_extension("keyUsage","keyEncipherment,dataEncipherment,digitalSignature")
]
ef.issuer_certificate = cert
cert.add_extension ef.create_extension("authorityKeyIdentifier", "keyid:always,issuer:always")
cert.sign(key, OpenSSL::Digest::SHA1.new)
ctx = OpenSSL::SSL::SSLContext.new
ctx.key = key
ctx.cert = cert
ctx.session_id_context = Rex::Text.rand_text(16)
@@ssl_cert_info = Rex::Socket::SslTcpServer.ssl_generate_certificate
wlog("Generated SSL certificate for Meterpreter sessions")
# Load the user's specified certificate
else
wlog("Loading custom SSL certificate for Meterpreter sessions")
@@ssl_cert_info = Rex::Socket::SslTcpServer.ssl_parse_pem(self.ssl_cert)
wlog("Loaded custom SSL certificate for Meterpreter sessions")
@@ssl_cached_cert = self.ssl_cert
end
end
@@ssl_ctx = ctx
end # End of if not @ssl_ctx
# Create a new context for each session
ctx = OpenSSL::SSL::SSLContext.new()
ctx.key = @@ssl_cert_info[0]
ctx.cert = @@ssl_cert_info[1]
ctx.extra_chain_cert = @@ssl_cert_info[2]
ctx.options = 0
ctx.session_id_context = Rex::Text.rand_text(16)
end # End of mutex.synchronize
@@ssl_ctx
ctx
end
##
@ -453,6 +450,10 @@ class Client
#
attr_accessor :ssl
#
# Use this SSL Certificate (unified PEM)
#
attr_accessor :ssl_cert
#
# The Session Expiration Timeout
#
attr_accessor :expiration