Class: Msf::Plugin::Requests::ConsoleCommandDispatcher

Inherits:
Object
  • Object
show all
Includes:
Ui::Console::CommandDispatcher
Defined in:
plugins/request.rb

Constant Summary collapse

HELP_REGEX =
/^-?-h(?:elp)?$/.freeze

Instance Attribute Summary

Attributes included from Ui::Console::CommandDispatcher

#driver

Attributes included from Rex::Ui::Text::DispatcherShell::CommandDispatcher

#shell, #tab_complete_items

Instance Method Summary collapse

Methods included from Ui::Console::CommandDispatcher

#active_module, #active_module=, #active_session, #active_session=, #build_range_array, #docs_dir, #framework, #initialize, #load_config, #log_error, #remove_lines

Methods included from Rex::Ui::Text::DispatcherShell::CommandDispatcher

#cmd_help, #cmd_help_help, #cmd_help_tabs, #deprecated_cmd, #deprecated_commands, #deprecated_help, #docs_dir, #help_to_s, included, #initialize, #print, #print_error, #print_good, #print_line, #print_status, #print_warning, #tab_complete_directory, #tab_complete_filenames, #tab_complete_generic, #tab_complete_source_address, #unknown_command, #update_prompt

Instance Method Details

#cmd_request(*args) ⇒ nil

The main handler for the request command.

Parameters:

  • args (Array<String>)

    The array of arguments provided by the user.

Returns:

  • (nil)


34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'plugins/request.rb', line 34

def cmd_request(*args)
  # short circuit the whole deal if they need help
  return help if args.empty?
  return help if args.length == 1 && args.first =~ HELP_REGEX

  # detect the request type from the uri which must be the last arg given
  uri = args.last
  if uri && uri =~ %r{^[A-Za-z]{3,5}://}
    type = uri.split('://', 2).first
  else
    print_error('The last argument must be a valid and supported URI')
    return help
  end

  # parse options
  opts, opt_parser = parse_args(args, type)
  if opts && opt_parser
    # handle any "global" options
    if opts[:output_file]
      begin
        opts[:output_file] = File.new(opts[:output_file], 'w')
      rescue ::Errno::EACCES, Errno::EISDIR, Errno::ENOTDIR
        return help(opt_parser, 'Failed to open the specified file for output')
      end
    end
    # hand off the actual request to the appropriate request handler
    handler_method = "handle_request_#{type}".to_sym
    if respond_to?(handler_method)
      # call the appropriate request handler
      send(handler_method, opts, opt_parser)
    else
      # this should be dead code if parse_args is doing it's job correctly
      help(opt_parser, "No request handler found for type (#{type}).")
    end
  elsif types.include? type
    help(opt_parser)
  else
    help
  end
end

#commandsObject



15
16
17
18
19
# File 'plugins/request.rb', line 15

def commands
  {
    'request' => "Make a request of the specified type (#{types.join(', ')})"
  }
end

#handle_request_http(opts, _opt_parser) ⇒ nil

Perform an HTTP request based on the user specified options.

Parameters:

  • opts (Hash)

    The options to use for making the HTTP request.

  • opt_parser (Rex::Parser::Arguments)

    the argument parser for the request type.

Options Hash (opts):

  • :auth_username (String)

    An optional username to use with basic authentication.

  • :auth_password (String)

    An optional password to use with basic authentication. This is only used when :auth_username is specified.

  • :data (String)

    Any data to include within the body of the request. Often used with the POST HTTP method.

  • :headers (Hash)

    A hash of additional headers to include in the request.

  • :method (String)

    The HTTP method to use in the request.

  • :output_file (#write)

    A file to write the response data to.

  • :print_body (Boolean)

    Whether or not to print the body of the response.

  • :print_headers (Boolean)

    Whether or not to print the headers of the response.

  • :ssl_version (String)

    The version of SSL to use if the request scheme is HTTPS.

  • :uri (String)

    The target uri to request.

  • :user_agent (String)

    The value to use in the User-Agent header of the request.

Returns:

  • (nil)


255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
# File 'plugins/request.rb', line 255

def handle_request_http(opts, _opt_parser)
  uri = opts[:uri]
  http_client = Rex::Proto::Http::Client.new(
    uri.host,
    uri.port,
    { 'Msf' => framework },
    uri.scheme == 'https',
    opts[:ssl_version]
  )

  if opts[:auth_username]
    auth_str = opts[:auth_username] + ':' + opts[:auth_password]
    auth_str = 'Basic ' + Rex::Text.encode_base64(auth_str)
    opts[:headers]['Authorization'] = auth_str
  end

  uri.path = '/' if uri.path.empty?

  begin
    http_client.connect
    req = http_client.request_cgi(
      'agent' => opts[:user_agent],
      'data' => opts[:data],
      'headers' => opts[:headers],
      'method' => opts[:method],
      'password' => opts[:auth_password],
      'query' => uri.query,
      'uri' => uri.path,
      'username' => opts[:auth_username],
      'version' => opts[:version]
    )

    response = http_client.send_recv(req)
  rescue ::OpenSSL::SSL::SSLError
    print_error('Encountered an SSL error')
  rescue ::Errno::ECONNRESET
    print_error('The connection was reset by the peer')
  rescue ::EOFError, Errno::ETIMEDOUT, Rex::ConnectionError, ::Timeout::Error
    print_error('Encountered an error')
  ensure
    http_client.close
  end

  unless response
    opts[:output_file].close if opts[:output_file]
    return nil
  end

  if opts[:print_headers]
    output_line(opts, response.cmd_string)
    output_line(opts, response.headers.to_s)
  end

  output_line(opts, response.body) if opts[:print_body]
  if opts[:output_file]
    print_status("Wrote #{opts[:output_file].tell} bytes to #{opts[:output_file].path}")
    opts[:output_file].close
  end
end

#handle_request_https(opts, opt_parser) ⇒ nil

Perform an HTTPS request based on the user specified options.

Parameters:

  • opts (Hash)

    The options to use for making the HTTPS request.

  • opt_parser (Rex::Parser::Arguments)

    the argument parser for the request type.

Options Hash (opts):

  • :auth_username (String)

    An optional username to use with basic authentication.

  • :auth_password (String)

    An optional password to use with basic authentication. This is only used when :auth_username is specified.

  • :data (String)

    Any data to include within the body of the request. Often used with the POST HTTP method.

  • :headers (Hash)

    A hash of additional headers to include in the request.

  • :method (String)

    The HTTP method to use in the request.

  • :output_file (#write)

    A file to write the response data to.

  • :print_body (Boolean)

    Whether or not to print the body of the response.

  • :print_headers (Boolean)

    Whether or not to print the headers of the response.

  • :ssl_version (String)

    The version of SSL to use if the request scheme is HTTPS.

  • :uri (String)

    The target uri to request.

  • :user_agent (String)

    The value to use in the User-Agent header of the request.

Returns:

  • (nil)


224
225
226
227
# File 'plugins/request.rb', line 224

def handle_request_https(opts, opt_parser)
  # let http do it
  handle_request_http(opts, opt_parser)
end

#help(opt_parser = nil, msg = 'Usage: request [options] uri') ⇒ Object

Print the appropriate help text depending on an optional option parser.

@return [nil]

Parameters:

  • opt_parser (Rex::Parser::Arguments) (defaults to: nil)

    the argument parser for the request type.

  • msg (String) (defaults to: 'Usage: request [options] uri')

    the first line of the help text to display to the user.



344
345
346
347
348
349
350
351
352
# File 'plugins/request.rb', line 344

def help(opt_parser = nil, msg = 'Usage: request [options] uri')
  print_line(msg)
  if opt_parser
    print_line(opt_parser.usage)
  else
    print_line("Supported uri types are: #{types.collect { |t| t + '://' }.join(', ')}")
    print_line('To see usage for a specific uri type, use request -h uri')
  end
end

#nameObject



11
12
13
# File 'plugins/request.rb', line 11

def name
  'Request'
end

#output_line(opts, line) ⇒ nil

Output lines based on the provided options. Data is either printed to the console or written to a file. Trailing new lines are removed.

Parameters:

  • opts (Hash)

    The options as parsed from parse_args.

  • line (String)

    The string to output.

Options Hash (opts):

  • :output_file (#write, nil)

    An optional file to write the output to.

Returns:

  • (nil)


323
324
325
326
327
328
329
330
331
332
333
334
335
# File 'plugins/request.rb', line 323

def output_line(opts, line)
  if opts[:output_file].nil?
    if line[-2..] == "\r\n"
      print_line(line[0..-3])
    elsif line[-1] == "\n"
      print_line(line[0..-2])
    else
      print_line(line)
    end
  else
    opts[:output_file].write(line)
  end
end

#parse_args(args, type = 'http') ⇒ Array<Hash, Rex::Parser::Arguments>

Parse the provided arguments by dispatching to the correct method based on the specified type.

Parameters:

  • args (Array<String>)

    The command line arguments to parse.

  • type (String) (defaults to: 'http')

    The protocol type that the request is for such as HTTP.

Returns:



83
84
85
86
87
88
89
90
91
# File 'plugins/request.rb', line 83

def parse_args(args, type = 'http')
  type.downcase!
  parse_method = "parse_args_#{type}".to_sym
  if respond_to?(parse_method)
    send(parse_method, args, type)
  else
    print_error("Unsupported URI type: #{type}")
  end
end

#parse_args_http(args = [], _type = 'http') ⇒ Array<Hash>, Rex::Parser::Arguments

Parse the provided arguments for making HTTP requests. The argument flags are intended to be similar to the curl utility.

Parameters:

  • args (Array<String>) (defaults to: [])

    The command line arguments to parse.

  • type (String)

    The protocol type that the request is for.

Returns:



112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
# File 'plugins/request.rb', line 112

def parse_args_http(args = [], _type = 'http')
  opt_parser = Rex::Parser::Arguments.new(
    '-0' => [ false, 'Use HTTP 1.0' ],
    '-1' => [ false, 'Use TLSv1 (SSL)' ],
    '-2' => [ false, 'Use SSLv2 (SSL)' ],
    '-3' => [ false, 'Use SSLv3 (SSL)' ],
    '-A' => [ true, 'User-Agent to send to server' ],
    '-d' => [ true, 'HTTP POST data' ],
    '-G' => [ false, 'Send the -d data with an HTTP GET' ],
    '-h' => [ false, 'This help text' ],
    '-H' => [ true, 'Custom header to pass to server' ],
    '-i' => [ false, 'Include headers in the output' ],
    '-I' => [ false, 'Show document info only' ],
    '-o' => [ true, 'Write output to <file> instead of stdout' ],
    '-u' => [ true, 'Server user and password' ],
    '-X' => [ true, 'Request method to use' ]
    # '-x' => [ true,  'Proxy to use, format: [proto://][user:pass@]host[:port]' +
    #                 '  Proto defaults to http:// and port to 1080'],
  )

  options = {
    headers: {},
    print_body: true,
    print_headers: false,
    ssl_version: 'Auto',
    user_agent: Rex::UserAgent.session_agent,
    version: '1.1'
  }

  opt_parser.parse(args) do |opt, _idx, val|
    case opt
    when '-0'
      options[:version] = '1.0'
    when '-1'
      options[:ssl_version] = 'TLS1'
    when '-2'
      options[:ssl_version] = 'SSL2'
    when '-3'
      options[:ssl_version] = 'SSL3'
    when '-A'
      options[:user_agent] = val
    when '-d'
      options[:data] = val
      options[:method] ||= 'POST'
    when '-G'
      options[:method] = 'GET'
    when HELP_REGEX
      # help(opt_parser)
      # guard to prevent further option processing & stymie request handling
      return [nil, opt_parser]
    when '-H'
      name, value = val.split(':', 2)
      options[:headers][name] = value.to_s.strip
    when '-i'
      options[:print_headers] = true
    when '-I'
      options[:print_headers] = true
      options[:print_body] = false
      options[:method] ||= 'HEAD'
    when '-o'
      options[:output_file] = File.expand_path(val)
    when '-u'
      val = val.split(':', 2) # only split on first ':' as per curl:
      # from curl man page: "The user name and passwords are split up on the
      # first colon, which makes it impossible to use a colon in the user
      # name with this option.  The password can, still.
      options[:auth_username] = val.first
      options[:auth_password] = val.last
    when '-p'
      options[:auth_password] = val
    when '-X'
      options[:method] = val
      # when '-x'
      # @TODO proxy
    else
      options[:uri] = val
    end
  end
  unless options[:uri]
    help(opt_parser)
  end
  options[:method] ||= 'GET'
  options[:uri] = URI(options[:uri])
  [options, opt_parser]
end

#parse_args_https(args = [], type = 'https') ⇒ Array<Hash, Rex::Parser::Arguments>

Parse the provided arguments for making HTTPS requests. The argument flags are intended to be similar to the curl utility.

Parameters:

  • args (Array<String>) (defaults to: [])

    The command line arguments to parse.

  • type (String) (defaults to: 'https')

    The protocol type that the request is for.

Returns:



100
101
102
103
# File 'plugins/request.rb', line 100

def parse_args_https(args = [], type = 'https')
  # just let http do it
  parse_args_http(args, type)
end

#typesArray<String>

Dynamically determine the types of requests that are supported based on methods prefixed with “parse_args”.

Returns:

  • (Array<String>)

    The supported request types.



25
26
27
28
# File 'plugins/request.rb', line 25

def types
  parse_methods = public_methods.select { |m| m.to_s =~ /^parse_args_/ }
  parse_methods.collect { |m| m.to_s.split('_').slice(2..-1).join('_') }
end