Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

first commit

  • Loading branch information...
commit 9ffa5875b931429468d26f68a5874fcbb62031b3 0 parents
HD Moore authored
9 README.markdown
@@ -0,0 +1,9 @@
+# Metasploit Pro RPC Client
+
+This is the official Ruby client for the Metasploit Pro RPC service. Metasploit
+Pro is a commercial penetration testing product provided by Rapid7. For more
+information on Metasploit Pro, please visit the http://metasploit.com/ site.
+
+# Credits
+Rapid7 LLC
+
18 Rakefile
@@ -0,0 +1,18 @@
+# encoding: utf-8
+
+task :build => :update do
+ Rake::Task['clean'].execute
+ puts "[*] Building msfrpc-client.gemspec"
+ system "gem build msfrpc-client.gemspec &> /dev/null"
+end
+
+task :release => :build do
+ puts "[*] Pushing msfrpc-client to rubygems.org"
+ system "gem push msfrpc-client-*.gem &> /dev/null"
+ Rake::Task['clean'].execute
+end
+
+task :clean do
+ system "rm *.gem &> /dev/null"
+end
+
27 examples/msfrpc_irb.rb
@@ -0,0 +1,27 @@
+#!/usr/bin/env ruby
+
+require 'rubygems'
+require 'optparse'
+require 'msfrpc-client'
+require 'rex/ui'
+
+
+# Use the RPC option parser to handle standard flags
+opts = {}
+parser = Msf::RPC::Client.option_parser(opts)
+parser.parse!(ARGV)
+
+# Parse additional options, environment variables, etc
+opts = Msf::RPC::Client.option_handler(opts)
+
+# Create the RPC client with our parsed options
+rpc = Msf::RPC::Client.new(opts)
+
+$stdout.puts "[*] The RPC client is available in variable 'rpc'"
+if rpc.token
+ $stdout.puts "[*] Sucessfully authenticated to the server"
+end
+
+$stdout.puts "[*] Starting IRB shell..."
+Rex::Ui::Text::IrbShell.new(binding).run
+
126 examples/msfrpc_pro_report.rb
@@ -0,0 +1,126 @@
+#!/usr/bin/env ruby
+
+require 'rubygems'
+require 'optparse'
+require 'msfrpc-client'
+require 'rex/ui'
+
+def usage(ropts)
+ $stderr.puts ropts
+
+ if @rpc and @rpc.token
+ wspaces = @rpc.call("pro.workspaces") rescue {}
+ if wspaces.keys.length > 0
+ $stderr.puts "Active Projects:"
+ wspaces.each_pair do |k,v|
+ $stderr.puts "\t#{k}"
+ end
+ end
+ end
+ $stderr.puts ""
+ exit(1)
+end
+
+opts = {
+ :format => 'PDF'
+}
+
+parser = Msf::RPC::Client.option_parser(opts)
+
+parser.separator('Report Options:')
+parser.on("--format FORMAT") do |v|
+ opts[:format] = v.upcase
+end
+
+parser.on("--project PROJECT") do |v|
+ opts[:project] = v
+end
+
+parser.on("--output OUTFILE") do |v|
+ opts[:output] = v
+end
+
+parser.on("--help") do
+ $stderr.puts ropts
+ exit(1)
+end
+parser.separator('')
+
+parser.parse!(ARGV)
+@rpc = Msf::RPC::Client.new(opts)
+
+if not @rpc.token
+ $stderr.puts "Error: Invalid RPC server options specified"
+ $stderr.puts parser
+ exit(1)
+end
+
+wspace = opts[:project] || usage(parser)
+fname = opts[:output] || usage(parser)
+rtype = opts[:format]
+user = @rpc.call("pro.default_admin_user")['username']
+
+task = @rpc.call("pro.start_report", {
+ 'DS_WHITELIST_HOSTS' => "",
+ 'DS_BLACKLIST_HOSTS' => "",
+ 'workspace' => wspace,
+ 'username' => user,
+ 'DS_MaskPasswords' => false,
+ 'DS_IncludeTaskLog' => false,
+ 'DS_JasperDisplaySession' => true,
+ 'DS_JasperDisplayCharts' => true,
+ 'DS_LootExcludeScreenshots' => false,
+ 'DS_LootExcludePasswords' => false,
+ 'DS_JasperTemplate' => "msfxv3.jrxml",
+ 'DS_REPORT_TYPE' => rtype.upcase,
+ 'DS_UseJasper' => true,
+ 'DS_UseCustomReporting' => true,
+ 'DS_JasperProductName' => "Metasploit Pro",
+ 'DS_JasperDbEnv' => "production",
+ 'DS_JasperLogo' => '',
+ 'DS_JasperDisplaySections' => "1,2,3,4,5,6,7,8",
+ 'DS_EnablePCIReport' => true,
+ 'DS_EnableFISMAReport' => true,
+ 'DS_JasperDisplayWeb' => true,
+})
+
+
+if not task['task_id']
+ $stderr.puts "[-] Error generating the report: #{task.inspect}"
+ exit(0)
+end
+
+puts "[*] Report is generating with Task ID #{task['task_id']}..."
+while true
+ select(nil, nil, nil, 0.50)
+ stat = @rpc.call("pro.task_status", task['task_id'])
+ if stat['status'] == 'invalid'
+ $stderr.puts "[-] Error checking task status"
+ exit(0)
+ end
+
+ info = stat[ task['task_id'] ]
+
+ if not info
+ $stderr.puts "[-] Error finding the task"
+ exit(0)
+ end
+
+ if info['status'] == "error"
+ $stderr.puts "[-] Error generating report: #{info['error']}"
+ exit(0)
+ end
+
+ break if info['progress'] == 100
+end
+
+report = @rpc.call('pro.report_download_by_task', task['task_id'])
+if report and report['data']
+ ::File.open(fname, "wb") do |fd|
+ fd.write(report['data'])
+ end
+ $stderr.puts "[-] Report saved to #{::File.expand_path(fname)}"
+else
+ $stderr.puts "[-] Error downloading report: #{report.inspect}"
+end
+
2  lib/msfrpc-client.rb
@@ -0,0 +1,2 @@
+require 'msfrpc-client/client'
+
214 lib/msfrpc-client/client.rb
@@ -0,0 +1,214 @@
+# MessagePack for data encoding (http://www.msgpack.org/)
+require 'msgpack'
+
+# Standardize option parsing
+require "optparse"
+
+# Parse configuration file
+require 'yaml'
+
+# Rex library from the Metasploit Framework
+require 'rex'
+require 'rex/proto/http'
+
+# Constants used by this client
+require 'msfrpc-client/constants'
+
+module Msf
+module RPC
+
+class Client
+
+ attr_accessor :token, :info
+
+ #
+ # Create a new RPC Client instance
+ #
+ def initialize(info={})
+ self.info = {
+ :host => '127.0.0.1',
+ :port => 3790,
+ :uri => '/api/',
+ :ssl => true,
+ :ssl_version => 'SSLv3',
+ :context => {}
+ }.merge(info)
+
+ info[:port] = info[:port].to_i
+
+ self.token = self.info[:token]
+
+ if not self.token and (info[:user] and info[:pass])
+ login(info[:user], info[:pass])
+ end
+ end
+
+ #
+ # Authenticate using a username and password
+ #
+ def login(user,pass)
+ res = self.call("auth.login", user, pass)
+ if(not (res and res['result'] == "success"))
+ raise RuntimeError, "authentication failed"
+ end
+ self.token = res['token']
+ true
+ end
+
+ #
+ # Prepend the authentication token as the first parameter
+ # of every call except auth.login. This simplifies the
+ # calling API.
+ #
+ def call(meth, *args)
+ if(meth != "auth.login")
+ if(not self.token)
+ raise RuntimeError, "client not authenticated"
+ end
+ args.unshift(self.token)
+ end
+
+ args.unshift(meth)
+
+ if not @cli
+ @cli = Rex::Proto::Http::Client.new(info[:host], info[:port], info[:context], info[:ssl], info[:ssl_version])
+ @cli.set_config(
+ :vhost => info[:host],
+ :agent => "Metasploit Pro RPC Client/#{API_VERSION}",
+ :read_max_data => (1024*1024*512)
+ )
+ end
+
+ req = @cli.request_cgi(
+ 'method' => 'POST',
+ 'uri' => self.info[:uri],
+ 'ctype' => 'binary/message-pack',
+ 'data' => args.to_msgpack
+ )
+
+ res = @cli.send_recv(req)
+
+ if res and [200, 401, 403, 500].include?(res.code)
+ resp = MessagePack.unpack(res.body)
+
+ if resp and resp.kind_of?(::Hash) and resp['error'] == true
+ raise Msf::RPC::ServerException.new(res.code, resp['error_message'] || resp['error_string'], resp['error_class'], resp['error_backtrace'])
+ end
+
+ return resp
+ else
+ raise RuntimeError, res.inspect
+ end
+ end
+
+
+ #
+ # Class methods
+ #
+
+
+ #
+ # Provides a parser object that understands the
+ # RPC specific options
+ #
+ def self.option_parser(options)
+ parser = OptionParser.new
+
+ parser.banner = "Usage: #{$0} [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
+
+ #
+ # Load options from the command-line, environment.
+ # and any configuration files specified
+ #
+ 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 and yaml.kind_of?(::Hash) and yaml['options']
+ yaml['options'].each_pair do |k,v|
+ options[k.intern] = v
+ 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
+
+ if options[:port]
+ options[:port] = options[:port].to_i
+ end
+
+ if options[:ssl]
+ options[:ssl] = (options[:ssl] =~ /(1|Y|T)/i ? true : false )
+ end
+
+ options
+ end
+
+end
+end
+end
+
34 lib/msfrpc-client/constants.rb
@@ -0,0 +1,34 @@
+module Msf
+module RPC
+
+API_VERSION = "1.0"
+
+
+class Exception < RuntimeError
+ attr_accessor :code, :message
+
+ def initialize(code, message)
+ self.code = code
+ self.message = message
+ end
+end
+
+
+class ServerException < RuntimeError
+ attr_accessor :code, :error_message, :error_class, :error_backtrace
+
+ def initialize(code, error_message, error_class, error_backtrace)
+ self.code = code
+ self.error_message = error_message
+ self.error_class = error_class
+ self.error_backtrace = error_backtrace
+ end
+
+ def to_s
+ "#{self.error_class} #{self.error_message} #{self.error_backtrace}"
+ end
+end
+
+end
+end
+
35 msfrpc-client.gemspec
@@ -0,0 +1,35 @@
+# -*- encoding: utf-8 -*-
+
+Gem::Specification.new do |s|
+ s.name = %q{msfrpc-client}
+ s.version = "1.0.1"
+
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
+ s.authors = ["HD Moore"]
+ s.date = %q{2011-10-18}
+ s.description = %q{This gem provides a Ruby client API to access the Rapid7 Metasploit Pro RPC service.}
+ s.email = ["hdm@metasploit.com"]
+ s.extra_rdoc_files = ["README.markdown"]
+ s.files = ["Rakefile", "README.markdown", "lib/msfrpc-client/client.rb", "lib/msfrpc-client/constants.rb", "lib/msfrpc-client.rb", "examples/msfrpc_irb.rb", "examples/msfrpc_pro_report.rb"]
+ s.homepage = %q{http://www.metasploit.com/}
+ s.licenses = ["BSD"]
+ s.require_paths = ["lib"]
+ s.required_ruby_version = Gem::Requirement.new(">= 1.8.7")
+ s.rubygems_version = %q{1.4.2}
+ s.summary = %q{Ruby API for the Rapid7 Metasploit Pro RPC service}
+
+ if s.respond_to? :specification_version then
+ s.specification_version = 3
+
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
+ s.add_runtime_dependency(%q<msgpack>, [">= 0.4.5"])
+ s.add_runtime_dependency(%q<librex>, [">= 0.0.32"])
+ else
+ s.add_dependency(%q<msgpack>, [">= 0.4.5"])
+ s.add_dependency(%q<librex>, [">= 0.0.32"])
+ end
+ else
+ s.add_dependency(%q<msgpack>, [">= 0.4.5"])
+ s.add_dependency(%q<librex>, [">= 0.0.32"])
+ end
+end
Please sign in to comment.
Something went wrong with that request. Please try again.