msfrpc-client 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.markdown +9 -0
- data/Rakefile +18 -0
- data/examples/msfrpc_irb.rb +27 -0
- data/examples/msfrpc_pro_report.rb +126 -0
- data/lib/msfrpc-client.rb +2 -0
- data/lib/msfrpc-client/client.rb +214 -0
- data/lib/msfrpc-client/constants.rb +34 -0
- metadata +107 -0
data/README.markdown
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
# Metasploit Pro RPC Client
|
2
|
+
|
3
|
+
This is the official Ruby client for the Metasploit Pro RPC service. Metasploit
|
4
|
+
Pro is a commercial penetration testing product provided by Rapid7. For more
|
5
|
+
information on Metasploit Pro, please visit the http://www.metasploit.com/ site.
|
6
|
+
|
7
|
+
# Credits
|
8
|
+
Rapid7 LLC
|
9
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
task :build => :update do
|
4
|
+
Rake::Task['clean'].execute
|
5
|
+
puts "[*] Building msfrpc-client.gemspec"
|
6
|
+
system "gem build msfrpc-client.gemspec &> /dev/null"
|
7
|
+
end
|
8
|
+
|
9
|
+
task :release => :build do
|
10
|
+
puts "[*] Pushing msfrpc-client to rubygems.org"
|
11
|
+
system "gem push msfrpc-client-*.gem &> /dev/null"
|
12
|
+
Rake::Task['clean'].execute
|
13
|
+
end
|
14
|
+
|
15
|
+
task :clean do
|
16
|
+
system "rm *.gem &> /dev/null"
|
17
|
+
end
|
18
|
+
|
@@ -0,0 +1,27 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'optparse'
|
5
|
+
require 'msfrpc-client'
|
6
|
+
require 'rex/ui'
|
7
|
+
|
8
|
+
|
9
|
+
# Use the RPC option parser to handle standard flags
|
10
|
+
opts = {}
|
11
|
+
parser = Msf::RPC::Client.option_parser(opts)
|
12
|
+
parser.parse!(ARGV)
|
13
|
+
|
14
|
+
# Parse additional options, environment variables, etc
|
15
|
+
opts = Msf::RPC::Client.option_handler(opts)
|
16
|
+
|
17
|
+
# Create the RPC client with our parsed options
|
18
|
+
rpc = Msf::RPC::Client.new(opts)
|
19
|
+
|
20
|
+
$stdout.puts "[*] The RPC client is available in variable 'rpc'"
|
21
|
+
if rpc.token
|
22
|
+
$stdout.puts "[*] Sucessfully authenticated to the server"
|
23
|
+
end
|
24
|
+
|
25
|
+
$stdout.puts "[*] Starting IRB shell..."
|
26
|
+
Rex::Ui::Text::IrbShell.new(binding).run
|
27
|
+
|
@@ -0,0 +1,126 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'optparse'
|
5
|
+
require 'msfrpc-client'
|
6
|
+
require 'rex/ui'
|
7
|
+
|
8
|
+
def usage(ropts)
|
9
|
+
$stderr.puts ropts
|
10
|
+
|
11
|
+
if @rpc and @rpc.token
|
12
|
+
wspaces = @rpc.call("pro.workspaces") rescue {}
|
13
|
+
if wspaces.keys.length > 0
|
14
|
+
$stderr.puts "Active Projects:"
|
15
|
+
wspaces.each_pair do |k,v|
|
16
|
+
$stderr.puts "\t#{k}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
$stderr.puts ""
|
21
|
+
exit(1)
|
22
|
+
end
|
23
|
+
|
24
|
+
opts = {
|
25
|
+
:format => 'PDF'
|
26
|
+
}
|
27
|
+
|
28
|
+
parser = Msf::RPC::Client.option_parser(opts)
|
29
|
+
|
30
|
+
parser.separator('Report Options:')
|
31
|
+
parser.on("--format FORMAT") do |v|
|
32
|
+
opts[:format] = v.upcase
|
33
|
+
end
|
34
|
+
|
35
|
+
parser.on("--project PROJECT") do |v|
|
36
|
+
opts[:project] = v
|
37
|
+
end
|
38
|
+
|
39
|
+
parser.on("--output OUTFILE") do |v|
|
40
|
+
opts[:output] = v
|
41
|
+
end
|
42
|
+
|
43
|
+
parser.on("--help") do
|
44
|
+
$stderr.puts ropts
|
45
|
+
exit(1)
|
46
|
+
end
|
47
|
+
parser.separator('')
|
48
|
+
|
49
|
+
parser.parse!(ARGV)
|
50
|
+
@rpc = Msf::RPC::Client.new(opts)
|
51
|
+
|
52
|
+
if not @rpc.token
|
53
|
+
$stderr.puts "Error: Invalid RPC server options specified"
|
54
|
+
$stderr.puts parser
|
55
|
+
exit(1)
|
56
|
+
end
|
57
|
+
|
58
|
+
wspace = opts[:project] || usage(parser)
|
59
|
+
fname = opts[:output] || usage(parser)
|
60
|
+
rtype = opts[:format]
|
61
|
+
user = @rpc.call("pro.default_admin_user")['username']
|
62
|
+
|
63
|
+
task = @rpc.call("pro.start_report", {
|
64
|
+
'DS_WHITELIST_HOSTS' => "",
|
65
|
+
'DS_BLACKLIST_HOSTS' => "",
|
66
|
+
'workspace' => wspace,
|
67
|
+
'username' => user,
|
68
|
+
'DS_MaskPasswords' => false,
|
69
|
+
'DS_IncludeTaskLog' => false,
|
70
|
+
'DS_JasperDisplaySession' => true,
|
71
|
+
'DS_JasperDisplayCharts' => true,
|
72
|
+
'DS_LootExcludeScreenshots' => false,
|
73
|
+
'DS_LootExcludePasswords' => false,
|
74
|
+
'DS_JasperTemplate' => "msfxv3.jrxml",
|
75
|
+
'DS_REPORT_TYPE' => rtype.upcase,
|
76
|
+
'DS_UseJasper' => true,
|
77
|
+
'DS_UseCustomReporting' => true,
|
78
|
+
'DS_JasperProductName' => "Metasploit Pro",
|
79
|
+
'DS_JasperDbEnv' => "production",
|
80
|
+
'DS_JasperLogo' => '',
|
81
|
+
'DS_JasperDisplaySections' => "1,2,3,4,5,6,7,8",
|
82
|
+
'DS_EnablePCIReport' => true,
|
83
|
+
'DS_EnableFISMAReport' => true,
|
84
|
+
'DS_JasperDisplayWeb' => true,
|
85
|
+
})
|
86
|
+
|
87
|
+
|
88
|
+
if not task['task_id']
|
89
|
+
$stderr.puts "[-] Error generating the report: #{task.inspect}"
|
90
|
+
exit(0)
|
91
|
+
end
|
92
|
+
|
93
|
+
puts "[*] Report is generating with Task ID #{task['task_id']}..."
|
94
|
+
while true
|
95
|
+
select(nil, nil, nil, 0.50)
|
96
|
+
stat = @rpc.call("pro.task_status", task['task_id'])
|
97
|
+
if stat['status'] == 'invalid'
|
98
|
+
$stderr.puts "[-] Error checking task status"
|
99
|
+
exit(0)
|
100
|
+
end
|
101
|
+
|
102
|
+
info = stat[ task['task_id'] ]
|
103
|
+
|
104
|
+
if not info
|
105
|
+
$stderr.puts "[-] Error finding the task"
|
106
|
+
exit(0)
|
107
|
+
end
|
108
|
+
|
109
|
+
if info['status'] == "error"
|
110
|
+
$stderr.puts "[-] Error generating report: #{info['error']}"
|
111
|
+
exit(0)
|
112
|
+
end
|
113
|
+
|
114
|
+
break if info['progress'] == 100
|
115
|
+
end
|
116
|
+
|
117
|
+
report = @rpc.call('pro.report_download_by_task', task['task_id'])
|
118
|
+
if report and report['data']
|
119
|
+
::File.open(fname, "wb") do |fd|
|
120
|
+
fd.write(report['data'])
|
121
|
+
end
|
122
|
+
$stderr.puts "[-] Report saved to #{::File.expand_path(fname)}"
|
123
|
+
else
|
124
|
+
$stderr.puts "[-] Error downloading report: #{report.inspect}"
|
125
|
+
end
|
126
|
+
|
@@ -0,0 +1,214 @@
|
|
1
|
+
# MessagePack for data encoding (http://www.msgpack.org/)
|
2
|
+
require 'msgpack'
|
3
|
+
|
4
|
+
# Standardize option parsing
|
5
|
+
require "optparse"
|
6
|
+
|
7
|
+
# Parse configuration file
|
8
|
+
require 'yaml'
|
9
|
+
|
10
|
+
# Rex library from the Metasploit Framework
|
11
|
+
require 'rex'
|
12
|
+
require 'rex/proto/http'
|
13
|
+
|
14
|
+
# Constants used by this client
|
15
|
+
require 'msfrpc-client/constants'
|
16
|
+
|
17
|
+
module Msf
|
18
|
+
module RPC
|
19
|
+
|
20
|
+
class Client
|
21
|
+
|
22
|
+
attr_accessor :token, :info
|
23
|
+
|
24
|
+
#
|
25
|
+
# Create a new RPC Client instance
|
26
|
+
#
|
27
|
+
def initialize(info={})
|
28
|
+
self.info = {
|
29
|
+
:host => '127.0.0.1',
|
30
|
+
:port => 3790,
|
31
|
+
:uri => '/api',
|
32
|
+
:ssl => true,
|
33
|
+
:ssl_version => 'SSLv3',
|
34
|
+
:context => {}
|
35
|
+
}.merge(info)
|
36
|
+
|
37
|
+
info[:port] = info[:port].to_i
|
38
|
+
|
39
|
+
self.token = self.info[:token]
|
40
|
+
|
41
|
+
if not self.token and (info[:user] and info[:pass])
|
42
|
+
login(info[:user], info[:pass])
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
#
|
47
|
+
# Authenticate using a username and password
|
48
|
+
#
|
49
|
+
def login(user,pass)
|
50
|
+
res = self.call("auth.login", user, pass)
|
51
|
+
if(not (res and res['result'] == "success"))
|
52
|
+
raise RuntimeError, "authentication failed"
|
53
|
+
end
|
54
|
+
self.token = res['token']
|
55
|
+
true
|
56
|
+
end
|
57
|
+
|
58
|
+
#
|
59
|
+
# Prepend the authentication token as the first parameter
|
60
|
+
# of every call except auth.login. This simplifies the
|
61
|
+
# calling API.
|
62
|
+
#
|
63
|
+
def call(meth, *args)
|
64
|
+
if(meth != "auth.login")
|
65
|
+
if(not self.token)
|
66
|
+
raise RuntimeError, "client not authenticated"
|
67
|
+
end
|
68
|
+
args.unshift(self.token)
|
69
|
+
end
|
70
|
+
|
71
|
+
args.unshift(meth)
|
72
|
+
|
73
|
+
if not @cli
|
74
|
+
@cli = Rex::Proto::Http::Client.new(info[:host], info[:port], info[:context], info[:ssl], info[:ssl_version])
|
75
|
+
@cli.set_config(
|
76
|
+
:vhost => info[:host],
|
77
|
+
:agent => "Metasploit Pro RPC Client/#{API_VERSION}",
|
78
|
+
:read_max_data => (1024*1024*512)
|
79
|
+
)
|
80
|
+
end
|
81
|
+
|
82
|
+
req = @cli.request_cgi(
|
83
|
+
'method' => 'POST',
|
84
|
+
'uri' => self.info[:uri],
|
85
|
+
'ctype' => 'binary/message-pack',
|
86
|
+
'data' => args.to_msgpack
|
87
|
+
)
|
88
|
+
|
89
|
+
res = @cli.send_recv(req)
|
90
|
+
|
91
|
+
if res and [200, 401, 403, 500].include?(res.code)
|
92
|
+
resp = MessagePack.unpack(res.body)
|
93
|
+
|
94
|
+
if resp and resp.kind_of?(::Hash) and resp['error'] == true
|
95
|
+
raise Msf::RPC::ServerException.new(res.code, resp['error_message'] || resp['error_string'], resp['error_class'], resp['error_backtrace'])
|
96
|
+
end
|
97
|
+
|
98
|
+
return resp
|
99
|
+
else
|
100
|
+
raise RuntimeError, res.inspect
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
#
|
106
|
+
# Class methods
|
107
|
+
#
|
108
|
+
|
109
|
+
|
110
|
+
#
|
111
|
+
# Provides a parser object that understands the
|
112
|
+
# RPC specific options
|
113
|
+
#
|
114
|
+
def self.option_parser(options)
|
115
|
+
parser = OptionParser.new
|
116
|
+
|
117
|
+
parser.banner = "Usage: #{$0} [options]"
|
118
|
+
parser.separator('')
|
119
|
+
parser.separator('RPC Options:')
|
120
|
+
|
121
|
+
parser.on("--rpc-host HOST") do |v|
|
122
|
+
options[:host] = v
|
123
|
+
end
|
124
|
+
|
125
|
+
parser.on("--rpc-port PORT") do |v|
|
126
|
+
options[:port] = v.to_i
|
127
|
+
end
|
128
|
+
|
129
|
+
parser.on("--rpc-ssl <true|false>") do |v|
|
130
|
+
options[:ssl] = v
|
131
|
+
end
|
132
|
+
|
133
|
+
parser.on("--rpc-uri URI") do |v|
|
134
|
+
options[:uri] = v
|
135
|
+
end
|
136
|
+
|
137
|
+
parser.on("--rpc-user USERNAME") do |v|
|
138
|
+
options[:user] = v
|
139
|
+
end
|
140
|
+
|
141
|
+
parser.on("--rpc-pass PASSWORD") do |v|
|
142
|
+
options[:pass] = v
|
143
|
+
end
|
144
|
+
|
145
|
+
parser.on("--rpc-token TOKEN") do |v|
|
146
|
+
options[:token] = v
|
147
|
+
end
|
148
|
+
|
149
|
+
parser.on("--rpc-config CONFIG-FILE") do |v|
|
150
|
+
options[:config] = v
|
151
|
+
end
|
152
|
+
|
153
|
+
parser.on("--rpc-help") do
|
154
|
+
$stderr.puts parser
|
155
|
+
exit(1)
|
156
|
+
end
|
157
|
+
|
158
|
+
parser.separator('')
|
159
|
+
|
160
|
+
parser
|
161
|
+
end
|
162
|
+
|
163
|
+
#
|
164
|
+
# Load options from the command-line, environment.
|
165
|
+
# and any configuration files specified
|
166
|
+
#
|
167
|
+
def self.option_handler(options={})
|
168
|
+
options[:host] ||= ENV['MSFRPC_HOST']
|
169
|
+
options[:port] ||= ENV['MSFRPC_PORT']
|
170
|
+
options[:uri] ||= ENV['MSFRPC_URI']
|
171
|
+
options[:user] ||= ENV['MSFRPC_USER']
|
172
|
+
options[:pass] ||= ENV['MSFRPC_PASS']
|
173
|
+
options[:ssl] ||= ENV['MSFRPC_SSL']
|
174
|
+
options[:token] ||= ENV['MSFRPC_TOKEN']
|
175
|
+
options[:config] ||= ENV['MSFRPC_CONFIG']
|
176
|
+
|
177
|
+
empty_keys = options.keys.select{|k| options[k].nil? }
|
178
|
+
empty_keys.each { |k| options.delete(k) }
|
179
|
+
|
180
|
+
config_file = options.delete(:config)
|
181
|
+
|
182
|
+
if config_file
|
183
|
+
yaml_data = ::File.read(config_file) rescue nil
|
184
|
+
if yaml_data
|
185
|
+
yaml = ::YAML.load(yaml_data) rescue nil
|
186
|
+
if yaml and yaml.kind_of?(::Hash) and yaml['options']
|
187
|
+
yaml['options'].each_pair do |k,v|
|
188
|
+
options[k.intern] = v
|
189
|
+
end
|
190
|
+
else
|
191
|
+
$stderr.puts "[-] Could not parse configuration file: #{config_file}"
|
192
|
+
exit(1)
|
193
|
+
end
|
194
|
+
else
|
195
|
+
$stderr.puts "[-] Could not read configuration file: #{config_file}"
|
196
|
+
exit(1)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
if options[:port]
|
201
|
+
options[:port] = options[:port].to_i
|
202
|
+
end
|
203
|
+
|
204
|
+
if options[:ssl]
|
205
|
+
options[:ssl] = (options[:ssl] =~ /(1|Y|T)/i ? true : false )
|
206
|
+
end
|
207
|
+
|
208
|
+
options
|
209
|
+
end
|
210
|
+
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Msf
|
2
|
+
module RPC
|
3
|
+
|
4
|
+
API_VERSION = "1.0"
|
5
|
+
|
6
|
+
|
7
|
+
class Exception < RuntimeError
|
8
|
+
attr_accessor :code, :message
|
9
|
+
|
10
|
+
def initialize(code, message)
|
11
|
+
self.code = code
|
12
|
+
self.message = message
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
class ServerException < RuntimeError
|
18
|
+
attr_accessor :code, :error_message, :error_class, :error_backtrace
|
19
|
+
|
20
|
+
def initialize(code, error_message, error_class, error_backtrace)
|
21
|
+
self.code = code
|
22
|
+
self.error_message = error_message
|
23
|
+
self.error_class = error_class
|
24
|
+
self.error_backtrace = error_backtrace
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_s
|
28
|
+
"#{self.error_class} #{self.error_message} #{self.error_backtrace}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
metadata
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: msfrpc-client
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 23
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
- 0
|
10
|
+
version: 1.0.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- HD Moore
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-07-23 00:00:00 -05:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: msgpack
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 5
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
- 4
|
33
|
+
- 5
|
34
|
+
version: 0.4.5
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: librex
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 95
|
46
|
+
segments:
|
47
|
+
- 0
|
48
|
+
- 0
|
49
|
+
- 32
|
50
|
+
version: 0.0.32
|
51
|
+
type: :runtime
|
52
|
+
version_requirements: *id002
|
53
|
+
description: This gem provides a Ruby client API to access the Rapid7 Metasploit Pro RPC service.
|
54
|
+
email:
|
55
|
+
- hdm@metasploit.com
|
56
|
+
executables: []
|
57
|
+
|
58
|
+
extensions: []
|
59
|
+
|
60
|
+
extra_rdoc_files:
|
61
|
+
- README.markdown
|
62
|
+
files:
|
63
|
+
- Rakefile
|
64
|
+
- README.markdown
|
65
|
+
- lib/msfrpc-client/client.rb
|
66
|
+
- lib/msfrpc-client/constants.rb
|
67
|
+
- lib/msfrpc-client.rb
|
68
|
+
- examples/msfrpc_irb.rb
|
69
|
+
- examples/msfrpc_pro_report.rb
|
70
|
+
has_rdoc: true
|
71
|
+
homepage: http://www.metasploit.com/
|
72
|
+
licenses:
|
73
|
+
- BSD
|
74
|
+
post_install_message:
|
75
|
+
rdoc_options: []
|
76
|
+
|
77
|
+
require_paths:
|
78
|
+
- lib
|
79
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
80
|
+
none: false
|
81
|
+
requirements:
|
82
|
+
- - ">="
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
hash: 57
|
85
|
+
segments:
|
86
|
+
- 1
|
87
|
+
- 8
|
88
|
+
- 7
|
89
|
+
version: 1.8.7
|
90
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
91
|
+
none: false
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
hash: 3
|
96
|
+
segments:
|
97
|
+
- 0
|
98
|
+
version: "0"
|
99
|
+
requirements: []
|
100
|
+
|
101
|
+
rubyforge_project:
|
102
|
+
rubygems_version: 1.4.2
|
103
|
+
signing_key:
|
104
|
+
specification_version: 3
|
105
|
+
summary: Ruby API for the Rapid7 Metasploit Pro RPC service
|
106
|
+
test_files: []
|
107
|
+
|