Skip to content

Commit 673e21c

Browse files
author
HD Moore
committed
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).
1 parent b34ddbd commit 673e21c

File tree

8 files changed

+87
-70
lines changed

8 files changed

+87
-70
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: 62 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,14 @@ class Client
4242
@@ext_hash = {}
4343

4444
#
45-
# Cached SSL certificate (required to scale)
45+
# Cached SSL context (required to scale)
4646
#
47-
@@ssl_ctx = nil
47+
@@ssl_cert_info = nil
48+
49+
#
50+
# Cached SSL certificate
51+
#
52+
@@ssl_cached_cert = nil
4853

4954
#
5055
# Mutex to synchronize class-wide operations
@@ -116,9 +121,21 @@ def init_meterpreter(sock,opts={})
116121

117122
self.response_timeout = opts[:timeout] || self.class.default_timeout
118123
self.send_keepalives = true
124+
125+
# TODO: Clarify why we don't allow unicode to be set in initial options
119126
# self.encode_unicode = opts.has_key?(:encode_unicode) ? opts[:encode_unicode] : true
120127
self.encode_unicode = false
121128

129+
# The SSL certificate is being passed down as a file path
130+
if opts[:ssl_cert]
131+
if ! File.exists? opts[:ssl_cert]
132+
elog("SSL certificate at #{opts[:ssl_cert]} does not exist and will be ignored")
133+
else
134+
# Load the certificate the same way that SslTcpServer does it
135+
self.ssl_cert = ::File.read(opts[:ssl_cert])
136+
end
137+
end
138+
122139
if opts[:passive_dispatcher]
123140
initialize_passive_dispatcher
124141

@@ -200,68 +217,48 @@ def swap_sock_ssl_to_plain
200217
end
201218

202219
def generate_ssl_context
220+
221+
# Initialize a null context
222+
ctx = nil
223+
224+
# Synchronize to prevent race conditions
203225
@@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
254-
255-
ctx.session_id_context = Rex::Text.rand_text(16)
256-
257-
wlog("Generated SSL certificate for Meterpreter sessions")
258-
259-
@@ssl_ctx = ctx
260-
261-
end # End of if not @ssl_ctx
226+
227+
# If the user specified a certificate and its not the cached one, delete the cached info
228+
if self.ssl_cert && self.ssl_cert != @@ssl_cached_cert
229+
@ssl_ctx = nil
230+
end
231+
232+
# If the user did not specify a certificate and we have cached one, delete the cached info
233+
if ! self.ssl_cert && @@ssl_cached_cert
234+
@@ssl_cert_info = nil
235+
end
236+
237+
unless @@ssl_cert_info
238+
# If no certificate was specified, generate one
239+
unless self.ssl_cert
240+
wlog("Generating SSL certificate for Meterpreter sessions")
241+
@@ssl_cert_info = Rex::Socket::SslTcpServer.ssl_generate_certificate
242+
wlog("Generated SSL certificate for Meterpreter sessions")
243+
# Load the user's specified certificate
244+
else
245+
wlog("Loading custom SSL certificate for Meterpreter sessions")
246+
@@ssl_cert_info = Rex::Socket::SslTcpServer.ssl_parse_pem(self.ssl_cert)
247+
wlog("Loaded custom SSL certificate for Meterpreter sessions")
248+
@@ssl_cached_cert = self.ssl_cert
249+
end
250+
end
251+
252+
# Create a new context for each session
253+
ctx = OpenSSL::SSL::SSLContext.new()
254+
ctx.key = @@ssl_cert_info[0]
255+
ctx.cert = @@ssl_cert_info[1]
256+
ctx.extra_chain_cert = @@ssl_cert_info[2]
257+
ctx.options = 0
258+
ctx.session_id_context = Rex::Text.rand_text(16)
262259
end # End of mutex.synchronize
263260

264-
@@ssl_ctx
261+
ctx
265262
end
266263

267264
##
@@ -453,6 +450,10 @@ def unicode_filter_decode(str)
453450
#
454451
attr_accessor :ssl
455452
#
453+
# Use this SSL Certificate (unified PEM)
454+
#
455+
attr_accessor :ssl_cert
456+
#
456457
# The Session Expiration Timeout
457458
#
458459
attr_accessor :expiration

0 commit comments

Comments
 (0)