Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/post/multi/manage/autoroute.rb
19851 views
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
class MetasploitModule < Msf::Post
7
8
def initialize(info = {})
9
super(
10
update_info(
11
info,
12
'Name' => 'Multi Manage Network Route via Meterpreter Session',
13
'Description' => %q{
14
This module manages session routing via an existing
15
Meterpreter session. It enables other modules to 'pivot' through a
16
compromised host when connecting to the named NETWORK and SUBMASK.
17
Autoadd will search a session for valid subnets from the routing table
18
and interface list then add routes to them. Default will add a default
19
route so that all TCP/IP traffic not specified in the MSF routing table
20
will be routed through the session when pivoting. See documentation for more
21
'info -d' and click 'Knowledge Base'
22
},
23
'License' => MSF_LICENSE,
24
'Author' => [
25
'todb',
26
'Josh Hale "sn0wfa11" <jhale85446[at]gmail.com>'
27
],
28
'SessionTypes' => [ 'meterpreter'],
29
'Compat' => {
30
'Meterpreter' => {
31
'Commands' => %w[
32
stdapi_net_config_get_interfaces
33
stdapi_net_config_get_routes
34
]
35
}
36
},
37
'Notes' => {
38
'Stability' => [CRASH_SAFE],
39
'SideEffects' => [],
40
'Reliability' => []
41
}
42
)
43
)
44
45
register_options(
46
[
47
OptString.new('SUBNET', [false, 'Subnet (IPv4, for example, 10.10.10.0)', nil]),
48
OptString.new('NETMASK', [false, 'Netmask (IPv4 as "255.255.255.0" or CIDR as "/24"', '255.255.255.0']),
49
OptEnum.new('CMD', [true, 'Specify the autoroute command', 'autoadd', ['add', 'autoadd', 'print', 'delete', 'default']])
50
]
51
)
52
end
53
54
# Get the CMD string vs ACTION
55
#
56
# Backwards compatability: This was changed because the option name of "ACTION"
57
# is special for some things, and indicates the :action attribute, not a datastore option.
58
# However, this is a semi-popular module, though, so I'd prefer not to break people's
59
# RC scripts that set ACTION. Note that ACTION is preferred over CMD.
60
#
61
# TODO: The better solution is to use 'Action' and 'DefaultAction' info elements,
62
# but there are some squirelly problems right now with rendering these for post modules.
63
#
64
# @return [string class] cmd string
65
def route_cmd
66
if datastore['ACTION'].to_s.empty?
67
datastore['CMD'].to_s.downcase.to_sym
68
else
69
wlog("Warning, deprecated use of 'ACTION' datastore option for #{fullname}'. Use 'CMD' instead.")
70
datastore['ACTION'].to_s.downcase.to_sym
71
end
72
end
73
74
# Run Method for when run command is issued
75
#
76
# @return [void] A useful return value is not expected here
77
def run
78
return unless session_good?
79
80
hostname = sysinfo.nil? ? cmd_exec('hostname') : sysinfo['Computer']
81
print_status("Running module against #{hostname} (#{session.session_host})")
82
83
subnet = datastore['SUBNET']
84
85
case route_cmd
86
when :print
87
print_routes
88
when :add
89
if validate_cmd(subnet, netmask)
90
print_status("Adding a route to #{subnet}/#{netmask}...")
91
add_route(subnet, netmask)
92
end
93
when :autoadd
94
autoadd_routes
95
when :default
96
add_default
97
when :delete
98
if subnet
99
print_status("Deleting route to #{subnet}/#{netmask}...")
100
delete_route(subnet, netmask)
101
else
102
delete_all_routes
103
end
104
end
105
end
106
107
# Delete all routes from framework routing table.
108
#
109
# @return [void] A useful return value is not expected here
110
def delete_all_routes
111
if Rex::Socket::SwitchBoard.routes.empty?
112
print_status('No routes associated with this session to delete.')
113
return
114
end
115
116
print_status("Deleting all routes associated with session: #{session.sid}.")
117
118
loop do
119
count = 0
120
Rex::Socket::SwitchBoard.each do |route|
121
next unless route.comm == session
122
123
print_status("Deleting: #{route.subnet}/#{route.netmask}")
124
delete_route(route.subnet, route.netmask)
125
count += 1
126
end
127
128
break if count == 0
129
end
130
131
print_status('Deleted all routes')
132
end
133
134
# Print all of the active routes defined on the framework
135
#
136
# Identical functionality to command_dispatcher/core.rb, and
137
# nearly identical code
138
#
139
# @return [void] A useful return value is not expected here
140
def print_routes
141
# IPv4 Table
142
tbl_ipv4 = Msf::Ui::Console::Table.new(
143
Msf::Ui::Console::Table::Style::Default,
144
'Header' => 'IPv4 Active Routing Table',
145
'Prefix' => "\n",
146
'Postfix' => "\n",
147
'Columns' =>
148
[
149
'Subnet',
150
'Netmask',
151
'Gateway',
152
],
153
'ColProps' =>
154
{
155
'Subnet' => { 'Width' => 17 },
156
'Netmask' => { 'Width' => 17 }
157
}
158
)
159
160
# IPv6 Table
161
tbl_ipv6 = Msf::Ui::Console::Table.new(
162
Msf::Ui::Console::Table::Style::Default,
163
'Header' => 'IPv6 Active Routing Table',
164
'Prefix' => "\n",
165
'Postfix' => "\n",
166
'Columns' =>
167
[
168
'Subnet',
169
'Netmask',
170
'Gateway',
171
],
172
'ColProps' =>
173
{
174
'Subnet' => { 'Width' => 17 },
175
'Netmask' => { 'Width' => 17 }
176
}
177
)
178
179
# Populate Route Tables
180
Rex::Socket::SwitchBoard.each do |route|
181
if route.comm.is_a?(Msf::Session)
182
gw = "Session #{route.comm.sid}"
183
else
184
gw = route.comm.name.split(/::/)[-1]
185
end
186
187
tbl_ipv4 << [ route.subnet, route.netmask, gw ] if Rex::Socket.is_ipv4?(route.netmask)
188
tbl_ipv6 << [ route.subnet, route.netmask, gw ] if Rex::Socket.is_ipv6?(route.netmask)
189
end
190
191
# Print Route Tables
192
print_status(tbl_ipv4.to_s) if !tbl_ipv4.rows.empty?
193
print_status(tbl_ipv6.to_s) if !tbl_ipv6.rows.empty?
194
if (tbl_ipv4.rows.length + tbl_ipv6.rows.length) < 1
195
print_status('There are currently no routes defined.')
196
elsif tbl_ipv4.rows.empty? && !tbl_ipv6.rows.empty?
197
print_status('There are currently no IPv4 routes defined.')
198
elsif !tbl_ipv4.rows.empty? && tbl_ipv6.rows.empty?
199
print_status('There are currently no IPv6 routes defined.')
200
end
201
end
202
203
# Validation check on an IPv4 address
204
#
205
# Yet another IP validator. I'm sure there's some Rex
206
# function that can just do this.
207
#
208
# @return [string class] IPv4 subnet
209
def check_ip(ip = nil)
210
return false if ip.nil? || ip.strip.empty?
211
212
begin
213
rw = Rex::Socket::RangeWalker.new(ip.strip)
214
(rw.valid? && rw.length == 1) ? true : false
215
rescue StandardError
216
false
217
end
218
end
219
220
# Converts a CIDR value to a netmask
221
#
222
# @return [string class] IPv4 netmask
223
def cidr_to_netmask(cidr)
224
int = cidr.gsub(/\x2f/, '').to_i
225
Rex::Socket.addr_ctoa(int)
226
end
227
228
# Validates the user input 'NETMASK'
229
#
230
# @return [string class] IPv4 netmask
231
def netmask
232
case datastore['NETMASK']
233
when /^\x2f[0-9]{1,2}/
234
cidr_to_netmask(datastore['NETMASK'])
235
when /^[0-9]{1,3}\.[0-9]/ # Close enough, if it's wrong it'll fail out later.
236
datastore['NETMASK']
237
else
238
'255.255.255.0'
239
end
240
end
241
242
# This function adds a route to the framework routing table
243
#
244
# @subnet [string class] subnet to add
245
# @netmask [string class] netmask
246
# @origin [string class] where route is coming from. Nill for none.
247
#
248
# @return [true] If added
249
# @return [false] If not
250
def add_route(subnet, netmask, origin = nil)
251
if origin
252
origin = " from #{origin}"
253
else
254
origin = ''
255
end
256
257
begin
258
if Rex::Socket::SwitchBoard.add_route(subnet, netmask, session)
259
print_good("Route added to subnet #{subnet}/#{netmask}#{origin}.")
260
return true
261
else
262
print_error("Could not add route to subnet #{subnet}/#{netmask}#{origin}.")
263
return false
264
end
265
rescue ::Rex::Post::Meterpreter::RequestError => e
266
print_error("Could not add route to subnet #{subnet}/#{netmask}#{origin}.")
267
print_error("#{e.class} #{e.message}\n#{e.backtrace * "\n"}")
268
return false
269
end
270
end
271
272
# This function removes a route to the framework routing table
273
#
274
# @subnet [string class] subnet to add
275
# @netmask [string class] netmask
276
# @origin [string class] where route is coming from.
277
#
278
# @return [true] If removed
279
# @return [false] If not
280
def delete_route(subnet, netmask)
281
Rex::Socket::SwitchBoard.remove_route(subnet, netmask, session)
282
rescue ::Rex::Post::Meterpreter::RequestError => e
283
print_error("Could not remove route to subnet #{subnet}/#{netmask}")
284
print_error("#{e.class} #{e.message}\n#{e.backtrace * "\n"}")
285
return false
286
end
287
288
# This function will exclude loopback, multicast, and default routes
289
#
290
# @subnet [string class] IPv4 subnet or address to check
291
# @netmask [string class] IPv4 netmask to check
292
#
293
# @return [true] If good to add
294
# @return [false] If not
295
def is_routable?(subnet, netmask)
296
if subnet =~ /^224\.|^127\./
297
return false
298
elsif subnet == '0.0.0.0'
299
return false
300
elsif netmask == '255.255.255.255'
301
return false
302
end
303
304
return true
305
end
306
307
# Search for valid subnets on the target and attempt
308
# add a route to each. (Operation from auto_add_route plugin.)
309
#
310
# @return [void] A useful return value is not expected here
311
def autoadd_routes
312
return unless route_compatible?
313
314
print_status('Searching for subnets to autoroute.')
315
found = false
316
317
begin
318
session.net.config.each_route do |route|
319
next unless Rex::Socket.is_ipv4?(route.subnet) && Rex::Socket.is_ipv4?(route.netmask) # Pick out the IPv4 addresses
320
321
subnet = get_subnet(route.subnet, route.netmask) # Make sure that the subnet is actually a subnet and not an IP address. Android phones like to send over their IP.
322
next unless is_routable?(subnet, route.netmask)
323
324
if !Rex::Socket::SwitchBoard.route_exists?(subnet, route.netmask) && add_route(subnet, route.netmask, "host's routing table")
325
found = true
326
end
327
end
328
rescue ::Rex::Post::Meterpreter::RequestError
329
print_status('Unable to get routes from session, trying interface list.')
330
end
331
332
if !autoadd_interface_routes && !found # Check interface list for more possible routes
333
print_status('Did not find any new subnets to add.')
334
end
335
end
336
337
# Look at network interfaces as options for additional routes.
338
# If the routes are not already included they will be added.
339
#
340
# @return [true] A route from the interface list was added
341
# @return [false] No additional routes were added
342
def autoadd_interface_routes
343
return unless interface_compatible?
344
345
found = false
346
347
begin
348
session.net.config.each_interface do |interface| # Step through each of the network interfaces
349
(0..(interface.addrs.size - 1)).each do |index| # Step through the addresses for the interface
350
ip_addr = interface.addrs[index]
351
netmask = interface.netmasks[index]
352
353
next unless Rex::Socket.is_ipv4?(ip_addr) && Rex::Socket.is_ipv4?(netmask) # Pick out the IPv4 addresses
354
next unless is_routable?(ip_addr, netmask)
355
356
subnet = get_subnet(ip_addr, netmask)
357
358
if subnet && !Rex::Socket::SwitchBoard.route_exists?(subnet, netmask) && add_route(subnet, netmask, interface.mac_name)
359
found = true
360
end
361
end
362
end
363
rescue ::Rex::Post::Meterpreter::RequestError
364
print_error('Unable to get interface information from session.')
365
end
366
return found
367
end
368
369
# Take an IP address and a netmask and return the appropreate subnet "Network"
370
#
371
# @ip_addr [string class] Input IPv4 Address
372
# @netmask [string class] Input IPv4 Netmask
373
#
374
# @return [string class] The subnet related to the IP address and netmask
375
# @return [nil class] Something is out of range
376
def get_subnet(ip_addr, netmask)
377
return nil if !validate_cmd(ip_addr, netmask) # make sure IP and netmask are valid
378
379
nets = ip_addr.split('.')
380
masks = netmask.split('.')
381
output = ''
382
383
4.times do |index|
384
octet = get_subnet_octet(int_or_nil(nets[index]), int_or_nil(masks[index]))
385
return nil if !octet
386
387
output << octet.to_s
388
output << '.' if index < 3
389
end
390
return output
391
end
392
393
# Input an octet of an IPv4 address and the cooresponding octet of the
394
# IPv4 netmask then return the appropreate subnet octet.
395
#
396
# @net [integer class] IPv4 address octet
397
# @mask [integer class] Ipv4 netmask octet
398
#
399
# @return [integer class] Octet of the subnet
400
# @return [nil class] If an input is nil
401
def get_subnet_octet(net, mask)
402
return nil if !net || !mask
403
404
subnet_range = 256 - mask # This is the address space of the subnet octet
405
406
multi = net / subnet_range # Integer division to get the multiplier needed to determine subnet octet
407
408
return(subnet_range * multi) # Multiply to get subnet octet
409
end
410
411
# Take a string of numbers and converts it to an integer.
412
#
413
# @string [string class] Input string, needs to be all numbers (0..9)
414
#
415
# @return [integer class] Integer representation of the number string
416
# @return [nil class] string contains non-numbers, cannot convert
417
def int_or_nil(string)
418
num = string.to_i
419
num if num.to_s == string
420
end
421
422
# Add a default route to the routing table
423
#
424
# @return [void] A useful return value is not expected here
425
def add_default
426
subnet = '0.0.0.0'
427
mask = '0.0.0.0'
428
429
switch_board = Rex::Socket::SwitchBoard.instance
430
print_status('Attempting to add a default route.')
431
432
if !switch_board.route_exists?(subnet, mask)
433
add_route(subnet, mask)
434
end
435
end
436
437
# Checks to see if the session is ready.
438
#
439
# Some Meterpreter types, like python, can take a few seconds to
440
# become fully established. This gracefully exits if the session
441
# is not ready yet.
442
#
443
# @return [true class] Session is good
444
# @return [false class] Session is not
445
def session_good?
446
if !session.info
447
print_error('Session is not yet fully established. Try again in a bit.')
448
return false
449
end
450
return true
451
end
452
453
# Checks to see if the session has routing capabilities
454
#
455
# @return [true class] Session has routing capabilities
456
# @return [false class] Session does not
457
def route_compatible?
458
session.respond_to?(:net) &&
459
session.net.config.respond_to?(:each_route)
460
end
461
462
# Checks to see if the session has capabilities of accessing network interfaces
463
#
464
# @return [true class] Session has ability to access network interfaces
465
# @return [false class] Session does not
466
def interface_compatible?
467
session.respond_to?(:net) &&
468
session.net.config.respond_to?(:each_interface)
469
end
470
471
# Validates the command options
472
#
473
# @return [true class] Everything is good
474
# @return [false class] Not so much
475
def validate_cmd(subnet = nil, netmask = nil)
476
if subnet.nil?
477
print_error 'Missing subnet option'
478
return false
479
end
480
481
unless check_ip(subnet)
482
print_error 'Subnet invalid (must be IPv4)'
483
return false
484
end
485
486
if netmask && !Rex::Socket.addr_atoc(netmask)
487
print_error 'Netmask invalid (must define contiguous IP addressing)'
488
return false
489
end
490
491
if netmask && !check_ip(netmask)
492
print_error 'Netmask invalid'
493
return false
494
end
495
return true
496
end
497
end
498
499