msfrpc-client 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|