CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

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