Path: blob/master/modules/post/multi/manage/autoroute.rb
19851 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Post67def initialize(info = {})8super(9update_info(10info,11'Name' => 'Multi Manage Network Route via Meterpreter Session',12'Description' => %q{13This module manages session routing via an existing14Meterpreter session. It enables other modules to 'pivot' through a15compromised host when connecting to the named NETWORK and SUBMASK.16Autoadd will search a session for valid subnets from the routing table17and interface list then add routes to them. Default will add a default18route so that all TCP/IP traffic not specified in the MSF routing table19will be routed through the session when pivoting. See documentation for more20'info -d' and click 'Knowledge Base'21},22'License' => MSF_LICENSE,23'Author' => [24'todb',25'Josh Hale "sn0wfa11" <jhale85446[at]gmail.com>'26],27'SessionTypes' => [ 'meterpreter'],28'Compat' => {29'Meterpreter' => {30'Commands' => %w[31stdapi_net_config_get_interfaces32stdapi_net_config_get_routes33]34}35},36'Notes' => {37'Stability' => [CRASH_SAFE],38'SideEffects' => [],39'Reliability' => []40}41)42)4344register_options(45[46OptString.new('SUBNET', [false, 'Subnet (IPv4, for example, 10.10.10.0)', nil]),47OptString.new('NETMASK', [false, 'Netmask (IPv4 as "255.255.255.0" or CIDR as "/24"', '255.255.255.0']),48OptEnum.new('CMD', [true, 'Specify the autoroute command', 'autoadd', ['add', 'autoadd', 'print', 'delete', 'default']])49]50)51end5253# Get the CMD string vs ACTION54#55# Backwards compatability: This was changed because the option name of "ACTION"56# is special for some things, and indicates the :action attribute, not a datastore option.57# However, this is a semi-popular module, though, so I'd prefer not to break people's58# RC scripts that set ACTION. Note that ACTION is preferred over CMD.59#60# TODO: The better solution is to use 'Action' and 'DefaultAction' info elements,61# but there are some squirelly problems right now with rendering these for post modules.62#63# @return [string class] cmd string64def route_cmd65if datastore['ACTION'].to_s.empty?66datastore['CMD'].to_s.downcase.to_sym67else68wlog("Warning, deprecated use of 'ACTION' datastore option for #{fullname}'. Use 'CMD' instead.")69datastore['ACTION'].to_s.downcase.to_sym70end71end7273# Run Method for when run command is issued74#75# @return [void] A useful return value is not expected here76def run77return unless session_good?7879hostname = sysinfo.nil? ? cmd_exec('hostname') : sysinfo['Computer']80print_status("Running module against #{hostname} (#{session.session_host})")8182subnet = datastore['SUBNET']8384case route_cmd85when :print86print_routes87when :add88if validate_cmd(subnet, netmask)89print_status("Adding a route to #{subnet}/#{netmask}...")90add_route(subnet, netmask)91end92when :autoadd93autoadd_routes94when :default95add_default96when :delete97if subnet98print_status("Deleting route to #{subnet}/#{netmask}...")99delete_route(subnet, netmask)100else101delete_all_routes102end103end104end105106# Delete all routes from framework routing table.107#108# @return [void] A useful return value is not expected here109def delete_all_routes110if Rex::Socket::SwitchBoard.routes.empty?111print_status('No routes associated with this session to delete.')112return113end114115print_status("Deleting all routes associated with session: #{session.sid}.")116117loop do118count = 0119Rex::Socket::SwitchBoard.each do |route|120next unless route.comm == session121122print_status("Deleting: #{route.subnet}/#{route.netmask}")123delete_route(route.subnet, route.netmask)124count += 1125end126127break if count == 0128end129130print_status('Deleted all routes')131end132133# Print all of the active routes defined on the framework134#135# Identical functionality to command_dispatcher/core.rb, and136# nearly identical code137#138# @return [void] A useful return value is not expected here139def print_routes140# IPv4 Table141tbl_ipv4 = Msf::Ui::Console::Table.new(142Msf::Ui::Console::Table::Style::Default,143'Header' => 'IPv4 Active Routing Table',144'Prefix' => "\n",145'Postfix' => "\n",146'Columns' =>147[148'Subnet',149'Netmask',150'Gateway',151],152'ColProps' =>153{154'Subnet' => { 'Width' => 17 },155'Netmask' => { 'Width' => 17 }156}157)158159# IPv6 Table160tbl_ipv6 = Msf::Ui::Console::Table.new(161Msf::Ui::Console::Table::Style::Default,162'Header' => 'IPv6 Active Routing Table',163'Prefix' => "\n",164'Postfix' => "\n",165'Columns' =>166[167'Subnet',168'Netmask',169'Gateway',170],171'ColProps' =>172{173'Subnet' => { 'Width' => 17 },174'Netmask' => { 'Width' => 17 }175}176)177178# Populate Route Tables179Rex::Socket::SwitchBoard.each do |route|180if route.comm.is_a?(Msf::Session)181gw = "Session #{route.comm.sid}"182else183gw = route.comm.name.split(/::/)[-1]184end185186tbl_ipv4 << [ route.subnet, route.netmask, gw ] if Rex::Socket.is_ipv4?(route.netmask)187tbl_ipv6 << [ route.subnet, route.netmask, gw ] if Rex::Socket.is_ipv6?(route.netmask)188end189190# Print Route Tables191print_status(tbl_ipv4.to_s) if !tbl_ipv4.rows.empty?192print_status(tbl_ipv6.to_s) if !tbl_ipv6.rows.empty?193if (tbl_ipv4.rows.length + tbl_ipv6.rows.length) < 1194print_status('There are currently no routes defined.')195elsif tbl_ipv4.rows.empty? && !tbl_ipv6.rows.empty?196print_status('There are currently no IPv4 routes defined.')197elsif !tbl_ipv4.rows.empty? && tbl_ipv6.rows.empty?198print_status('There are currently no IPv6 routes defined.')199end200end201202# Validation check on an IPv4 address203#204# Yet another IP validator. I'm sure there's some Rex205# function that can just do this.206#207# @return [string class] IPv4 subnet208def check_ip(ip = nil)209return false if ip.nil? || ip.strip.empty?210211begin212rw = Rex::Socket::RangeWalker.new(ip.strip)213(rw.valid? && rw.length == 1) ? true : false214rescue StandardError215false216end217end218219# Converts a CIDR value to a netmask220#221# @return [string class] IPv4 netmask222def cidr_to_netmask(cidr)223int = cidr.gsub(/\x2f/, '').to_i224Rex::Socket.addr_ctoa(int)225end226227# Validates the user input 'NETMASK'228#229# @return [string class] IPv4 netmask230def netmask231case datastore['NETMASK']232when /^\x2f[0-9]{1,2}/233cidr_to_netmask(datastore['NETMASK'])234when /^[0-9]{1,3}\.[0-9]/ # Close enough, if it's wrong it'll fail out later.235datastore['NETMASK']236else237'255.255.255.0'238end239end240241# This function adds a route to the framework routing table242#243# @subnet [string class] subnet to add244# @netmask [string class] netmask245# @origin [string class] where route is coming from. Nill for none.246#247# @return [true] If added248# @return [false] If not249def add_route(subnet, netmask, origin = nil)250if origin251origin = " from #{origin}"252else253origin = ''254end255256begin257if Rex::Socket::SwitchBoard.add_route(subnet, netmask, session)258print_good("Route added to subnet #{subnet}/#{netmask}#{origin}.")259return true260else261print_error("Could not add route to subnet #{subnet}/#{netmask}#{origin}.")262return false263end264rescue ::Rex::Post::Meterpreter::RequestError => e265print_error("Could not add route to subnet #{subnet}/#{netmask}#{origin}.")266print_error("#{e.class} #{e.message}\n#{e.backtrace * "\n"}")267return false268end269end270271# This function removes a route to the framework routing table272#273# @subnet [string class] subnet to add274# @netmask [string class] netmask275# @origin [string class] where route is coming from.276#277# @return [true] If removed278# @return [false] If not279def delete_route(subnet, netmask)280Rex::Socket::SwitchBoard.remove_route(subnet, netmask, session)281rescue ::Rex::Post::Meterpreter::RequestError => e282print_error("Could not remove route to subnet #{subnet}/#{netmask}")283print_error("#{e.class} #{e.message}\n#{e.backtrace * "\n"}")284return false285end286287# This function will exclude loopback, multicast, and default routes288#289# @subnet [string class] IPv4 subnet or address to check290# @netmask [string class] IPv4 netmask to check291#292# @return [true] If good to add293# @return [false] If not294def is_routable?(subnet, netmask)295if subnet =~ /^224\.|^127\./296return false297elsif subnet == '0.0.0.0'298return false299elsif netmask == '255.255.255.255'300return false301end302303return true304end305306# Search for valid subnets on the target and attempt307# add a route to each. (Operation from auto_add_route plugin.)308#309# @return [void] A useful return value is not expected here310def autoadd_routes311return unless route_compatible?312313print_status('Searching for subnets to autoroute.')314found = false315316begin317session.net.config.each_route do |route|318next unless Rex::Socket.is_ipv4?(route.subnet) && Rex::Socket.is_ipv4?(route.netmask) # Pick out the IPv4 addresses319320subnet = 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.321next unless is_routable?(subnet, route.netmask)322323if !Rex::Socket::SwitchBoard.route_exists?(subnet, route.netmask) && add_route(subnet, route.netmask, "host's routing table")324found = true325end326end327rescue ::Rex::Post::Meterpreter::RequestError328print_status('Unable to get routes from session, trying interface list.')329end330331if !autoadd_interface_routes && !found # Check interface list for more possible routes332print_status('Did not find any new subnets to add.')333end334end335336# Look at network interfaces as options for additional routes.337# If the routes are not already included they will be added.338#339# @return [true] A route from the interface list was added340# @return [false] No additional routes were added341def autoadd_interface_routes342return unless interface_compatible?343344found = false345346begin347session.net.config.each_interface do |interface| # Step through each of the network interfaces348(0..(interface.addrs.size - 1)).each do |index| # Step through the addresses for the interface349ip_addr = interface.addrs[index]350netmask = interface.netmasks[index]351352next unless Rex::Socket.is_ipv4?(ip_addr) && Rex::Socket.is_ipv4?(netmask) # Pick out the IPv4 addresses353next unless is_routable?(ip_addr, netmask)354355subnet = get_subnet(ip_addr, netmask)356357if subnet && !Rex::Socket::SwitchBoard.route_exists?(subnet, netmask) && add_route(subnet, netmask, interface.mac_name)358found = true359end360end361end362rescue ::Rex::Post::Meterpreter::RequestError363print_error('Unable to get interface information from session.')364end365return found366end367368# Take an IP address and a netmask and return the appropreate subnet "Network"369#370# @ip_addr [string class] Input IPv4 Address371# @netmask [string class] Input IPv4 Netmask372#373# @return [string class] The subnet related to the IP address and netmask374# @return [nil class] Something is out of range375def get_subnet(ip_addr, netmask)376return nil if !validate_cmd(ip_addr, netmask) # make sure IP and netmask are valid377378nets = ip_addr.split('.')379masks = netmask.split('.')380output = ''3813824.times do |index|383octet = get_subnet_octet(int_or_nil(nets[index]), int_or_nil(masks[index]))384return nil if !octet385386output << octet.to_s387output << '.' if index < 3388end389return output390end391392# Input an octet of an IPv4 address and the cooresponding octet of the393# IPv4 netmask then return the appropreate subnet octet.394#395# @net [integer class] IPv4 address octet396# @mask [integer class] Ipv4 netmask octet397#398# @return [integer class] Octet of the subnet399# @return [nil class] If an input is nil400def get_subnet_octet(net, mask)401return nil if !net || !mask402403subnet_range = 256 - mask # This is the address space of the subnet octet404405multi = net / subnet_range # Integer division to get the multiplier needed to determine subnet octet406407return(subnet_range * multi) # Multiply to get subnet octet408end409410# Take a string of numbers and converts it to an integer.411#412# @string [string class] Input string, needs to be all numbers (0..9)413#414# @return [integer class] Integer representation of the number string415# @return [nil class] string contains non-numbers, cannot convert416def int_or_nil(string)417num = string.to_i418num if num.to_s == string419end420421# Add a default route to the routing table422#423# @return [void] A useful return value is not expected here424def add_default425subnet = '0.0.0.0'426mask = '0.0.0.0'427428switch_board = Rex::Socket::SwitchBoard.instance429print_status('Attempting to add a default route.')430431if !switch_board.route_exists?(subnet, mask)432add_route(subnet, mask)433end434end435436# Checks to see if the session is ready.437#438# Some Meterpreter types, like python, can take a few seconds to439# become fully established. This gracefully exits if the session440# is not ready yet.441#442# @return [true class] Session is good443# @return [false class] Session is not444def session_good?445if !session.info446print_error('Session is not yet fully established. Try again in a bit.')447return false448end449return true450end451452# Checks to see if the session has routing capabilities453#454# @return [true class] Session has routing capabilities455# @return [false class] Session does not456def route_compatible?457session.respond_to?(:net) &&458session.net.config.respond_to?(:each_route)459end460461# Checks to see if the session has capabilities of accessing network interfaces462#463# @return [true class] Session has ability to access network interfaces464# @return [false class] Session does not465def interface_compatible?466session.respond_to?(:net) &&467session.net.config.respond_to?(:each_interface)468end469470# Validates the command options471#472# @return [true class] Everything is good473# @return [false class] Not so much474def validate_cmd(subnet = nil, netmask = nil)475if subnet.nil?476print_error 'Missing subnet option'477return false478end479480unless check_ip(subnet)481print_error 'Subnet invalid (must be IPv4)'482return false483end484485if netmask && !Rex::Socket.addr_atoc(netmask)486print_error 'Netmask invalid (must define contiguous IP addressing)'487return false488end489490if netmask && !check_ip(netmask)491print_error 'Netmask invalid'492return false493end494return true495end496end497498499