Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/plugins/request.rb
Views: 11705
require 'uri'12module Msf3class Plugin::Requests < Msf::Plugin45class ConsoleCommandDispatcher6include Msf::Ui::Console::CommandDispatcher78HELP_REGEX = /^-?-h(?:elp)?$/.freeze910def name11'Request'12end1314def commands15{16'request' => "Make a request of the specified type (#{types.join(', ')})"17}18end1920# Dynamically determine the types of requests that are supported based on21# methods prefixed with "parse_args".22#23# @return [Array<String>] The supported request types.24def types25parse_methods = public_methods.select { |m| m.to_s =~ /^parse_args_/ }26parse_methods.collect { |m| m.to_s.split('_').slice(2..-1).join('_') }27end2829# The main handler for the request command.30#31# @param args [Array<String>] The array of arguments provided by the user.32# @return [nil]33def cmd_request(*args)34# short circuit the whole deal if they need help35return help if args.empty?36return help if args.length == 1 && args.first =~ HELP_REGEX3738# detect the request type from the uri which must be the last arg given39uri = args.last40if uri && uri =~ %r{^[A-Za-z]{3,5}://}41type = uri.split('://', 2).first42else43print_error('The last argument must be a valid and supported URI')44return help45end4647# parse options48opts, opt_parser = parse_args(args, type)49if opts && opt_parser50# handle any "global" options51if opts[:output_file]52begin53opts[:output_file] = File.new(opts[:output_file], 'w')54rescue ::Errno::EACCES, Errno::EISDIR, Errno::ENOTDIR55return help(opt_parser, 'Failed to open the specified file for output')56end57end58# hand off the actual request to the appropriate request handler59handler_method = "handle_request_#{type}".to_sym60if respond_to?(handler_method)61# call the appropriate request handler62send(handler_method, opts, opt_parser)63else64# this should be dead code if parse_args is doing it's job correctly65help(opt_parser, "No request handler found for type (#{type}).")66end67elsif types.include? type68help(opt_parser)69else70help71end72end7374# Parse the provided arguments by dispatching to the correct method based75# on the specified type.76#77# @param args [Array<String>] The command line arguments to parse.78# @param type [String] The protocol type that the request is for such as79# HTTP.80# @return [Array<Hash, Rex::Parser::Arguments>] An array with the options81# hash and the argument parser.82def parse_args(args, type = 'http')83type.downcase!84parse_method = "parse_args_#{type}".to_sym85if respond_to?(parse_method)86send(parse_method, args, type)87else88print_error("Unsupported URI type: #{type}")89end90end9192# Parse the provided arguments for making HTTPS requests. The argument flags93# are intended to be similar to the curl utility.94#95# @param args [Array<String>] The command line arguments to parse.96# @param type [String] The protocol type that the request is for.97# @return [Array<Hash, Rex::Parser::Arguments>] An array with the options98# hash and the argument parser.99def parse_args_https(args = [], type = 'https')100# just let http do it101parse_args_http(args, type)102end103104# Parse the provided arguments for making HTTP requests. The argument flags105# are intended to be similar to the curl utility.106#107# @param args [Array<String>] The command line arguments to parse.108# @param type [String] The protocol type that the request is for.109# @return [Array<Hash>, Rex::Parser::Arguments>] An array with the options110# hash and the argument parser.111def parse_args_http(args = [], _type = 'http')112opt_parser = Rex::Parser::Arguments.new(113'-0' => [ false, 'Use HTTP 1.0' ],114'-1' => [ false, 'Use TLSv1 (SSL)' ],115'-2' => [ false, 'Use SSLv2 (SSL)' ],116'-3' => [ false, 'Use SSLv3 (SSL)' ],117'-A' => [ true, 'User-Agent to send to server' ],118'-d' => [ true, 'HTTP POST data' ],119'-G' => [ false, 'Send the -d data with an HTTP GET' ],120'-h' => [ false, 'This help text' ],121'-H' => [ true, 'Custom header to pass to server' ],122'-i' => [ false, 'Include headers in the output' ],123'-I' => [ false, 'Show document info only' ],124'-o' => [ true, 'Write output to <file> instead of stdout' ],125'-u' => [ true, 'Server user and password' ],126'-X' => [ true, 'Request method to use' ]127# '-x' => [ true, 'Proxy to use, format: [proto://][user:pass@]host[:port]' +128# ' Proto defaults to http:// and port to 1080'],129)130131options = {132headers: {},133print_body: true,134print_headers: false,135ssl_version: 'Auto',136user_agent: Rex::UserAgent.session_agent,137version: '1.1'138}139140opt_parser.parse(args) do |opt, _idx, val|141case opt142when '-0'143options[:version] = '1.0'144when '-1'145options[:ssl_version] = 'TLS1'146when '-2'147options[:ssl_version] = 'SSL2'148when '-3'149options[:ssl_version] = 'SSL3'150when '-A'151options[:user_agent] = val152when '-d'153options[:data] = val154options[:method] ||= 'POST'155when '-G'156options[:method] = 'GET'157when HELP_REGEX158# help(opt_parser)159# guard to prevent further option processing & stymie request handling160return [nil, opt_parser]161when '-H'162name, value = val.split(':', 2)163options[:headers][name] = value.to_s.strip164when '-i'165options[:print_headers] = true166when '-I'167options[:print_headers] = true168options[:print_body] = false169options[:method] ||= 'HEAD'170when '-o'171options[:output_file] = File.expand_path(val)172when '-u'173val = val.split(':', 2) # only split on first ':' as per curl:174# from curl man page: "The user name and passwords are split up on the175# first colon, which makes it impossible to use a colon in the user176# name with this option. The password can, still.177options[:auth_username] = val.first178options[:auth_password] = val.last179when '-p'180options[:auth_password] = val181when '-X'182options[:method] = val183# when '-x'184# @TODO proxy185else186options[:uri] = val187end188end189unless options[:uri]190help(opt_parser)191end192options[:method] ||= 'GET'193options[:uri] = URI(options[:uri])194[options, opt_parser]195end196197# Perform an HTTPS request based on the user specified options.198#199# @param opts [Hash] The options to use for making the HTTPS request.200# @option opts [String] :auth_username An optional username to use with201# basic authentication.202# @option opts [String] :auth_password An optional password to use with203# basic authentication. This is only used when :auth_username is204# specified.205# @option opts [String] :data Any data to include within the body of the206# request. Often used with the POST HTTP method.207# @option opts [Hash] :headers A hash of additional headers to include in208# the request.209# @option opts [String] :method The HTTP method to use in the request.210# @option opts [#write] :output_file A file to write the response data to.211# @option opts [Boolean] :print_body Whether or not to print the body of the212# response.213# @option opts [Boolean] :print_headers Whether or not to print the headers214# of the response.215# @option opts [String] :ssl_version The version of SSL to use if the216# request scheme is HTTPS.217# @option opts [String] :uri The target uri to request.218# @option opts [String] :user_agent The value to use in the User-Agent219# header of the request.220# @param opt_parser [Rex::Parser::Arguments] the argument parser for the221# request type.222# @return [nil]223def handle_request_https(opts, opt_parser)224# let http do it225handle_request_http(opts, opt_parser)226end227228# Perform an HTTP request based on the user specified options.229#230# @param opts [Hash] The options to use for making the HTTP request.231# @option opts [String] :auth_username An optional username to use with232# basic authentication.233# @option opts [String] :auth_password An optional password to use with234# basic authentication. This is only used when :auth_username is235# specified.236# @option opts [String] :data Any data to include within the body of the237# request. Often used with the POST HTTP method.238# @option opts [Hash] :headers A hash of additional headers to include in239# the request.240# @option opts [String] :method The HTTP method to use in the request.241# @option opts [#write] :output_file A file to write the response data to.242# @option opts [Boolean] :print_body Whether or not to print the body of the243# response.244# @option opts [Boolean] :print_headers Whether or not to print the headers245# of the response.246# @option opts [String] :ssl_version The version of SSL to use if the247# request scheme is HTTPS.248# @option opts [String] :uri The target uri to request.249# @option opts [String] :user_agent The value to use in the User-Agent250# header of the request.251# @param opt_parser [Rex::Parser::Arguments] the argument parser for the252# request type.253# @return [nil]254def handle_request_http(opts, _opt_parser)255uri = opts[:uri]256http_client = Rex::Proto::Http::Client.new(257uri.host,258uri.port,259{ 'Msf' => framework },260uri.scheme == 'https',261opts[:ssl_version]262)263264if opts[:auth_username]265auth_str = opts[:auth_username] + ':' + opts[:auth_password]266auth_str = 'Basic ' + Rex::Text.encode_base64(auth_str)267opts[:headers]['Authorization'] = auth_str268end269270uri.path = '/' if uri.path.empty?271272begin273http_client.connect274req = http_client.request_cgi(275'agent' => opts[:user_agent],276'data' => opts[:data],277'headers' => opts[:headers],278'method' => opts[:method],279'password' => opts[:auth_password],280'query' => uri.query,281'uri' => uri.path,282'username' => opts[:auth_username],283'version' => opts[:version]284)285286response = http_client.send_recv(req)287rescue ::OpenSSL::SSL::SSLError288print_error('Encountered an SSL error')289rescue ::Errno::ECONNRESET290print_error('The connection was reset by the peer')291rescue ::EOFError, Errno::ETIMEDOUT, Rex::ConnectionError, ::Timeout::Error292print_error('Encountered an error')293ensure294http_client.close295end296297unless response298opts[:output_file].close if opts[:output_file]299return nil300end301302if opts[:print_headers]303output_line(opts, response.cmd_string)304output_line(opts, response.headers.to_s)305end306307output_line(opts, response.body) if opts[:print_body]308if opts[:output_file]309print_status("Wrote #{opts[:output_file].tell} bytes to #{opts[:output_file].path}")310opts[:output_file].close311end312end313314# Output lines based on the provided options. Data is either printed to the315# console or written to a file. Trailing new lines are removed.316#317# @param opts [Hash] The options as parsed from parse_args.318# @option opts [#write, nil] :output_file An optional file to write the319# output to.320# @param line [String] The string to output.321# @return [nil]322def output_line(opts, line)323if opts[:output_file].nil?324if line[-2..] == "\r\n"325print_line(line[0..-3])326elsif line[-1] == "\n"327print_line(line[0..-2])328else329print_line(line)330end331else332opts[:output_file].write(line)333end334end335336# Print the appropriate help text depending on an optional option parser.337#338# @param opt_parser [Rex::Parser::Arguments] the argument parser for the339# request type.340# @param msg [String] the first line of the help text to display to the341# user.342# @return [nil]343def help(opt_parser = nil, msg = 'Usage: request [options] uri')344print_line(msg)345if opt_parser346print_line(opt_parser.usage)347else348print_line("Supported uri types are: #{types.collect { |t| t + '://' }.join(', ')}")349print_line('To see usage for a specific uri type, use request -h uri')350end351end352353end354355def initialize(framework, opts)356super357add_console_dispatcher(ConsoleCommandDispatcher)358end359360def cleanup361remove_console_dispatcher('Request')362end363364def name365'Request'366end367368def desc369'Make requests from within Metasploit using various protocols.'370end371372end373end374375376