CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/plugins/aggregator.rb
Views: 11705
1
module Msf
2
Aggregator_yaml = "#{Msf::Config.config_directory}/aggregator.yaml".freeze # location of the aggregator.yml containing saved aggregator creds
3
4
# This plugin provides management and interaction with an external session aggregator.
5
class Plugin::Aggregator < Msf::Plugin
6
class AggregatorCommandDispatcher
7
include Msf::Ui::Console::CommandDispatcher
8
9
@response_queue = []
10
11
def name
12
'Aggregator'
13
end
14
15
def commands
16
{
17
'aggregator_connect' => 'Connect to a running Aggregator instance ( host[:port] )',
18
'aggregator_save' => 'Save connection details to an Aggregator instance',
19
'aggregator_disconnect' => 'Disconnect from an active Aggregator instance',
20
'aggregator_addresses' => 'List all remote ip addresses available for ingress',
21
'aggregator_cables' => 'List all remote listeners for sessions',
22
'aggregator_cable_add' => 'Setup remote https listener for sessions',
23
'aggregator_cable_remove' => 'Stop remote listener for sessions',
24
'aggregator_default_forward' => 'forward a unlisted/unhandled sessions to a specified listener',
25
'aggregator_sessions' => 'List all remote sessions currently available from the Aggregator instance',
26
'aggregator_session_forward' => 'forward a session to a specified listener',
27
'aggregator_session_park' => 'Park an existing session on the Aggregator instance'
28
}
29
end
30
31
def aggregator_verify
32
if !@aggregator
33
print_error("No active Aggregator instance has been configured, please use 'aggregator_connect'")
34
return false
35
end
36
37
true
38
end
39
40
def usage(*lines)
41
print_status('Usage: ')
42
lines.each do |line|
43
print_status(" #{line}")
44
end
45
end
46
47
def usage_save
48
usage('aggregator_save')
49
end
50
51
def usage_connect
52
usage('aggregator_connect host[:port]',
53
' -OR- ',
54
'aggregator_connect host port')
55
end
56
57
def usage_cable_add
58
usage('aggregator_cable_add host:port [certificate]',
59
' -OR- ',
60
'aggregator_cable_add host port [certificate]')
61
end
62
63
def usage_cable_remove
64
usage('aggregator_cable_remove host:port',
65
' -OR- ',
66
'aggregator_cable_remove host port')
67
end
68
69
def usage_session_forward
70
usage('aggregator_session_forward remote_id')
71
end
72
73
def usage_default_forward
74
usage('aggregator_session_forward')
75
end
76
77
def show_session(details, _target, local_id)
78
status = pad_space(" #{local_id}", 4)
79
status += " #{details['ID']}"
80
status = pad_space(status, 15)
81
status += ' meterpreter '
82
status += "#{guess_target_platform(details['OS'])} "
83
status = pad_space(status, 43)
84
status += "#{details['USER']} @ #{details['HOSTNAME']} "
85
status = pad_space(status, 64)
86
status += "#{details['LOCAL_SOCKET']} -> #{details['REMOTE_SOCKET']}"
87
print_status status
88
end
89
90
def show_session_detailed(details, target, local_id)
91
print_status "\t Remote ID: #{details['ID']}"
92
print_status "\t Type: meterpreter #{guess_target_platform(details['OS'])}"
93
print_status "\t Info: #{details['USER']} @ #{details['HOSTNAME']}"
94
print_status "\t Tunnel: #{details['LOCAL_SOCKET']} -> #{details['REMOTE_SOCKET']}"
95
print_status "\t Via: exploit/multi/handler"
96
print_status "\t UUID: #{details['UUID']}"
97
print_status "\t MachineID: #{details['MachineID']}"
98
print_status "\t CheckIn: #{details['LAST_SEEN'].to_i}s ago" unless details['LAST_SEEN'].nil?
99
print_status "\tRegistered: Not Yet Implemented"
100
print_status "\t Forward: #{target}"
101
print_status "\tSession ID: #{local_id}" unless local_id.nil?
102
print_status ''
103
end
104
105
def cmd_aggregator_save(*args)
106
# if we are logged in, save session details to aggregator.yaml
107
if !args.empty? || args[0] == '-h'
108
usage_save
109
return
110
end
111
112
if args[0]
113
usage_save
114
return
115
end
116
117
group = 'default'
118
119
if (@host && !@host.empty?) && (@port && !@port.empty? && @port.to_i > 0)
120
config = { group.to_s => { 'server' => @host, 'port' => @port } }
121
::File.open(Aggregator_yaml.to_s, 'wb') { |f| f.puts YAML.dump(config) }
122
print_good("#{Aggregator_yaml} created.")
123
else
124
print_error('Missing server/port - reconnect and then try again.')
125
return
126
end
127
end
128
129
def cmd_aggregator_connect(*args)
130
if !args[0] && ::File.readable?(Aggregator_yaml.to_s)
131
lconfig = YAML.load_file(Aggregator_yaml.to_s)
132
@host = lconfig['default']['server']
133
@port = lconfig['default']['port']
134
aggregator_login
135
return
136
end
137
138
if args.empty? || args[0].empty? || args[0] == '-h'
139
usage_connect
140
return
141
end
142
143
@host = @port = @sslv = nil
144
145
case args.length
146
when 1
147
@host, @port = args[0].split(':', 2)
148
@port ||= '2447'
149
when 2
150
@host, @port = args
151
else
152
usage_connect
153
return
154
end
155
aggregator_login
156
end
157
158
def cmd_aggregator_sessions(*args)
159
case args.length
160
when 0
161
is_detailed = false
162
when 1
163
unless args[0] == '-v'
164
usage_sessions
165
return
166
end
167
is_detailed = true
168
else
169
usage_sessions
170
return
171
end
172
return unless aggregator_verify
173
174
sessions_list = @aggregator.sessions
175
return if sessions_list.nil?
176
177
session_map = {}
178
179
# get details for each session and print in format of sessions -v
180
sessions_list.each do |session|
181
session_id, target = session
182
details = @aggregator.session_details(session_id)
183
local_id = nil
184
framework.sessions.each_pair do |key, value|
185
next unless value.conn_id == session_id
186
187
local_id = key
188
end
189
# filter session that do not have details as forwarding options (this may change later)
190
next unless details && details['ID']
191
192
session_map[details['ID']] = [details, target, local_id]
193
end
194
195
print_status('Remote sessions')
196
print_status('===============')
197
print_status('')
198
if session_map.empty?
199
print_status('No remote sessions.')
200
else
201
unless is_detailed
202
print_status(' Id Remote Id Type Information Connection')
203
print_status(' -- --------- ---- ----------- ----------')
204
end
205
session_map.keys.sort.each do |key|
206
details, target, local_id = session_map[key]
207
if is_detailed
208
show_session_detailed(details, target, local_id)
209
else
210
show_session(details, target, local_id)
211
end
212
end
213
end
214
end
215
216
def cmd_aggregator_addresses(*_args)
217
return if !aggregator_verify
218
219
address_list = @aggregator.available_addresses
220
return if address_list.nil?
221
222
print_status('Remote addresses found:')
223
address_list.each do |addr|
224
print_status(" #{addr}")
225
end
226
end
227
228
def cmd_aggregator_cable_add(*args)
229
host, port, certificate = nil
230
case args.length
231
when 1
232
host, port = args[0].split(':', 2)
233
when 2
234
host, port = args[0].split(':', 2)
235
if port.nil?
236
port = args[1]
237
else
238
certificate = args[1]
239
end
240
when 3
241
host, port, certificate = args
242
else
243
usage_cable_add
244
return
245
end
246
247
if !aggregator_verify || args.empty? || args[0] == '-h' || \
248
port.nil? || port.to_i <= 0
249
usage_cable_add
250
return
251
end
252
253
certificate = File.new(certificate).read if certificate && File.exist?(certificate)
254
255
@aggregator.add_cable(Metasploit::Aggregator::Cable::HTTPS, host, port, certificate)
256
end
257
258
def cmd_aggregator_cables(*_args)
259
return if !aggregator_verify
260
261
res = @aggregator.cables
262
print_status('Remote Cables:')
263
res.each do |k|
264
print_status(" #{k}")
265
end
266
end
267
268
def cmd_aggregator_cable_remove(*args)
269
case args.length
270
when 1
271
host, port = args[0].split(':', 2)
272
when 2
273
host, port = args
274
end
275
if !aggregator_verify || args.empty? || args[0] == '-h' || host.nil?
276
usage_cable_remove
277
return
278
end
279
@aggregator.remove_cable(host, port)
280
end
281
282
def cmd_aggregator_session_park(*args)
283
return if !aggregator_verify
284
285
case args.length
286
when 1
287
session_id = args[0]
288
s = framework.sessions.get(session_id)
289
if s.nil?
290
print_status("#{session_id} is not a valid session.")
291
elsif @aggregator.sessions.keys.include? s.conn_id
292
@aggregator.release_session(s.conn_id)
293
framework.sessions.deregister(s)
294
else
295
# TODO: determine if we can add a transport and route with the
296
# aggregator. For now, just report action not taken.
297
print_status("#{session_id} does not originate from the aggregator connection.")
298
end
299
else
300
usage('aggregator_session_park session_id')
301
return
302
end
303
end
304
305
def cmd_aggregator_default_forward(*_args)
306
return if !aggregator_verify
307
308
@aggregator.register_default(@aggregator.uuid, nil)
309
end
310
311
def cmd_aggregator_session_forward(*args)
312
return if !aggregator_verify
313
314
remote_id = nil
315
case args.length
316
when 1
317
remote_id = args[0]
318
else
319
usage_session_forward
320
return
321
end
322
# find session with ID matching request
323
@aggregator.sessions.each do |session|
324
session_uri, _target = session
325
details = @aggregator.session_details(session_uri)
326
next unless details['ID'] == remote_id
327
328
return @aggregator.obtain_session(session_uri, @aggregator.uuid)
329
end
330
print_error("#{remote_id} was not found.")
331
end
332
333
def cmd_aggregator_disconnect(*_args)
334
if @aggregator && @aggregator.available?
335
# check if this connection is the default forward
336
@aggregator.register_default(nil, nil) if @aggregator.default == @aggregator.uuid
337
338
# now check for any specifically forwarded sessions
339
local_sessions_by_id = {}
340
framework.sessions.each_pair do |_id, s|
341
local_sessions_by_id[s.conn_id] = s
342
end
343
344
sessions = @aggregator.sessions
345
unless sessions.nil?
346
sessions.each_pair do |session, console|
347
next unless local_sessions_by_id.keys.include?(session)
348
349
if console == @aggregator.uuid
350
# park each session locally addressed
351
cmd_aggregator_session_park(framework.sessions.key(local_sessions_by_id[session]))
352
else
353
# simple disconnect session that were from the default forward
354
framework.sessions.deregister(local_sessions_by_id[session])
355
end
356
end
357
end
358
end
359
@aggregator.stop if @aggregator
360
if @payload_job_ids
361
@payload_job_ids.each do |id|
362
framework.jobs.stop_job(id)
363
end
364
@payload_job_ids = nil
365
end
366
@aggregator = nil
367
end
368
369
def aggregator_login
370
if !((@host && !@host.empty?) && (@port && !@port.empty? && @port.to_i > 0))
371
usage_connect
372
return
373
end
374
375
if (@host != 'localhost') && (@host != '127.0.0.1')
376
print_error('Warning: SSL connections are not verified in this release, it is possible for an attacker')
377
print_error(' with the ability to man-in-the-middle the Aggregator traffic to capture the Aggregator')
378
print_error(' traffic, if you are running this on an untrusted network.')
379
return
380
end
381
382
# Wrap this so a duplicate session does not prevent access
383
begin
384
cmd_aggregator_disconnect
385
rescue ::Interrupt => e
386
raise e
387
rescue ::Exception
388
end
389
390
begin
391
print_status("Connecting to Aggregator instance at #{@host}:#{@port}...")
392
@aggregator = Metasploit::Aggregator::ServerProxy.new(@host, @port)
393
end
394
395
aggregator_compatibility_check
396
397
unless @payload_job_ids
398
@payload_job_ids = []
399
@my_io = local_handler
400
end
401
402
@aggregator.register_response_channel(@my_io)
403
@aggregator
404
end
405
406
def aggregator_compatibility_check
407
false if @aggregator.nil?
408
unless @aggregator.available?
409
print_error("Connection to aggregator @ #{@host}:#{@port} is unavailable.")
410
cmd_aggregator_disconnect
411
end
412
end
413
414
def local_handler
415
# get a random ephemeral port
416
server = TCPServer.new('127.0.0.1', 0)
417
port = server.addr[1]
418
server.close
419
420
multi_handler = framework.exploits.create('multi/handler')
421
422
multi_handler.datastore['LHOST'] = '127.0.0.1'
423
# multi_handler.datastore['PAYLOAD'] = "multi/meterpreter/reverse_https"
424
multi_handler.datastore['PAYLOAD'] = 'multi/meterpreter/reverse_http'
425
multi_handler.datastore['LPORT'] = port.to_s
426
427
# %w(DebugOptions PrependMigrate PrependMigrateProc
428
# InitialAutoRunScript AutoRunScript CAMPAIGN_ID HandlerSSLCert
429
# StagerVerifySSLCert PayloadUUIDTracking PayloadUUIDName
430
# IgnoreUnknownPayloads SessionRetryTotal SessionRetryWait
431
# SessionExpirationTimeout SessionCommunicationTimeout).each do |opt|
432
# multi_handler.datastore[opt] = datastore[opt] if datastore[opt]
433
# end
434
435
multi_handler.datastore['ExitOnSession'] = false
436
multi_handler.datastore['EXITFUNC'] = 'thread'
437
438
multi_handler.exploit_simple(
439
'LocalInput' => nil,
440
'LocalOutput' => nil,
441
'Payload' => multi_handler.datastore['PAYLOAD'],
442
'RunAsJob' => true
443
)
444
@payload_job_ids << multi_handler.job_id
445
# requester = Metasploit::Aggregator::Http::SslRequester.new(multi_handler.datastore['LHOST'], multi_handler.datastore['LPORT'])
446
requester = Metasploit::Aggregator::Http::Requester.new(multi_handler.datastore['LHOST'], multi_handler.datastore['LPORT'])
447
requester
448
end
449
450
# borrowed from Msf::Sessions::Meterpreter for now
451
def guess_target_platform(os)
452
case os
453
when /windows/i
454
Msf::Module::Platform::Windows.realname.downcase
455
when /darwin/i
456
Msf::Module::Platform::OSX.realname.downcase
457
when /mac os ?x/i
458
# this happens with java on OSX (for real!)
459
Msf::Module::Platform::OSX.realname.downcase
460
when /freebsd/i
461
Msf::Module::Platform::FreeBSD.realname.downcase
462
when /openbsd/i, /netbsd/i
463
Msf::Module::Platform::BSD.realname.downcase
464
else
465
Msf::Module::Platform::Linux.realname.downcase
466
end
467
end
468
469
def pad_space(status, length)
470
status << ' ' while status.length < length
471
status
472
end
473
474
private :guess_target_platform
475
private :aggregator_login
476
private :aggregator_compatibility_check
477
private :aggregator_verify
478
private :local_handler
479
private :pad_space
480
private :show_session
481
private :show_session_detailed
482
end
483
484
#
485
# Plugin initialization
486
#
487
488
def initialize(framework, opts)
489
super
490
491
#
492
# Require the metasploit/aggregator gem, but fail nicely if it's not there.
493
#
494
begin
495
require 'metasploit/aggregator'
496
rescue LoadError
497
raise 'WARNING: metasploit/aggregator is not available for now.'
498
end
499
500
add_console_dispatcher(AggregatorCommandDispatcher)
501
print_status('Aggregator interaction has been enabled')
502
end
503
504
def cleanup
505
remove_console_dispatcher('Aggregator')
506
end
507
508
def name
509
'aggregator'
510
end
511
512
def desc
513
'Interacts with the external Session Aggregator'
514
end
515
end
516
end
517
518