">
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Post
def initialize(info = {})
super(
update_info(
info,
'Name' => 'Multi Manage Network Route via Meterpreter Session',
'Description' => %q{
This module manages session routing via an existing
Meterpreter session. It enables other modules to 'pivot' through a
compromised host when connecting to the named NETWORK and SUBMASK.
Autoadd will search a session for valid subnets from the routing table
and interface list then add routes to them. Default will add a default
route so that all TCP/IP traffic not specified in the MSF routing table
will be routed through the session when pivoting. See documentation for more
'info -d' and click 'Knowledge Base'
},
'License' => MSF_LICENSE,
'Author' => [
'todb',
'Josh Hale "sn0wfa11" <jhale85446[at]gmail.com>'
],
'SessionTypes' => [ 'meterpreter'],
'Compat' => {
'Meterpreter' => {
'Commands' => %w[
stdapi_net_config_get_interfaces
stdapi_net_config_get_routes
]
}
}
)
)
register_options(
[
OptString.new('SUBNET', [false, 'Subnet (IPv4, for example, 10.10.10.0)', nil]),
OptString.new('NETMASK', [false, 'Netmask (IPv4 as "255.255.255.0" or CIDR as "/24"', '255.255.255.0']),
OptEnum.new('CMD', [true, 'Specify the autoroute command', 'autoadd', ['add', 'autoadd', 'print', 'delete', 'default']])
]
)
end
# Get the CMD string vs ACTION
#
# Backwards compatability: This was changed because the option name of "ACTION"
# is special for some things, and indicates the :action attribute, not a datastore option.
# However, this is a semi-popular module, though, so I'd prefer not to break people's
# RC scripts that set ACTION. Note that ACTION is preferred over CMD.
#
# TODO: The better solution is to use 'Action' and 'DefaultAction' info elements,
# but there are some squirelly problems right now with rendering these for post modules.
#
# @return [string class] cmd string
def route_cmd
if datastore['ACTION'].to_s.empty?
datastore['CMD'].to_s.downcase.to_sym
else
wlog("Warning, deprecated use of 'ACTION' datastore option for #{fullname}'. Use 'CMD' instead.")
datastore['ACTION'].to_s.downcase.to_sym
end
end
# Run Method for when run command is issued
#
# @return [void] A useful return value is not expected here
def run
return unless session_good?
print_status("Running module against #{sysinfo['Computer']}")
case route_cmd
when :print
print_routes
when :add
if validate_cmd(datastore['SUBNET'], netmask)
print_status('Adding a route to %s/%s...' % [datastore['SUBNET'], netmask])
add_route(datastore['SUBNET'], netmask)
end
when :autoadd
autoadd_routes
when :default
add_default
when :delete
if datastore['SUBNET']
print_status('Deleting route to %s/%s...' % [datastore['SUBNET'], netmask])
delete_route(datastore['SUBNET'], netmask)
else
delete_all_routes
end
end
end
# Delete all routes from framework routing table.
#
# @return [void] A useful return value is not expected here
def delete_all_routes
if !Rex::Socket::SwitchBoard.routes.empty?
print_status("Deleting all routes associated with session: #{session.sid}.")
loop do
count = 0
Rex::Socket::SwitchBoard.each do |route|
if route.comm == session
print_status("Deleting: #{route.subnet}/#{route.netmask}")
delete_route(route.subnet, route.netmask)
end
end
Rex::Socket::SwitchBoard.each do |route|
count += 1 if route.comm == session
end
break if count == 0
end
print_status('Deleted all routes')
else
print_status('No routes associated with this session to delete.')
end
end
# Print all of the active routes defined on the framework
#
# Identical functionality to command_dispatcher/core.rb, and
# nearly identical code
#
# @return [void] A useful return value is not expected here
def print_routes
# IPv4 Table
tbl_ipv4 = Msf::Ui::Console::Table.new(
Msf::Ui::Console::Table::Style::Default,
'Header' => 'IPv4 Active Routing Table',
'Prefix' => "\n",
'Postfix' => "\n",
'Columns' =>
[
'Subnet',
'Netmask',
'Gateway',
],
'ColProps' =>
{
'Subnet' => { 'Width' => 17 },
'Netmask' => { 'Width' => 17 }
}
)
# IPv6 Table
tbl_ipv6 = Msf::Ui::Console::Table.new(
Msf::Ui::Console::Table::Style::Default,
'Header' => 'IPv6 Active Routing Table',
'Prefix' => "\n",
'Postfix' => "\n",
'Columns' =>
[
'Subnet',
'Netmask',
'Gateway',
],
'ColProps' =>
{
'Subnet' => { 'Width' => 17 },
'Netmask' => { 'Width' => 17 }
}
)
# Populate Route Tables
Rex::Socket::SwitchBoard.each do |route|
if route.comm.is_a?(Msf::Session)
gw = "Session #{route.comm.sid}"
else
gw = route.comm.name.split(/::/)[-1]
end
tbl_ipv4 << [ route.subnet, route.netmask, gw ] if Rex::Socket.is_ipv4?(route.netmask)
tbl_ipv6 << [ route.subnet, route.netmask, gw ] if Rex::Socket.is_ipv6?(route.netmask)
end
# Print Route Tables
print_status(tbl_ipv4.to_s) if !tbl_ipv4.rows.empty?
print_status(tbl_ipv6.to_s) if !tbl_ipv6.rows.empty?
if (tbl_ipv4.rows.length + tbl_ipv6.rows.length) < 1
print_status('There are currently no routes defined.')
elsif tbl_ipv4.rows.empty? && !tbl_ipv6.rows.empty?
print_status('There are currently no IPv4 routes defined.')
elsif !tbl_ipv4.rows.empty? && tbl_ipv6.rows.empty?
print_status('There are currently no IPv6 routes defined.')
end
end
# Validation check on an IPv4 address
#
# Yet another IP validator. I'm sure there's some Rex
# function that can just do this.
#
# @return [string class] IPv4 subnet
def check_ip(ip = nil)
return false if (ip.nil? || ip.strip.empty?)
begin
rw = Rex::Socket::RangeWalker.new(ip.strip)
(rw.valid? && rw.length == 1) ? true : false
rescue StandardError
false
end
end
# Converts a CIDR value to a netmask
#
# @return [string class] IPv4 netmask
def cidr_to_netmask(cidr)
int = cidr.gsub(/\x2f/, '').to_i
Rex::Socket.addr_ctoa(int)
end
# Validates the user input 'NETMASK'
#
# @return [string class] IPv4 netmask
def netmask
case datastore['NETMASK']
when /^\x2f[0-9]{1,2}/
cidr_to_netmask(datastore['NETMASK'])
when /^[0-9]{1,3}\.[0-9]/ # Close enough, if it's wrong it'll fail out later.
datastore['NETMASK']
else
'255.255.255.0'
end
end
# This function adds a route to the framework routing table
#
# @subnet [string class] subnet to add
# @netmask [string class] netmask
# @origin [string class] where route is coming from. Nill for none.
#
# @return [true] If added
# @return [false] If not
def add_route(subnet, netmask, origin = nil)
if origin
origin = " from #{origin}"
else
origin = ''
end
begin
if Rex::Socket::SwitchBoard.add_route(subnet, netmask, session)
print_good("Route added to subnet #{subnet}/#{netmask}#{origin}.")
return true
else
print_error("Could not add route to subnet #{subnet}/#{netmask}#{origin}.")
return false
end
rescue ::Rex::Post::Meterpreter::RequestError => e
print_error("Could not add route to subnet #{subnet}/#{netmask}#{origin}.")
print_error("#{e.class} #{e.message}\n#{e.backtrace * "\n"}")
return false
end
end
# This function removes a route to the framework routing table
#
# @subnet [string class] subnet to add
# @netmask [string class] netmask
# @origin [string class] where route is coming from.
#
# @return [true] If removed
# @return [false] If not
def delete_route(subnet, netmask)
Rex::Socket::SwitchBoard.remove_route(subnet, netmask, session)
rescue ::Rex::Post::Meterpreter::RequestError => e
print_error("Could not remove route to subnet #{subnet}/#{netmask}")
print_error("#{e.class} #{e.message}\n#{e.backtrace * "\n"}")
return false
end
# This function will exclude loopback, multicast, and default routes
#
# @subnet [string class] IPv4 subnet or address to check
# @netmask [string class] IPv4 netmask to check
#
# @return [true] If good to add
# @return [false] If not
def is_routable?(subnet, netmask)
if subnet =~ /^224\.|^127\./
return false
elsif subnet == '0.0.0.0'
return false
elsif netmask == '255.255.255.255'
return false
end
return true
end
# Search for valid subnets on the target and attempt
# add a route to each. (Operation from auto_add_route plugin.)
#
# @return [void] A useful return value is not expected here
def autoadd_routes
return unless route_compatible?
print_status('Searching for subnets to autoroute.')
found = false
begin
session.net.config.each_route do |route|
next unless (Rex::Socket.is_ipv4?(route.subnet) && Rex::Socket.is_ipv4?(route.netmask)) # Pick out the IPv4 addresses
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.
next unless is_routable?(subnet, route.netmask)
if !Rex::Socket::SwitchBoard.route_exists?(subnet, route.netmask) && add_route(subnet, route.netmask, "host's routing table")
found = true
end
end
rescue ::Rex::Post::Meterpreter::RequestError => e
print_status('Unable to get routes from session, trying interface list.')
end
if !autoadd_interface_routes && !found # Check interface list for more possible routes
print_status('Did not find any new subnets to add.')
end
end
# Look at network interfaces as options for additional routes.
# If the routes are not already included they will be added.
#
# @return [true] A route from the interface list was added
# @return [false] No additional routes were added
def autoadd_interface_routes
return unless interface_compatible?
found = false
begin
session.net.config.each_interface do |interface| # Step through each of the network interfaces
(0..(interface.addrs.size - 1)).each do |index| # Step through the addresses for the interface
ip_addr = interface.addrs[index]
netmask = interface.netmasks[index]
next unless (Rex::Socket.is_ipv4?(ip_addr) && Rex::Socket.is_ipv4?(netmask)) # Pick out the IPv4 addresses
next unless is_routable?(ip_addr, netmask)
subnet = get_subnet(ip_addr, netmask)
if subnet && !Rex::Socket::SwitchBoard.route_exists?(subnet, netmask) && add_route(subnet, netmask, interface.mac_name)
found = true
end
end
end
rescue ::Rex::Post::Meterpreter::RequestError => e
print_error('Unable to get interface information from session.')
end
return found
end
# Take an IP address and a netmask and return the appropreate subnet "Network"
#
# @ip_addr [string class] Input IPv4 Address
# @netmask [string class] Input IPv4 Netmask
#
# @return [string class] The subnet related to the IP address and netmask
# @return [nil class] Something is out of range
def get_subnet(ip_addr, netmask)
return nil if !validate_cmd(ip_addr, netmask) # make sure IP and netmask are valid
nets = ip_addr.split('.')
masks = netmask.split('.')
output = ''
4.times do |index|
octet = get_subnet_octet(int_or_nil(nets[index]), int_or_nil(masks[index]))
return nil if !octet
output << octet.to_s
output << '.' if index < 3
end
return output
end
# Input an octet of an IPv4 address and the cooresponding octet of the
# IPv4 netmask then return the appropreate subnet octet.
#
# @net [integer class] IPv4 address octet
# @mask [integer class] Ipv4 netmask octet
#
# @return [integer class] Octet of the subnet
# @return [nil class] If an input is nil
def get_subnet_octet(net, mask)
return nil if !net || !mask
subnet_range = 256 - mask # This is the address space of the subnet octet
multi = net / subnet_range # Integer division to get the multiplier needed to determine subnet octet
return(subnet_range * multi) # Multiply to get subnet octet
end
# Take a string of numbers and converts it to an integer.
#
# @string [string class] Input string, needs to be all numbers (0..9)
#
# @return [integer class] Integer representation of the number string
# @return [nil class] string contains non-numbers, cannot convert
def int_or_nil(string)
num = string.to_i
num if num.to_s == string
end
# Add a default route to the routing table
#
# @return [void] A useful return value is not expected here
def add_default
subnet = '0.0.0.0'
mask = '0.0.0.0'
switch_board = Rex::Socket::SwitchBoard.instance
print_status('Attempting to add a default route.')
if !switch_board.route_exists?(subnet, mask)
add_route(subnet, mask)
end
end
# Checks to see if the session is ready.
#
# Some Meterpreter types, like python, can take a few seconds to
# become fully established. This gracefully exits if the session
# is not ready yet.
#
# @return [true class] Session is good
# @return [false class] Session is not
def session_good?
if !session.info
print_error('Session is not yet fully established. Try again in a bit.')
return false
end
return true
end
# Checks to see if the session has routing capabilities
#
# @return [true class] Session has routing capabilities
# @return [false class] Session does not
def route_compatible?
session.respond_to?(:net) &&
session.net.config.respond_to?(:each_route)
end
# Checks to see if the session has capabilities of accessing network interfaces
#
# @return [true class] Session has ability to access network interfaces
# @return [false class] Session does not
def interface_compatible?
session.respond_to?(:net) &&
session.net.config.respond_to?(:each_interface)
end
# Validates the command options
#
# @return [true class] Everything is good
# @return [false class] Not so much
def validate_cmd(subnet = nil, netmask = nil)
if subnet.nil?
print_error 'Missing subnet option'
return false
end
unless check_ip(subnet)
print_error 'Subnet invalid (must be IPv4)'
return false
end
if (netmask && !Rex::Socket.addr_atoc(netmask))
print_error 'Netmask invalid (must define contiguous IP addressing)'
return false
end
if (netmask && !check_ip(netmask))
print_error 'Netmask invalid'
return false
end
return true
end
end
Data
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation