class Msf::RPC::Client

Attributes

info[RW]

@!attribute info

@return [Hash] Login information.
token[RW]

@!attribute token

@return [String] A login token.

Public Class Methods

new(info = {}) click to toggle source

Initializes the RPC client to connect to: 127.0.0.1:3790 (TLS1) The connection information is overridden through the optional info hash.

@param [Hash] info Information needed for the initialization. @option info [String] :token A token used by the client. @return [void]

# File lib/msfrpc-client/client.rb, line 35
def initialize(info = {})
  @user = nil
  @pass = nil

  self.info = {
    host:  '127.0.0.1',
    port:  3790,
    uri:   '/api/',
    ssl:   true,
    ssl_version: 'TLS1.2',
  }.merge(info)

  self.token = self.info[:token]
end
option_handler(options = {}) click to toggle source

Load options from the command-line, environment. and any configuration files specified

# File lib/msfrpc-client/client.rb, line 197
def self.option_handler(options = {})
  options[:host]   ||= ENV['MSFRPC_HOST']
  options[:port]   ||= ENV['MSFRPC_PORT']
  options[:uri]    ||= ENV['MSFRPC_URI']
  options[:user]   ||= ENV['MSFRPC_USER']
  options[:pass]   ||= ENV['MSFRPC_PASS']
  options[:ssl]    ||= ENV['MSFRPC_SSL']
  options[:token]  ||= ENV['MSFRPC_TOKEN']
  options[:config] ||= ENV['MSFRPC_CONFIG']

  empty_keys = options.keys.select { |k| options[k].nil? }
  empty_keys.each { |k| options.delete(k) }

  config_file = options.delete(:config)

  if config_file
    yaml_data = ::File.read(config_file) rescue nil
    if yaml_data
      yaml = ::YAML.load(yaml_data) rescue nil
      if yaml && yaml.is_a?(::Hash) && yaml['options']
        yaml['options'].each_pair do |k, v|
          case k
          when 'ssl'
            options[k.intern] = !!(v.to_s =~ /^(t|y|1)/i)
          when 'port'
            options[k.intern] = v.to_i
          else
            options[k.intern] = v
          end
        end
      else
        $stderr.puts "Could not parse configuration file: #{config_file}"
        exit(1)
      end
    else
      $stderr.puts "Could not read configuration file: #{config_file}"
      exit(1)
    end
  end

  options[:port] = options[:port].to_i if options[:port]

  options[:ssl] = !!(options[:ssl].to_s =~ /^(t|y|1)/i) if options[:ssl]

  options
end
option_parser(options) click to toggle source

Provides a parser object that understands the RPC specific options

# File lib/msfrpc-client/client.rb, line 144
def self.option_parser(options)
  parser = OptionParser.new

  parser.banner = "Usage: #{$PROGRAM_NAME} [options]"
  parser.separator('')
  parser.separator('RPC Options:')

  parser.on('--rpc-host HOST') do |v|
    options[:host] = v
  end

  parser.on('--rpc-port PORT') do |v|
    options[:port] = v.to_i
  end

  parser.on('--rpc-ssl <true|false>') do |v|
    options[:ssl] = v
  end

  parser.on('--rpc-uri URI') do |v|
    options[:uri] = v
  end

  parser.on('--rpc-user USERNAME') do |v|
    options[:user] = v
  end

  parser.on('--rpc-pass PASSWORD') do |v|
    options[:pass] = v
  end

  parser.on('--rpc-token TOKEN') do |v|
    options[:token] = v
  end

  parser.on('--rpc-config CONFIG-FILE') do |v|
    options[:config] = v
  end

  parser.on('--rpc-help') do
    $stderr.puts parser
    exit(1)
  end

  parser.separator('')

  parser
end

Public Instance Methods

call(meth, *args) click to toggle source

Calls an API.

@param [String] meth The RPC API to call. @param [Array<string>] args The arguments to pass. @raise [RuntimeError] Something is wrong while calling the remote API,

including:
* A missing token (your client needs to
  authenticate).
* A unexpected response from the server, such as
  a timeout or unexpected HTTP code.

@raise [Msf::RPC::ServerException] The RPC service returns an error. @return [Hash] The API response. It contains the following keys:

* 'version' [String] Framework version.
* 'ruby' [String] Ruby version.
* 'api' [String] API version.

@example

# This will return something like this:
# {"version"=>"4.11.0-dev",
#  "ruby"=>"2.1.5 x86_64-darwin14.0 2014-11-13", "api"=>"1.0"}
rpc.call('core.version')
# File lib/msfrpc-client/client.rb, line 101
def call(meth, *args)
  if meth == 'auth.logout'
    do_logout_cleanup
  end

  unless meth == 'auth.login'
    unless self.token
      raise Msf::RPC::Exception.new('Client not authenticated')
    end
    args.unshift(self.token)
  end

  args.unshift(meth)

  begin
    send_rpc_request(args)
  rescue Msf::RPC::ServerException => e
    if e.message =~ /Invalid Authentication Token/i &&
       meth != 'auth.login' && @user && @pass
      re_login
      args[1] = self.token
      retry
    else
      raise e
    end
  end
end
close() click to toggle source

Closes the client.

@return [void]

# File lib/msfrpc-client/client.rb, line 132
def close
  @cli = nil
end
login(user, pass) click to toggle source

Logs in by calling the 'auth.login' API. The authentication token will expire after 5 minutes, but will automatically be rewnewed when you make a new RPC request.

@param [String] user Username. @param [String] pass Password. @raise RuntimeError Indicating a failed authentication. @return [TrueClass] Indicating a successful login.

# File lib/msfrpc-client/client.rb, line 59
def login(user, pass)
  @user = user
  @pass = pass
  res = self.call('auth.login', user, pass)
  unless res && res['result'] == 'success'
    raise Msf::RPC::Exception.new('Authentication failed')
  end
  self.token = res['token']
  true
end
re_login() click to toggle source

Attempts to login again with the last known user name and password.

@return [TrueClass] Indicating a successful login.

# File lib/msfrpc-client/client.rb, line 75
def re_login
  login(@user, @pass)
end

Private Instance Methods

do_logout_cleanup() click to toggle source
# File lib/msfrpc-client/client.rb, line 290
def do_logout_cleanup
  @user = nil
  @pass = nil
end
send_rpc_request(args) click to toggle source
# File lib/msfrpc-client/client.rb, line 246
def send_rpc_request(args)
  unless @cli
    @cli = Net::HTTP.new(info[:host], info[:port])
    @cli.use_ssl = info[:ssl]
    @cli.verify_mode = OpenSSL::SSL::VERIFY_NONE
  end

  req = Net::HTTP::Post.new(self.info[:uri], initheader = {
    'User-Agent' => "Metasploit RPC Client/#{API_VERSION}",
    'Content-Type' => 'binary/message-pack'
    }
  )
  req.body = args.to_msgpack

  begin
    res = @cli.request(req)
  rescue => e
      raise Msf::RPC::ServerException.new(000, e.message, e.class)
  end

  if res && [200, 401, 403, 500].include?(res.code.to_i)
    resp = MessagePack.unpack(res.body)

    # Boolean true versus truthy check required here;
    # RPC responses such as { "error" => "Here I am" } and
    # { "error" => # "" } must be accommodated.
    if resp && resp.is_a?(::Hash) && resp['error'] == true
      raise Msf::RPC::ServerException.new(
        resp['error_code'] || res.code,
        resp['error_message'] || resp['error_string'],
        resp['error_class'], resp['error_backtrace']
      )
    end

    return resp
  else
    if res
      raise Msf::RPC::Exception.new(res.inspect)
    else
      raise Msf::RPC::Exception.new('Unknown error parsing or sending response')
    end
  end
end