Skip to content

Commit 7443af6

Browse files
author
Brent Cook
committed
Land rapid7#5247, add RPC API call documentation
2 parents 1fd6015 + a0eb7d0 commit 7443af6

File tree

11 files changed

+1568
-17
lines changed

11 files changed

+1568
-17
lines changed

lib/msf/core/rpc/v10/client.rb

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,21 @@ module RPC
1212

1313
class Client
1414

15-
attr_accessor :token, :info
15+
# @!attribute token
16+
# @return [String] A login token.
17+
attr_accessor :token
1618

19+
# @!attribute info
20+
# @return [Hash] Login information.
21+
attr_accessor :info
1722

23+
24+
# Initializes the RPC client to connect to: https://127.0.0.1:3790 (TLS1)
25+
# The connection information is overridden through the optional info hash.
26+
#
27+
# @param [Hash] info Information needed for the initialization.
28+
# @option info [String] :token A token used by the client.
29+
# @return [void]
1830
def initialize(info={})
1931
self.info = {
2032
:host => '127.0.0.1',
@@ -29,6 +41,13 @@ def initialize(info={})
2941
end
3042

3143

44+
# Logs in by calling the 'auth.login' API. The authentication token will expire 5 minutes
45+
# after the last request was made.
46+
#
47+
# @param [String] user Username.
48+
# @param [String] pass Password.
49+
# @raise RuntimeError Indicating a failed authentication.
50+
# @return [TrueClass] Indicating a successful login.
3251
def login(user,pass)
3352
res = self.call("auth.login", user, pass)
3453
unless (res && res['result'] == "success")
@@ -38,8 +57,23 @@ def login(user,pass)
3857
true
3958
end
4059

41-
# Prepend the authentication token as the first parameter
42-
# of every call except auth.login. Requires the
60+
61+
# Calls an API.
62+
#
63+
# @param [String] meth The RPC API to call.
64+
# @param [Array<string>] args The arguments to pass.
65+
# @raise [RuntimeError] Something is wrong while calling the remote API, including:
66+
# * A missing token (your client needs to authenticate).
67+
# * A unexpected response from the server, such as a timeout or unexpected HTTP code.
68+
# @raise [Msf::RPC::ServerException] The RPC service returns an error.
69+
# @return [Hash] The API response. It contains the following keys:
70+
# * 'version' [String] Framework version.
71+
# * 'ruby' [String] Ruby version.
72+
# * 'api' [String] API version.
73+
# @example
74+
# # This will return something like this:
75+
# # {"version"=>"4.11.0-dev", "ruby"=>"2.1.5 x86_64-darwin14.0 2014-11-13", "api"=>"1.0"}
76+
# rpc.call('core.version')
4377
def call(meth, *args)
4478
unless meth == "auth.login"
4579
unless self.token
@@ -84,6 +118,10 @@ def call(meth, *args)
84118
end
85119
end
86120

121+
122+
# Closes the client.
123+
#
124+
# @return [void]
87125
def close
88126
if @cli && @cli.conn?
89127
@cli.close

lib/msf/core/rpc/v10/constants.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ module RPC
88
class Exception < RuntimeError
99
attr_accessor :code, :message
1010

11+
# Initializes Exception.
12+
#
13+
# @param [Fixnum] code An error code.
14+
# @param [String] message An error message.
15+
# @return [void]
1116
def initialize(code, message)
1217
self.code = code
1318
self.message = message
@@ -18,6 +23,13 @@ def initialize(code, message)
1823
class ServerException < RuntimeError
1924
attr_accessor :code, :error_message, :error_class, :error_backtrace
2025

26+
# Initializes ServerException.
27+
#
28+
# @param [Fixnum] code An error code.
29+
# @param [String] error_message An error message.
30+
# @param [Exception] error_class An error class.
31+
# @param [Array] error_backtrace A backtrace of the error.
32+
# @return [void]
2133
def initialize(code, error_message, error_class, error_backtrace)
2234
self.code = code
2335
self.error_message = error_message

lib/msf/core/rpc/v10/rpc_auth.rb

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,21 @@ class RPC_Auth < RPC_Base
1111
rescue ::LoadError
1212
end
1313

14+
# Handles client authentication. The authentication token will expire 5 minutes after the
15+
# last request was made.
16+
#
17+
# @param [String] user The username.
18+
# @param [String] pass The password.
19+
# @raise [Msf::RPC::Exception] Something is wrong while authenticating, you can possibly get:
20+
# * 401 Failed authentication.
21+
# @return [Hash] A hash indicating a successful login, it contains the following keys:
22+
# * 'result' [String] A successful message: 'success'.
23+
# * 'token' [String] A token for the authentication.
24+
# @example Here's how you would use this from the client:
25+
# # This returns something like the following:
26+
# # {"result"=>"success", "token"=>"TEMPyp1N40NK8GM0Tx7A87E6Neak2tVJ"}
27+
# rpc.call('auth.login_noauth', 'username', 'password')
1428
def rpc_login_noauth(user,pass)
15-
1629
if not (user.kind_of?(::String) and pass.kind_of?(::String))
1730
error(401, "Login Failed")
1831
end
@@ -42,6 +55,19 @@ def rpc_login_noauth(user,pass)
4255
{ "result" => "success", "token" => token }
4356
end
4457

58+
59+
# Handles client deauthentication.
60+
#
61+
# @param [String] token The user's token to log off.
62+
# @raise [Msf::RPC::Exception] An error indicating a failed deauthentication, including:
63+
# * 500 Invalid authentication token.
64+
# * 500 Permanent authentication token.
65+
# @return [Hash] A hash indiciating the action was successful. It contains the following key:
66+
# * 'result' [String] The successful message: 'success'
67+
# @example Here's how you would use this from the client:
68+
# # This returns something like:
69+
# # {"result"=>"success"}
70+
# rpc.call('auth.logout', 'TEMPyp1N40NK8GM0Tx7A87E6Neak2tVJ')
4571
def rpc_logout(token)
4672
found = self.service.tokens[token]
4773
error("500", "Invalid Authentication Token") if not found
@@ -53,6 +79,16 @@ def rpc_logout(token)
5379
{ "result" => "success" }
5480
end
5581

82+
83+
# Returns a list of authentication tokens, including the ones that are
84+
# temporary, permanent, or stored in the backend.
85+
#
86+
# @return [Hash] A hash that contains a list of authentication tokens. It contains the following key:
87+
# * 'tokens' [Array<string>] An array of tokens.
88+
# @example Here's how you would use this from the client:
89+
# # This returns something like:
90+
# # {"tokens"=>["TEMPf5I4Ec8cBEKVD8D7xtIbTXWoKapP", "TEMPtcVmMld8w74zo0CYeosM3iXW0nJz"]}
91+
# rpc.call('auth.token_list')
5692
def rpc_token_list
5793
res = self.service.tokens.keys
5894
begin
@@ -66,6 +102,14 @@ def rpc_token_list
66102
{ "tokens" => res }
67103
end
68104

105+
106+
# Adds a new token to the database.
107+
#
108+
# @param [String] token A unique token.
109+
# @return [Hash] A hash indicating the action was successful. It contains the following key:
110+
# * 'result' [String] The successful message: 'success'
111+
# @example Here's how you would use this from the client:
112+
# rpc.call('auth.token_add', 'UNIQUE_TOKEN')
69113
def rpc_token_add(token)
70114
db = false
71115
begin
@@ -85,6 +129,16 @@ def rpc_token_add(token)
85129
{ "result" => "success" }
86130
end
87131

132+
133+
# Generates a random 32-byte authentication token. The token is added to the
134+
# database as a side-effect.
135+
#
136+
# @return [Hash] A hash indicating the action was successful, also the new token.
137+
# It contains the following keys:
138+
# * 'result' [String] The successful message: 'success'
139+
# * 'token' [String] A new token.
140+
# @example Here's how you would use this from the client:
141+
# rpc.call('auth.token_generate')
88142
def rpc_token_generate
89143
token = Rex::Text.rand_text_alphanumeric(32)
90144
db = false
@@ -106,6 +160,16 @@ def rpc_token_generate
106160
{ "result" => "success", "token" => token }
107161
end
108162

163+
164+
# Removes a token from the database. Similar to what #rpc_logout does internally, except this
165+
# can remove tokens stored in the database backend (Mdm).
166+
#
167+
# @see #rpc_logout
168+
# @param [String] token The token to delete.
169+
# @return [Hash] A hash indicating the action was successful. It contains the following key:
170+
# * 'result' [String] The successful message: 'success'
171+
# @example Here's how you would use this from the client:
172+
# rpc.call('auth.token_remove', 'TEMPtcVmMld8w74zo0CYeosM3iXW0nJz')
109173
def rpc_token_remove(token)
110174
db = false
111175
begin

lib/msf/core/rpc/v10/rpc_base.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,22 @@ module RPC
55
class RPC_Base
66
attr_accessor :framework, :service, :tokens, :users
77

8+
# Initializes framework, service, tokens, and users
9+
#
10+
# return [void]
811
def initialize(service)
912
self.service = service
1013
self.framework = service.framework
1114
self.tokens = service.tokens
1215
self.users = service.users
1316
end
1417

18+
# Raises an Msf::RPC Exception.
19+
#
20+
# @param [Fixnum] code The error code to raise.
21+
# @param [String] message The error message.
22+
# @raise [Msf::RPC::Exception]
23+
# @return [void]
1524
def error(code, message)
1625
raise Msf::RPC::Exception.new(code, message)
1726
end

lib/msf/core/rpc/v10/rpc_console.rb

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,24 @@ module Msf
77
module RPC
88
class RPC_Console < RPC_Base
99

10+
# Initializes the RPC console
11+
#
12+
# @return [Msf::Ui::Web::Driver]
1013
def initialize(*args)
1114
super
1215
@console_driver = Msf::Ui::Web::Driver.new(:framework => framework)
1316
end
1417

18+
# Creates a new framework console instance.
19+
#
20+
# @param [Hash] opts See Msf::Ui::Web::Driver#create_console
21+
# @return [Hash] Information about the new console. It contains the following keys:
22+
# * 'id' [Fixnum] The console's ID.
23+
# * 'prompt' [String] The framework prompt (example: 'msf > ')
24+
# * 'busy' [TrueClass] The console's busy state, or
25+
# * 'busy' [FalseClass] The console's busy state.
26+
# @example Here's how you would use this from the client:
27+
# rpc.call('console.create')
1528
def rpc_create(opts={})
1629
cid = @console_driver.create_console(opts)
1730
{
@@ -21,6 +34,17 @@ def rpc_create(opts={})
2134
}
2235
end
2336

37+
38+
# Returns a list of framework consoles.
39+
#
40+
# @return [Hash] Console information.
41+
# * 'consoles' [Array<Hash>] consoles, each element is a hash that includes:
42+
# * 'id' [Fixnum] The console's ID
43+
# * 'prompt' [String] The framework prompt (example: 'msf > ')
44+
# * 'busy' [TrueClass] The console's busy state, or
45+
# * 'busy' [FalseClass] The console's busy state.
46+
# @example Here's how you would use this from the client:
47+
# rpc.call('console.list')
2448
def rpc_list
2549
ret = []
2650
@console_driver.consoles.each_key do |cid|
@@ -33,13 +57,37 @@ def rpc_list
3357
{'consoles' => ret}
3458
end
3559

60+
61+
# Deletes a framework console instance.
62+
#
63+
# @param [Fixnum] cid Framework console ID.
64+
# @return [Hash] A result indicating whether the action was successful or not.
65+
# It contains the following key:
66+
# * 'result' [String] Either 'success' or 'failure'.
67+
# @example Here's how you would use this from the client:
68+
# rpc.call('console.destroy', 1)
3669
def rpc_destroy(cid)
3770
cid = cid.to_s
3871
return { 'result' => 'failure' } if not @console_driver.consoles[cid]
3972
res = @console_driver.destroy_console(cid)
4073
{ 'result' => res ? 'success' : 'failure' }
4174
end
4275

76+
77+
# Returns the framework console output in raw form.
78+
#
79+
# @param [Fixnum] cid Framework console ID.
80+
# @return [Hash] There are two different hashes you might get:
81+
#
82+
# If the console ID is invalid, you will get a hash like the following:
83+
# * 'result' [String] A value that says 'failure'.
84+
# If the console ID is valid, you will get a hash like the following:
85+
# * 'data' [String] The output the framework console produces (example: the banner)
86+
# * 'prompt' [String] The framework prompt (example: 'msf > ')
87+
# * 'busy' [TrueClass] The console's busy state, or
88+
# * 'busy' [FalseClass] The console's busy state.
89+
# @example Here's how you would use this from the client:
90+
# rpc.call('console.read', 1)
4391
def rpc_read(cid)
4492
cid = cid.to_s
4593
return { 'result' => 'failure' } if not @console_driver.consoles[cid]
@@ -50,25 +98,75 @@ def rpc_read(cid)
5098
}
5199
end
52100

101+
102+
# Sends an input (such as a command) to the framework console.
103+
#
104+
# @param [Fixnum] cid Framework console ID.
105+
# @param [String] data User input.
106+
# @return [Hash] There are two different hashes you might get:
107+
#
108+
# If the console ID is invalid, you will get a hash like the following:
109+
# * 'result' [String] A value that says 'failure'.
110+
# If the console ID is invalid, you will get a hash like the following:
111+
# * 'wrote' [Fixnum] Number of bytes sent.
112+
# @note Remember to add a newline (\\r\\n) at the end of input, otherwise
113+
# the console will not do anything. And you will need to use the
114+
# #rpc_read method to retrieve the output again.
115+
# @example Here's how you would use this from the client:
116+
# # This will show the current module's options.
117+
# rpc.call('console.write', 4, "show options\r\n")
53118
def rpc_write(cid, data)
54119
cid = cid.to_s
55120
return { 'result' => 'failure' } if not @console_driver.consoles[cid]
56121
{ "wrote" => @console_driver.write_console(cid, data || '') }
57122
end
58123

124+
125+
# Returns the tab-completed version of your input (such as a module path).
126+
#
127+
# @param [Fixnum] cid Framework console ID.
128+
# @param [String] line Command.
129+
# @return [Hash] There are two different hashes you might get:
130+
#
131+
# If the console ID is invalid, you will get a hash like the following:
132+
# * 'result' [String] A value that says 'failure'.
133+
# If the console ID is valid, you will get a hash like the following:
134+
# * 'tabs' [String] The tab-completed version of the command.
135+
# @example Here's how you would use this from the client:
136+
# # This will return:
137+
# # {"tabs"=>["use exploit/windows/smb/ms08_067_netapi"]}
138+
# rpc.call('console.tabs', 4, "use exploit/windows/smb/ms08_067_")
59139
def rpc_tabs(cid, line)
60140
cid = cid.to_s
61141
return { 'result' => 'failure' } if not @console_driver.consoles[cid]
62142
{ "tabs" => @console_driver.consoles[cid].tab_complete(line) }
63143
end
64144

145+
146+
# Kills a framework session. This serves the same purpose as [CTRL]+[C] to abort an interactive session.
147+
# You might also want to considering using the session API calls instead of this.
148+
#
149+
# @param [Fixnum] cid Framework console ID.
150+
# @return [Hash] A hash indicating whether the action was successful or not. It contains:
151+
# * 'result' [String] A message that says 'success' if the console ID is valid (and successfully killed, otherwise 'failed')
152+
# @example Here's how you would use this from the client:
153+
# rpc.call('console.session_kill', 4)
65154
def rpc_session_kill(cid)
66155
cid = cid.to_s
67156
return { 'result' => 'failure' } if not @console_driver.consoles[cid]
68157
@console_driver.consoles[cid].session_kill
69158
{ 'result' => 'success' }
70159
end
71160

161+
162+
# Detaches a framework session. This serves the same purpose as [CTRL]+[Z] to
163+
# background an interactive session.
164+
#
165+
# @param [Fixnum] cid Framework console ID.
166+
# @return [Hash] A hash indicating whether the action was successful or not. It contains:
167+
# * 'result' [String] A message that says 'success' if the console ID is valid (and successfully detached, otherwise 'failed')
168+
# @example Here's how you would use this from the client:
169+
# rpc.call('console.session_detach', 4)
72170
def rpc_session_detach(cid)
73171
cid = cid.to_s
74172
return { 'result' => 'failure' } if not @console_driver.consoles[cid]

0 commit comments

Comments
 (0)