CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/plugins/request.rb
Views: 11705
1
require 'uri'
2
3
module Msf
4
class Plugin::Requests < Msf::Plugin
5
6
class ConsoleCommandDispatcher
7
include Msf::Ui::Console::CommandDispatcher
8
9
HELP_REGEX = /^-?-h(?:elp)?$/.freeze
10
11
def name
12
'Request'
13
end
14
15
def commands
16
{
17
'request' => "Make a request of the specified type (#{types.join(', ')})"
18
}
19
end
20
21
# Dynamically determine the types of requests that are supported based on
22
# methods prefixed with "parse_args".
23
#
24
# @return [Array<String>] The supported request types.
25
def types
26
parse_methods = public_methods.select { |m| m.to_s =~ /^parse_args_/ }
27
parse_methods.collect { |m| m.to_s.split('_').slice(2..-1).join('_') }
28
end
29
30
# The main handler for the request command.
31
#
32
# @param args [Array<String>] The array of arguments provided by the user.
33
# @return [nil]
34
def cmd_request(*args)
35
# short circuit the whole deal if they need help
36
return help if args.empty?
37
return help if args.length == 1 && args.first =~ HELP_REGEX
38
39
# detect the request type from the uri which must be the last arg given
40
uri = args.last
41
if uri && uri =~ %r{^[A-Za-z]{3,5}://}
42
type = uri.split('://', 2).first
43
else
44
print_error('The last argument must be a valid and supported URI')
45
return help
46
end
47
48
# parse options
49
opts, opt_parser = parse_args(args, type)
50
if opts && opt_parser
51
# handle any "global" options
52
if opts[:output_file]
53
begin
54
opts[:output_file] = File.new(opts[:output_file], 'w')
55
rescue ::Errno::EACCES, Errno::EISDIR, Errno::ENOTDIR
56
return help(opt_parser, 'Failed to open the specified file for output')
57
end
58
end
59
# hand off the actual request to the appropriate request handler
60
handler_method = "handle_request_#{type}".to_sym
61
if respond_to?(handler_method)
62
# call the appropriate request handler
63
send(handler_method, opts, opt_parser)
64
else
65
# this should be dead code if parse_args is doing it's job correctly
66
help(opt_parser, "No request handler found for type (#{type}).")
67
end
68
elsif types.include? type
69
help(opt_parser)
70
else
71
help
72
end
73
end
74
75
# Parse the provided arguments by dispatching to the correct method based
76
# on the specified type.
77
#
78
# @param args [Array<String>] The command line arguments to parse.
79
# @param type [String] The protocol type that the request is for such as
80
# HTTP.
81
# @return [Array<Hash, Rex::Parser::Arguments>] An array with the options
82
# hash and the argument parser.
83
def parse_args(args, type = 'http')
84
type.downcase!
85
parse_method = "parse_args_#{type}".to_sym
86
if respond_to?(parse_method)
87
send(parse_method, args, type)
88
else
89
print_error("Unsupported URI type: #{type}")
90
end
91
end
92
93
# Parse the provided arguments for making HTTPS requests. The argument flags
94
# are intended to be similar to the curl utility.
95
#
96
# @param args [Array<String>] The command line arguments to parse.
97
# @param type [String] The protocol type that the request is for.
98
# @return [Array<Hash, Rex::Parser::Arguments>] An array with the options
99
# hash and the argument parser.
100
def parse_args_https(args = [], type = 'https')
101
# just let http do it
102
parse_args_http(args, type)
103
end
104
105
# Parse the provided arguments for making HTTP requests. The argument flags
106
# are intended to be similar to the curl utility.
107
#
108
# @param args [Array<String>] The command line arguments to parse.
109
# @param type [String] The protocol type that the request is for.
110
# @return [Array<Hash>, Rex::Parser::Arguments>] An array with the options
111
# hash and the argument parser.
112
def parse_args_http(args = [], _type = 'http')
113
opt_parser = Rex::Parser::Arguments.new(
114
'-0' => [ false, 'Use HTTP 1.0' ],
115
'-1' => [ false, 'Use TLSv1 (SSL)' ],
116
'-2' => [ false, 'Use SSLv2 (SSL)' ],
117
'-3' => [ false, 'Use SSLv3 (SSL)' ],
118
'-A' => [ true, 'User-Agent to send to server' ],
119
'-d' => [ true, 'HTTP POST data' ],
120
'-G' => [ false, 'Send the -d data with an HTTP GET' ],
121
'-h' => [ false, 'This help text' ],
122
'-H' => [ true, 'Custom header to pass to server' ],
123
'-i' => [ false, 'Include headers in the output' ],
124
'-I' => [ false, 'Show document info only' ],
125
'-o' => [ true, 'Write output to <file> instead of stdout' ],
126
'-u' => [ true, 'Server user and password' ],
127
'-X' => [ true, 'Request method to use' ]
128
# '-x' => [ true, 'Proxy to use, format: [proto://][user:pass@]host[:port]' +
129
# ' Proto defaults to http:// and port to 1080'],
130
)
131
132
options = {
133
headers: {},
134
print_body: true,
135
print_headers: false,
136
ssl_version: 'Auto',
137
user_agent: Rex::UserAgent.session_agent,
138
version: '1.1'
139
}
140
141
opt_parser.parse(args) do |opt, _idx, val|
142
case opt
143
when '-0'
144
options[:version] = '1.0'
145
when '-1'
146
options[:ssl_version] = 'TLS1'
147
when '-2'
148
options[:ssl_version] = 'SSL2'
149
when '-3'
150
options[:ssl_version] = 'SSL3'
151
when '-A'
152
options[:user_agent] = val
153
when '-d'
154
options[:data] = val
155
options[:method] ||= 'POST'
156
when '-G'
157
options[:method] = 'GET'
158
when HELP_REGEX
159
# help(opt_parser)
160
# guard to prevent further option processing & stymie request handling
161
return [nil, opt_parser]
162
when '-H'
163
name, value = val.split(':', 2)
164
options[:headers][name] = value.to_s.strip
165
when '-i'
166
options[:print_headers] = true
167
when '-I'
168
options[:print_headers] = true
169
options[:print_body] = false
170
options[:method] ||= 'HEAD'
171
when '-o'
172
options[:output_file] = File.expand_path(val)
173
when '-u'
174
val = val.split(':', 2) # only split on first ':' as per curl:
175
# from curl man page: "The user name and passwords are split up on the
176
# first colon, which makes it impossible to use a colon in the user
177
# name with this option. The password can, still.
178
options[:auth_username] = val.first
179
options[:auth_password] = val.last
180
when '-p'
181
options[:auth_password] = val
182
when '-X'
183
options[:method] = val
184
# when '-x'
185
# @TODO proxy
186
else
187
options[:uri] = val
188
end
189
end
190
unless options[:uri]
191
help(opt_parser)
192
end
193
options[:method] ||= 'GET'
194
options[:uri] = URI(options[:uri])
195
[options, opt_parser]
196
end
197
198
# Perform an HTTPS request based on the user specified options.
199
#
200
# @param opts [Hash] The options to use for making the HTTPS request.
201
# @option opts [String] :auth_username An optional username to use with
202
# basic authentication.
203
# @option opts [String] :auth_password An optional password to use with
204
# basic authentication. This is only used when :auth_username is
205
# specified.
206
# @option opts [String] :data Any data to include within the body of the
207
# request. Often used with the POST HTTP method.
208
# @option opts [Hash] :headers A hash of additional headers to include in
209
# the request.
210
# @option opts [String] :method The HTTP method to use in the request.
211
# @option opts [#write] :output_file A file to write the response data to.
212
# @option opts [Boolean] :print_body Whether or not to print the body of the
213
# response.
214
# @option opts [Boolean] :print_headers Whether or not to print the headers
215
# of the response.
216
# @option opts [String] :ssl_version The version of SSL to use if the
217
# request scheme is HTTPS.
218
# @option opts [String] :uri The target uri to request.
219
# @option opts [String] :user_agent The value to use in the User-Agent
220
# header of the request.
221
# @param opt_parser [Rex::Parser::Arguments] the argument parser for the
222
# request type.
223
# @return [nil]
224
def handle_request_https(opts, opt_parser)
225
# let http do it
226
handle_request_http(opts, opt_parser)
227
end
228
229
# Perform an HTTP request based on the user specified options.
230
#
231
# @param opts [Hash] The options to use for making the HTTP request.
232
# @option opts [String] :auth_username An optional username to use with
233
# basic authentication.
234
# @option opts [String] :auth_password An optional password to use with
235
# basic authentication. This is only used when :auth_username is
236
# specified.
237
# @option opts [String] :data Any data to include within the body of the
238
# request. Often used with the POST HTTP method.
239
# @option opts [Hash] :headers A hash of additional headers to include in
240
# the request.
241
# @option opts [String] :method The HTTP method to use in the request.
242
# @option opts [#write] :output_file A file to write the response data to.
243
# @option opts [Boolean] :print_body Whether or not to print the body of the
244
# response.
245
# @option opts [Boolean] :print_headers Whether or not to print the headers
246
# of the response.
247
# @option opts [String] :ssl_version The version of SSL to use if the
248
# request scheme is HTTPS.
249
# @option opts [String] :uri The target uri to request.
250
# @option opts [String] :user_agent The value to use in the User-Agent
251
# header of the request.
252
# @param opt_parser [Rex::Parser::Arguments] the argument parser for the
253
# request type.
254
# @return [nil]
255
def handle_request_http(opts, _opt_parser)
256
uri = opts[:uri]
257
http_client = Rex::Proto::Http::Client.new(
258
uri.host,
259
uri.port,
260
{ 'Msf' => framework },
261
uri.scheme == 'https',
262
opts[:ssl_version]
263
)
264
265
if opts[:auth_username]
266
auth_str = opts[:auth_username] + ':' + opts[:auth_password]
267
auth_str = 'Basic ' + Rex::Text.encode_base64(auth_str)
268
opts[:headers]['Authorization'] = auth_str
269
end
270
271
uri.path = '/' if uri.path.empty?
272
273
begin
274
http_client.connect
275
req = http_client.request_cgi(
276
'agent' => opts[:user_agent],
277
'data' => opts[:data],
278
'headers' => opts[:headers],
279
'method' => opts[:method],
280
'password' => opts[:auth_password],
281
'query' => uri.query,
282
'uri' => uri.path,
283
'username' => opts[:auth_username],
284
'version' => opts[:version]
285
)
286
287
response = http_client.send_recv(req)
288
rescue ::OpenSSL::SSL::SSLError
289
print_error('Encountered an SSL error')
290
rescue ::Errno::ECONNRESET
291
print_error('The connection was reset by the peer')
292
rescue ::EOFError, Errno::ETIMEDOUT, Rex::ConnectionError, ::Timeout::Error
293
print_error('Encountered an error')
294
ensure
295
http_client.close
296
end
297
298
unless response
299
opts[:output_file].close if opts[:output_file]
300
return nil
301
end
302
303
if opts[:print_headers]
304
output_line(opts, response.cmd_string)
305
output_line(opts, response.headers.to_s)
306
end
307
308
output_line(opts, response.body) if opts[:print_body]
309
if opts[:output_file]
310
print_status("Wrote #{opts[:output_file].tell} bytes to #{opts[:output_file].path}")
311
opts[:output_file].close
312
end
313
end
314
315
# Output lines based on the provided options. Data is either printed to the
316
# console or written to a file. Trailing new lines are removed.
317
#
318
# @param opts [Hash] The options as parsed from parse_args.
319
# @option opts [#write, nil] :output_file An optional file to write the
320
# output to.
321
# @param line [String] The string to output.
322
# @return [nil]
323
def output_line(opts, line)
324
if opts[:output_file].nil?
325
if line[-2..] == "\r\n"
326
print_line(line[0..-3])
327
elsif line[-1] == "\n"
328
print_line(line[0..-2])
329
else
330
print_line(line)
331
end
332
else
333
opts[:output_file].write(line)
334
end
335
end
336
337
# Print the appropriate help text depending on an optional option parser.
338
#
339
# @param opt_parser [Rex::Parser::Arguments] the argument parser for the
340
# request type.
341
# @param msg [String] the first line of the help text to display to the
342
# user.
343
# @return [nil]
344
def help(opt_parser = nil, msg = 'Usage: request [options] uri')
345
print_line(msg)
346
if opt_parser
347
print_line(opt_parser.usage)
348
else
349
print_line("Supported uri types are: #{types.collect { |t| t + '://' }.join(', ')}")
350
print_line('To see usage for a specific uri type, use request -h uri')
351
end
352
end
353
354
end
355
356
def initialize(framework, opts)
357
super
358
add_console_dispatcher(ConsoleCommandDispatcher)
359
end
360
361
def cleanup
362
remove_console_dispatcher('Request')
363
end
364
365
def name
366
'Request'
367
end
368
369
def desc
370
'Make requests from within Metasploit using various protocols.'
371
end
372
373
end
374
end
375
376