msfrpc-client 1.1.0 → 1.1.1

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.
@@ -0,0 +1,91 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+ require 'optparse'
4
+ require 'msfrpc-client'
5
+ require 'rex/ui'
6
+
7
+ def usage(ropts)
8
+ $stderr.puts ropts
9
+
10
+ if @rpc and @rpc.token
11
+ wspaces = @rpc.call("pro.workspaces") rescue {}
12
+ if wspaces.keys.length > 0
13
+ $stderr.puts "Active Projects:"
14
+ wspaces.each_pair do |k,v|
15
+ $stderr.puts "\t#{k}"
16
+ end
17
+ end
18
+ end
19
+ exit(1)
20
+ end
21
+
22
+ opts = {}
23
+
24
+ # Parse script-specific options
25
+ parser = Msf::RPC::Client.option_parser(opts)
26
+ parser.separator('Task Options:')
27
+
28
+ parser.on("--path PATH") do |path|
29
+ opts[:path] = path
30
+ end
31
+
32
+ parser.on("--project PROJECT") do |project|
33
+ opts[:project] = project
34
+ end
35
+
36
+ parser.on("--help") do
37
+ $stderr.puts parser
38
+ exit(1)
39
+ end
40
+ parser.separator('')
41
+
42
+ parser.parse!(ARGV)
43
+ @rpc = Msf::RPC::Client.new(opts)
44
+
45
+ if not @rpc.token
46
+ $stderr.puts "Error: Invalid RPC server options specified"
47
+ $stderr.puts parser
48
+ exit(1)
49
+ end
50
+
51
+ project = opts[:project] || usage(parser)
52
+ path = opts[:path] || usage(parser)
53
+ user = @rpc.call("pro.default_admin_user")['username']
54
+ task = @rpc.call("pro.start_import", {
55
+ 'workspace' => project,
56
+ 'username' => user,
57
+ 'DS_PATH' => path
58
+ })
59
+
60
+ if not task['task_id']
61
+ $stderr.puts "[-] Error starting the task: #{task.inspect}"
62
+ exit(0)
63
+ end
64
+
65
+ puts "[*] Creating Task ID #{task['task_id']}..."
66
+ while true
67
+ select(nil, nil, nil, 0.50)
68
+
69
+ stat = @rpc.call("pro.task_status", task['task_id'])
70
+
71
+ if stat['status'] == 'invalid'
72
+ $stderr.puts "[-] Error checking task status"
73
+ exit(0)
74
+ end
75
+
76
+ info = stat[ task['task_id'] ]
77
+
78
+ if not info
79
+ $stderr.puts "[-] Error finding the task"
80
+ exit(0)
81
+ end
82
+
83
+ if info['status'] == "error"
84
+ $stderr.puts "[-] Error generating report: #{info['error']}"
85
+ exit(0)
86
+ end
87
+
88
+ break if info['progress'] == 100
89
+ end
90
+
91
+ $stdout.puts "[+] Task Complete!"
@@ -0,0 +1,148 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+ require 'optparse'
4
+ require 'msfrpc-client'
5
+ require 'rex/ui'
6
+
7
+ def usage(ropts)
8
+ $stderr.puts ropts
9
+
10
+ if @rpc and @rpc.token
11
+ wspaces = @rpc.call("pro.workspaces") rescue {}
12
+ if wspaces.keys.length > 0
13
+ $stderr.puts "Active Projects:"
14
+ wspaces.each_pair do |k,v|
15
+ $stderr.puts "\t#{k}"
16
+ end
17
+ end
18
+ end
19
+ $stderr.puts ""
20
+ exit(1)
21
+ end
22
+
23
+ opts = {}
24
+
25
+ # Parse script-specific options
26
+ parser = Msf::RPC::Client.option_parser(opts)
27
+ parser.separator('NeXpose Specific Options:')
28
+
29
+ parser.on("--project PROJECT") do |x|
30
+ opts[:project] = x
31
+ end
32
+
33
+ parser.on("--targets TARGETS") do |x|
34
+ opts[:targets] = [x]
35
+ end
36
+
37
+ parser.on("--nexpose-host HOST") do |x|
38
+ opts[:nexpose_host] = x
39
+ end
40
+
41
+ parser.on("--nexpose-user USER") do |x|
42
+ opts[:nexpose_user] = x
43
+ end
44
+
45
+ parser.on("--nexpose-pass PASSWORD") do |x|
46
+ opts[:nexpose_pass] = x
47
+ end
48
+
49
+ parser.on("--nexpose-pass-file PATH") do |x|
50
+ opts[:nexpose_pass_file] = x
51
+ end
52
+
53
+ parser.on("--scan-template TEMPLATE (optional)") do |x|
54
+ opts[:scan_template] = x
55
+ end
56
+
57
+ parser.on("--nexpose-port PORT (optional)") do |x|
58
+ opts[:nexpose_port] = x
59
+ end
60
+
61
+ parser.on("--blacklist BLACKLIST (optional)") do |x|
62
+ opts[:blacklist] = x
63
+ end
64
+
65
+ parser.on("--help") do
66
+ $stderr.puts parser
67
+ exit(1)
68
+ end
69
+
70
+ parser.separator('')
71
+ parser.parse!(ARGV)
72
+
73
+ @rpc = Msf::RPC::Client.new(opts)
74
+
75
+ if not @rpc.token
76
+ $stderr.puts "Error: Invalid RPC server options specified"
77
+ $stderr.puts parser
78
+ exit(1)
79
+ end
80
+
81
+ # Get the password from the file
82
+ if opts[:nexpose_pass_file]
83
+ nexpose_pass = File.open(opts[:nexpose_pass_file],"r").read.chomp!
84
+ else
85
+ nexpose_pass = opts[:nexpose_pass] || usage(parser)
86
+ end
87
+
88
+ # Store the user's settings
89
+ project = opts[:project] || usage(parser),
90
+ targets = opts[:targets] || usage(parser),
91
+ blacklist = opts[:blacklist],
92
+ nexpose_host = opts[:nexpose_host] || usage(parser),
93
+ nexpose_port = opts[:nexpose_port] || "3780",
94
+ nexpose_user = opts[:nexpose_user] || "nxadmin"
95
+ scan_template = opts[:scan_template] || "pentest-audit"
96
+
97
+ # Get the default user
98
+ user = @rpc.call("pro.default_admin_user")['username']
99
+
100
+ options = {
101
+ 'workspace' => project,
102
+ 'username' => user,
103
+ 'DS_WHITELIST_HOSTS' => targets,
104
+ 'DS_NEXPOSE_HOST' => nexpose_host,
105
+ 'DS_NEXPOSE_PORT' => nexpose_port,
106
+ 'DS_NEXPOSE_USER' => nexpose_user,
107
+ 'nexpose_pass' => nexpose_pass,
108
+ 'DS_SCAN_TEMPLATE' => scan_template
109
+ }
110
+
111
+ puts "DEBUG: Running task with #{options}"
112
+
113
+ # Create the task object with all options
114
+ task = @rpc.call("pro.start_exploit", options)
115
+
116
+
117
+ if not task['task_id']
118
+ $stderr.puts "[-] Error starting the task: #{task.inspect}"
119
+ exit(0)
120
+ end
121
+
122
+ puts "[*] Creating Task ID #{task['task_id']}..."
123
+ while true
124
+ select(nil, nil, nil, 0.50)
125
+
126
+ stat = @rpc.call("pro.task_status", task['task_id'])
127
+
128
+ if stat['status'] == 'invalid'
129
+ $stderr.puts "[-] Error checking task status"
130
+ exit(0)
131
+ end
132
+
133
+ info = stat[ task['task_id'] ]
134
+
135
+ if not info
136
+ $stderr.puts "[-] Error finding the task"
137
+ exit(0)
138
+ end
139
+
140
+ if info['status'] == "error"
141
+ $stderr.puts "[-] Error generating report: #{info['error']}"
142
+ exit(0)
143
+ end
144
+
145
+ break if info['progress'] == 100
146
+ end
147
+
148
+ $stdout.puts "[+] Task Complete!"
@@ -6,43 +6,43 @@ require 'msfrpc-client'
6
6
  require 'rex/ui'
7
7
 
8
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)
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
22
  end
23
23
 
24
24
  opts = {
25
- :format => 'PDF'
25
+ :format => 'PDF'
26
26
  }
27
27
 
28
28
  parser = Msf::RPC::Client.option_parser(opts)
29
29
 
30
30
  parser.separator('Report Options:')
31
31
  parser.on("--format FORMAT") do |v|
32
- opts[:format] = v.upcase
32
+ opts[:format] = v.upcase
33
33
  end
34
34
 
35
35
  parser.on("--project PROJECT") do |v|
36
- opts[:project] = v
36
+ opts[:project] = v
37
37
  end
38
38
 
39
39
  parser.on("--output OUTFILE") do |v|
40
- opts[:output] = v
40
+ opts[:output] = v
41
41
  end
42
42
 
43
43
  parser.on("--help") do
44
- $stderr.puts ropts
45
- exit(1)
44
+ $stderr.puts ropts
45
+ exit(1)
46
46
  end
47
47
  parser.separator('')
48
48
 
@@ -50,9 +50,9 @@ parser.parse!(ARGV)
50
50
  @rpc = Msf::RPC::Client.new(opts)
51
51
 
52
52
  if not @rpc.token
53
- $stderr.puts "Error: Invalid RPC server options specified"
54
- $stderr.puts parser
55
- exit(1)
53
+ $stderr.puts "Error: Invalid RPC server options specified"
54
+ $stderr.puts parser
55
+ exit(1)
56
56
  end
57
57
 
58
58
  wspace = opts[:project] || usage(parser)
@@ -61,66 +61,66 @@ rtype = opts[:format]
61
61
  user = @rpc.call("pro.default_admin_user")['username']
62
62
 
63
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,
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
85
  })
86
86
 
87
87
 
88
88
  if not task['task_id']
89
- $stderr.puts "[-] Error generating the report: #{task.inspect}"
90
- exit(0)
89
+ $stderr.puts "[-] Error generating the report: #{task.inspect}"
90
+ exit(0)
91
91
  end
92
92
 
93
93
  puts "[*] Report is generating with Task ID #{task['task_id']}..."
94
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
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
115
  end
116
116
 
117
117
  report = @rpc.call('pro.report_download_by_task', task['task_id'])
118
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)}"
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
123
  else
124
- $stderr.puts "[-] Error downloading report: #{report.inspect}"
124
+ $stderr.puts "[-] Error downloading report: #{report.inspect}"
125
125
  end
126
126
 
@@ -1,8 +1,10 @@
1
+ # -*- coding: binary -*-
2
+
1
3
  # MessagePack for data encoding (http://www.msgpack.org/)
2
4
  require 'msgpack'
3
5
 
4
6
  # Standardize option parsing
5
- require "optparse"
7
+ require 'optparse'
6
8
 
7
9
  # Parse configuration file
8
10
  require 'yaml'
@@ -15,207 +17,291 @@ require 'rex/proto/http'
15
17
  require 'msfrpc-client/constants'
16
18
 
17
19
  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(config={})
28
-
29
- self.info = {
30
- :host => '127.0.0.1',
31
- :port => 3790,
32
- :uri => '/api/' + Msf::RPC::API_VERSION,
33
- :ssl => true,
34
- :ssl_version => 'TLS1',
35
- :context => {}
36
- }.merge(config)
37
-
38
- # Set the token
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
- case k
189
- when 'ssl'
190
- options[k.intern] = !!(v.to_s =~ /^(t|y|1)/i)
191
- when 'port'
192
- options[k.intern] = v.to_i
193
- else
194
- options[k.intern] = v
195
- end
196
- end
197
- else
198
- $stderr.puts "[-] Could not parse configuration file: #{config_file}"
199
- exit(1)
200
- end
201
- else
202
- $stderr.puts "[-] Could not read configuration file: #{config_file}"
203
- exit(1)
204
- end
205
- end
206
-
207
- if options[:port]
208
- options[:port] = options[:port].to_i
209
- end
210
-
211
- if options[:ssl]
212
- options[:ssl] = !!(options[:ssl].to_s =~ /^(t|y|1)/i)
213
- end
214
-
215
- options
216
- end
217
-
218
- end
20
+ module RPC
21
+ class Client
22
+ # @!attribute token
23
+ # @return [String] A login token.
24
+ attr_accessor :token
25
+
26
+ # @!attribute info
27
+ # @return [Hash] Login information.
28
+ attr_accessor :info
29
+
30
+ # Initializes the RPC client to connect to: https://127.0.0.1:3790 (TLS1)
31
+ # The connection information is overridden through the optional info hash.
32
+ #
33
+ # @param [Hash] info Information needed for the initialization.
34
+ # @option info [String] :token A token used by the client.
35
+ # @return [void]
36
+
37
+ def initialize(info = {})
38
+ @user = nil
39
+ @pass = nil
40
+
41
+ self.info = {
42
+ host: '127.0.0.1',
43
+ port: 3790,
44
+ uri: '/api/',
45
+ ssl: true,
46
+ ssl_version: 'TLS1.2',
47
+ context: {}
48
+ }.merge(info)
49
+
50
+ self.token = self.info[:token]
51
+ end
52
+
53
+ # Logs in by calling the 'auth.login' API. The authentication token will
54
+ # expire after 5 minutes, but will automatically be rewnewed when you
55
+ # make a new RPC request.
56
+ #
57
+ # @param [String] user Username.
58
+ # @param [String] pass Password.
59
+ # @raise RuntimeError Indicating a failed authentication.
60
+ # @return [TrueClass] Indicating a successful login.
61
+
62
+ def login(user, pass)
63
+ @user = user
64
+ @pass = pass
65
+ res = self.call('auth.login', user, pass)
66
+ unless res && res['result'] == 'success'
67
+ raise Msf::RPC::Exception.new('Authentication failed')
68
+ end
69
+ self.token = res['token']
70
+ true
71
+ end
72
+
73
+
74
+ # Attempts to login again with the last known user name and password.
75
+ #
76
+ # @return [TrueClass] Indicating a successful login.
77
+
78
+ def re_login
79
+ login(@user, @pass)
80
+ end
81
+
82
+
83
+ # Calls an API.
84
+ #
85
+ # @param [String] meth The RPC API to call.
86
+ # @param [Array<string>] args The arguments to pass.
87
+ # @raise [RuntimeError] Something is wrong while calling the remote API,
88
+ # including:
89
+ # * A missing token (your client needs to
90
+ # authenticate).
91
+ # * A unexpected response from the server, such as
92
+ # a timeout or unexpected HTTP code.
93
+ # @raise [Msf::RPC::ServerException] The RPC service returns an error.
94
+ # @return [Hash] The API response. It contains the following keys:
95
+ # * 'version' [String] Framework version.
96
+ # * 'ruby' [String] Ruby version.
97
+ # * 'api' [String] API version.
98
+ # @example
99
+ # # This will return something like this:
100
+ # # {"version"=>"4.11.0-dev",
101
+ # # "ruby"=>"2.1.5 x86_64-darwin14.0 2014-11-13", "api"=>"1.0"}
102
+ # rpc.call('core.version')
103
+
104
+ def call(meth, *args)
105
+ if meth == 'auth.logout'
106
+ do_logout_cleanup
107
+ end
108
+
109
+ unless meth == 'auth.login'
110
+ unless self.token
111
+ raise Msf::RPC::Exception.new('Client not authenticated')
112
+ end
113
+ args.unshift(self.token)
114
+ end
115
+
116
+ args.unshift(meth)
117
+
118
+ begin
119
+ send_rpc_request(args)
120
+ rescue Msf::RPC::ServerException => e
121
+ if e.message =~ /Invalid Authentication Token/i &&
122
+ meth != 'auth.login' && @user && @pass
123
+ re_login
124
+ args[1] = self.token
125
+ retry
126
+ else
127
+ raise e
128
+ end
129
+ ensure
130
+ @cli.close if @cli
131
+ end
132
+ end
133
+
134
+ # Closes the client.
135
+ #
136
+ # @return [void]
137
+ def close
138
+ if @cli && @cli.conn?
139
+ @cli.close
140
+ end
141
+ @cli = nil
142
+ end
143
+
144
+ #
145
+ # Class methods
146
+ #
147
+
148
+ #
149
+ # Provides a parser object that understands the
150
+ # RPC specific options
151
+ #
152
+ def self.option_parser(options)
153
+ parser = OptionParser.new
154
+
155
+ parser.banner = "Usage: #{$PROGRAM_NAME} [options]"
156
+ parser.separator('')
157
+ parser.separator('RPC Options:')
158
+
159
+ parser.on('--rpc-host HOST') do |v|
160
+ options[:host] = v
161
+ end
162
+
163
+ parser.on('--rpc-port PORT') do |v|
164
+ options[:port] = v.to_i
165
+ end
166
+
167
+ parser.on('--rpc-ssl <true|false>') do |v|
168
+ options[:ssl] = v
169
+ end
170
+
171
+ parser.on('--rpc-uri URI') do |v|
172
+ options[:uri] = v
173
+ end
174
+
175
+ parser.on('--rpc-user USERNAME') do |v|
176
+ options[:user] = v
177
+ end
178
+
179
+ parser.on('--rpc-pass PASSWORD') do |v|
180
+ options[:pass] = v
181
+ end
182
+
183
+ parser.on('--rpc-token TOKEN') do |v|
184
+ options[:token] = v
185
+ end
186
+
187
+ parser.on('--rpc-config CONFIG-FILE') do |v|
188
+ options[:config] = v
189
+ end
190
+
191
+ parser.on('--rpc-help') do
192
+ $stderr.puts parser
193
+ exit(1)
194
+ end
195
+
196
+ parser.separator('')
197
+
198
+ parser
199
+ end
200
+
201
+ #
202
+ # Load options from the command-line, environment.
203
+ # and any configuration files specified
204
+ #
205
+ def self.option_handler(options = {})
206
+ options[:host] ||= ENV['MSFRPC_HOST']
207
+ options[:port] ||= ENV['MSFRPC_PORT']
208
+ options[:uri] ||= ENV['MSFRPC_URI']
209
+ options[:user] ||= ENV['MSFRPC_USER']
210
+ options[:pass] ||= ENV['MSFRPC_PASS']
211
+ options[:ssl] ||= ENV['MSFRPC_SSL']
212
+ options[:token] ||= ENV['MSFRPC_TOKEN']
213
+ options[:config] ||= ENV['MSFRPC_CONFIG']
214
+
215
+ empty_keys = options.keys.select { |k| options[k].nil? }
216
+ empty_keys.each { |k| options.delete(k) }
217
+
218
+ config_file = options.delete(:config)
219
+
220
+ if config_file
221
+ yaml_data = ::File.read(config_file) rescue nil
222
+ if yaml_data
223
+ yaml = ::YAML.load(yaml_data) rescue nil
224
+ if yaml && yaml.is_a?(::Hash) && yaml['options']
225
+ yaml['options'].each_pair do |k, v|
226
+ case k
227
+ when 'ssl'
228
+ options[k.intern] = !!(v.to_s =~ /^(t|y|1)/i)
229
+ when 'port'
230
+ options[k.intern] = v.to_i
231
+ else
232
+ options[k.intern] = v
233
+ end
234
+ end
235
+ else
236
+ $stderr.puts "Could not parse configuration file: #{config_file}"
237
+ exit(1)
238
+ end
239
+ else
240
+ $stderr.puts "Could not read configuration file: #{config_file}"
241
+ exit(1)
242
+ end
243
+ end
244
+
245
+ options[:port] = options[:port].to_i if options[:port]
246
+
247
+ options[:ssl] = !!(options[:ssl].to_s =~ /^(t|y|1)/i) if options[:ssl]
248
+
249
+ options
250
+ end
251
+
252
+ private
253
+
254
+ def send_rpc_request(args)
255
+ unless @cli
256
+ @cli = Rex::Proto::Http::Client.new(info[:host], info[:port], info[:context], info[:ssl], info[:ssl_version])
257
+ @cli.set_config(
258
+ vhost: info[:host],
259
+ agent: "Metasploit RPC Client/#{API_VERSION}",
260
+ read_max_data: 1024 * 1024 * 512
261
+ )
262
+ end
263
+
264
+ req = @cli.request_cgi(
265
+ 'method' => 'POST',
266
+ 'uri' => self.info[:uri],
267
+ 'ctype' => 'binary/message-pack',
268
+ 'data' => args.to_msgpack
269
+ )
270
+
271
+ begin
272
+ res = @cli.send_recv(req)
273
+ rescue => e
274
+ raise Msf::RPC::ServerException.new(000, e.message, e.class)
275
+ end
276
+
277
+ if res && [200, 401, 403, 500].include?(res.code)
278
+ resp = MessagePack.unpack(res.body)
279
+
280
+ # Boolean true versus truthy check required here;
281
+ # RPC responses such as { "error" => "Here I am" } and
282
+ # { "error" => # "" } must be accommodated.
283
+ if resp && resp.is_a?(::Hash) && resp['error'] == true
284
+ raise Msf::RPC::ServerException.new(
285
+ resp['error_code'] || res.code,
286
+ resp['error_message'] || resp['error_string'],
287
+ resp['error_class'], resp['error_backtrace']
288
+ )
289
+ end
290
+
291
+ return resp
292
+ else
293
+ if res
294
+ raise Msf::RPC::Exception.new(res.inspect)
295
+ else
296
+ raise Msf::RPC::Exception.new('Unknown error parsing or sending response')
297
+ end
298
+ end
299
+ end
300
+
301
+ def do_logout_cleanup
302
+ @user = nil
303
+ @pass = nil
304
+ end
305
+ end
306
+ end
219
307
  end
220
- end
221
-