Skip to content

Commit c681654

Browse files
committed
Land rapid7#4252 - Rework meterpreter SSL & pass datastore to handle_connection()
2 parents 65b9aa1 + 335d1ef commit c681654

File tree

8 files changed

+75
-69
lines changed

8 files changed

+75
-69
lines changed

lib/msf/base/sessions/meterpreter.rb

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,12 @@ class Meterpreter < Rex::Post::Meterpreter::Client
3030

3131
include Msf::Session::Scriptable
3232

33-
# Override for server implementations that can't do ssl
33+
# Override for server implementations that can't do SSL
3434
def supports_ssl?
3535
true
3636
end
37+
38+
# Override for server implementations that can't do zlib
3739
def supports_zlib?
3840
true
3941
end
@@ -49,11 +51,24 @@ def initialize(rstream, opts={})
4951
:ssl => supports_ssl?,
5052
:zlib => supports_zlib?
5153
}
54+
55+
# The caller didn't request to skip ssl, so make sure we support it
5256
if not opts[:skip_ssl]
53-
# the caller didn't request to skip ssl, so make sure we support it
5457
opts.merge!(:skip_ssl => (not supports_ssl?))
5558
end
5659

60+
#
61+
# Parse options passed in via the datastore
62+
#
63+
64+
# Extract the HandlerSSLCert option if specified by the user
65+
if opts[:datastore] and opts[:datastore]['HandlerSSLCert']
66+
opts[:ssl_cert] = opts[:datastore]['HandlerSSLCert']
67+
end
68+
69+
# Don't pass the datastore into the init_meterpreter method
70+
opts.delete(:datastore)
71+
5772
#
5873
# Initialize the meterpreter client
5974
#

lib/msf/base/sessions/meterpreter_options.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ def initialize(info = {})
1515
OptString.new('InitialAutoRunScript', [false, "An initial script to run on session creation (before AutoRunScript)", '']),
1616
OptString.new('AutoRunScript', [false, "A script to run automatically on session creation.", '']),
1717
OptBool.new('AutoSystemInfo', [true, "Automatically capture system information on initialization.", true]),
18-
OptBool.new('EnableUnicodeEncoding', [true, "Automatically encode UTF-8 strings as hexadecimal", true])
18+
OptBool.new('EnableUnicodeEncoding', [true, "Automatically encode UTF-8 strings as hexadecimal", true]),
19+
OptPath.new('HandlerSSLCert', [false, "Path to a SSL certificate in unified PEM format, ignored for HTTP transports"])
1920
], self.class)
2021
end
2122

lib/msf/core/handler/bind_tcp.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ def start_handler
146146
# to implement the Stream interface.
147147
conn_threads << framework.threads.spawn("BindTcpHandlerSession", false, client) { |client_copy|
148148
begin
149-
handle_connection(wrap_aes_socket(client_copy))
149+
handle_connection(wrap_aes_socket(client_copy), { datastore: datastore })
150150
rescue
151151
elog("Exception raised from BindTcp.handle_connection: #{$!}")
152152
end

lib/msf/core/handler/find_port.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ def handler(sock)
5555
# If this is a multi-stage payload, then we just need to blindly
5656
# transmit the stage and create the session, hoping that it works.
5757
if (self.payload_type != Msf::Payload::Type::Single)
58-
handle_connection(sock)
58+
handle_connection(sock, { datastore: datastore })
5959
# Otherwise, check to see if we found a session. We really need
6060
# to improve this, as we could create a session when the exploit
6161
# really didn't succeed.

lib/msf/core/handler/reverse_tcp.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,10 +163,10 @@ def start_handler
163163
begin
164164
if datastore['ReverseListenerThreaded']
165165
self.conn_threads << framework.threads.spawn("ReverseTcpHandlerSession-#{local_port}-#{client.peerhost}", false, client) { | client_copy|
166-
handle_connection(wrap_aes_socket(client_copy))
166+
handle_connection(wrap_aes_socket(client_copy), { datastore: datastore })
167167
}
168168
else
169-
handle_connection(wrap_aes_socket(client))
169+
handle_connection(wrap_aes_socket(client), { datastore: datastore })
170170
end
171171
rescue ::Exception
172172
elog("Exception raised from handle_connection: #{$!.class}: #{$!}\n\n#{$@.join("\n")}")

lib/msf/core/handler/reverse_tcp_double.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ def start_handler
120120
begin
121121
sock_inp, sock_out = detect_input_output(client_a_copy, client_b_copy)
122122
chan = TcpReverseDoubleSessionChannel.new(framework, sock_inp, sock_out)
123-
handle_connection(chan.lsock)
123+
handle_connection(chan.lsock, { datastore: datastore })
124124
rescue
125125
elog("Exception raised from handle_connection: #{$!}\n\n#{$@.join("\n")}")
126126
end

lib/msf/core/handler/reverse_tcp_double_ssl.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ def start_handler
121121
begin
122122
sock_inp, sock_out = detect_input_output(client_a_copy, client_b_copy)
123123
chan = TcpReverseDoubleSSLSessionChannel.new(framework, sock_inp, sock_out)
124-
handle_connection(chan.lsock)
124+
handle_connection(chan.lsock, { datastore: datastore })
125125
rescue
126126
elog("Exception raised from handle_connection: #{$!}\n\n#{$@.join("\n")}")
127127
end

lib/rex/post/meterpreter/client.rb

Lines changed: 50 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,9 @@ class Client
4242
@@ext_hash = {}
4343

4444
#
45-
# Cached SSL certificate (required to scale)
45+
# Cached auto-generated SSL certificate
4646
#
47-
@@ssl_ctx = nil
47+
@@ssl_cached_cert = nil
4848

4949
#
5050
# Mutex to synchronize class-wide operations
@@ -106,7 +106,6 @@ def init_meterpreter(sock,opts={})
106106
self.capabilities = opts[:capabilities] || {}
107107
self.commands = []
108108

109-
110109
self.conn_id = opts[:conn_id]
111110
self.url = opts[:url]
112111
self.ssl = opts[:ssl]
@@ -116,9 +115,21 @@ def init_meterpreter(sock,opts={})
116115

117116
self.response_timeout = opts[:timeout] || self.class.default_timeout
118117
self.send_keepalives = true
118+
119+
# TODO: Clarify why we don't allow unicode to be set in initial options
119120
# self.encode_unicode = opts.has_key?(:encode_unicode) ? opts[:encode_unicode] : true
120121
self.encode_unicode = false
121122

123+
# The SSL certificate is being passed down as a file path
124+
if opts[:ssl_cert]
125+
if ! ::File.exists? opts[:ssl_cert]
126+
elog("SSL certificate at #{opts[:ssl_cert]} does not exist and will be ignored")
127+
else
128+
# Load the certificate the same way that SslTcpServer does it
129+
self.ssl_cert = ::File.read(opts[:ssl_cert])
130+
end
131+
end
132+
122133
if opts[:passive_dispatcher]
123134
initialize_passive_dispatcher
124135

@@ -200,68 +211,43 @@ def swap_sock_ssl_to_plain
200211
end
201212

202213
def generate_ssl_context
203-
@@ssl_mutex.synchronize do
204-
if not @@ssl_ctx
205-
206-
wlog("Generating SSL certificate for Meterpreter sessions")
207-
208-
key = OpenSSL::PKey::RSA.new(1024){ }
209-
cert = OpenSSL::X509::Certificate.new
210-
cert.version = 2
211-
cert.serial = rand(0xFFFFFFFF)
212-
213-
# Depending on how the socket was created, getsockname will
214-
# return either a struct sockaddr as a String (the default ruby
215-
# Socket behavior) or an Array (the extend'd Rex::Socket::Tcp
216-
# behavior). Avoid the ambiguity by always picking a random
217-
# hostname. See #7350.
218-
subject_cn = Rex::Text.rand_hostname
219-
220-
subject = OpenSSL::X509::Name.new([
221-
["C","US"],
222-
['ST', Rex::Text.rand_state()],
223-
["L", Rex::Text.rand_text_alpha(rand(20) + 10)],
224-
["O", Rex::Text.rand_text_alpha(rand(20) + 10)],
225-
["CN", subject_cn],
226-
])
227-
issuer = OpenSSL::X509::Name.new([
228-
["C","US"],
229-
['ST', Rex::Text.rand_state()],
230-
["L", Rex::Text.rand_text_alpha(rand(20) + 10)],
231-
["O", Rex::Text.rand_text_alpha(rand(20) + 10)],
232-
["CN", Rex::Text.rand_text_alpha(rand(20) + 10)],
233-
])
234-
235-
cert.subject = subject
236-
cert.issuer = issuer
237-
cert.not_before = Time.now - (3600 * 365) + rand(3600 * 14)
238-
cert.not_after = Time.now + (3600 * 365) + rand(3600 * 14)
239-
cert.public_key = key.public_key
240-
ef = OpenSSL::X509::ExtensionFactory.new(nil,cert)
241-
cert.extensions = [
242-
ef.create_extension("basicConstraints","CA:FALSE"),
243-
ef.create_extension("subjectKeyIdentifier","hash"),
244-
ef.create_extension("extendedKeyUsage","serverAuth"),
245-
ef.create_extension("keyUsage","keyEncipherment,dataEncipherment,digitalSignature")
246-
]
247-
ef.issuer_certificate = cert
248-
cert.add_extension ef.create_extension("authorityKeyIdentifier", "keyid:always,issuer:always")
249-
cert.sign(key, OpenSSL::Digest::SHA1.new)
250-
251-
ctx = OpenSSL::SSL::SSLContext.new
252-
ctx.key = key
253-
ctx.cert = cert
254214

255-
ctx.session_id_context = Rex::Text.rand_text(16)
215+
ctx = nil
216+
ssl_cert_info = nil
256217

257-
wlog("Generated SSL certificate for Meterpreter sessions")
218+
loop do
258219

259-
@@ssl_ctx = ctx
220+
# Load a custom SSL certificate if one has been specified
221+
if self.ssl_cert
222+
wlog("Loading custom SSL certificate for Meterpreter session")
223+
ssl_cert_info = Rex::Socket::SslTcpServer.ssl_parse_pem(self.ssl_cert)
224+
wlog("Loaded custom SSL certificate for Meterpreter session")
225+
break
226+
end
260227

261-
end # End of if not @ssl_ctx
262-
end # End of mutex.synchronize
228+
# Generate a certificate if necessary and cache it
229+
if ! @@ssl_cached_cert
230+
@@ssl_mutex.synchronize do
231+
wlog("Generating SSL certificate for Meterpreter sessions")
232+
@@ssl_cached_cert = Rex::Socket::SslTcpServer.ssl_generate_certificate
233+
wlog("Generated SSL certificate for Meterpreter sessions")
234+
end
235+
end
236+
237+
# Use the cached certificate
238+
ssl_cert_info = @@ssl_cached_cert
239+
break
240+
end
263241

264-
@@ssl_ctx
242+
# Create a new context for each session
243+
ctx = OpenSSL::SSL::SSLContext.new()
244+
ctx.key = ssl_cert_info[0]
245+
ctx.cert = ssl_cert_info[1]
246+
ctx.extra_chain_cert = ssl_cert_info[2]
247+
ctx.options = 0
248+
ctx.session_id_context = Rex::Text.rand_text(16)
249+
250+
ctx
265251
end
266252

267253
##
@@ -453,6 +439,10 @@ def unicode_filter_decode(str)
453439
#
454440
attr_accessor :ssl
455441
#
442+
# Use this SSL Certificate (unified PEM)
443+
#
444+
attr_accessor :ssl_cert
445+
#
456446
# The Session Expiration Timeout
457447
#
458448
attr_accessor :expiration

0 commit comments

Comments
 (0)