lib/msf/ui/console/module_argument_parsing.rb
# -*- coding: binary -*-
require 'addressable'
require 'msf/ui/console/command_dispatcher'
module Msf
module Ui
module Console
###
#
# A centralized mixin to ensure that options are consistently parsed across all module types
# when running a module's cmd_run/cmd_check/cmd_exploit arguments
#
###
module ModuleArgumentParsing
# Options which are standard and predictable across all modules
@@module_opts = Rex::Parser::Arguments.new(
['-h', '--help'] => [ false, 'Help banner.' ],
['-j', '--job'] => [ false, 'Run in the context of a job.' ],
['-J', '--foreground'] => [ false, 'Force running in the foreground, even if passive.' ],
['-o', '--options'] => [ true, 'A comma separated list of options in VAR=VAL format.', '<options>' ],
['-q', '--quiet'] => [ false, 'Run the module in quiet mode with no output' ],
['-r', '--reload-libs'] => [ false, 'Reload all libraries before running.' ]
)
@@module_opts_with_action_support = @@module_opts.merge(
['-a', '--action'] => [ true, 'The action to use. If none is specified, ACTION is used.', '<action>']
)
@@exploit_opts = @@module_opts.merge(
['-e', '--encoder'] => [ true, 'The payload encoder to use. If none is specified, ENCODER is used.', '<encoder>' ],
['-f', '--force-run'] => [ false, 'Force the exploit to run regardless of the value of MinimumRank.' ],
['-n', '--nop-generator'] => [ true, 'The NOP generator to use. If none is specified, NOP is used.', '<generator>' ],
['-p', '--payload'] => [ true, 'The payload to use. If none is specified, PAYLOAD is used.', '<payload>' ],
['-t', '--target'] => [ true, 'The target index to use. If none is specified, TARGET is used.', '<target>' ],
['-z', '--no-interact'] => [ false, 'Do not interact with the session after successful exploitation.' ]
)
def parse_check_opts(args)
help_cmd = proc do |_result|
cmd_check_help
end
parse_opts(@@module_opts_with_action_support, args, help_cmd: help_cmd)&.slice(:datastore_options, :reload_libs)
end
def parse_run_opts(args, action: nil)
help_cmd = proc do |result|
if result[:action].nil?
cmd_run_help
else
cmd_action_help(action)
end
end
parse_opts(@@module_opts_with_action_support, args, help_cmd: help_cmd, action: action)
end
def parse_exploit_opts(args)
help_cmd = proc do |_result|
cmd_exploit_help
end
parse_opts(@@exploit_opts, args, help_cmd: help_cmd)&.except(:action)
end
def print_module_run_or_check_usage(command:, description: nil, options: @@module_opts)
description ||= command == :check ? 'Check if the target is vulnerable' : "Run the current #{name.downcase} module"
is_http_mod = mod.is_a?(Msf::Exploit::Remote::HttpClient)
is_smb_mod = mod.is_a?(Msf::Exploit::Remote::SMB::Client) || mod.options.include?('SMBUser')
is_mysql_mod = mod.is_a?(Msf::Exploit::Remote::MYSQL)
print_line("Usage: #{command} [options] [RHOSTS]")
print_line('')
print_line(description)
print_line(options.usage)
print_line('Examples:')
print_line('')
print_line(" #{command} 192.168.1.123")
print_line(" #{command} 192.168.1.1-192.168.1.254")
print_line(" #{command} file:///tmp/rhost_list.txt")
print_line(" #{command} http://192.168.1.123/foo") if is_http_mod
print_line(" #{command} http://user:pass@192.168.1.123/foo") if is_http_mod
print_line(" #{command} HttpTrace=true http://192.168.1.123/foo") if is_http_mod
print_line(" #{command} mysql://user:pass@192.168.1.123") if is_mysql_mod
print_line(" #{command} SQL='select version()' mysql://user:pass@192.168.1.123") if is_mysql_mod && mod.options.include?('SQL')
print_line(" #{command} smb://192.168.1.123") if is_smb_mod
print_line(" #{command} smb://user:pass@192.168.1.123") if is_smb_mod
print_line(" #{command} LPATH=/tmp/foo.txt smb://user:pass@192.168.1.123/share_name/foo.txt") if is_smb_mod && mod.options.include?('RPATH')
print_line('')
print_line('Learn more at https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html')
print_line('')
end
protected
def parse_opts(opts, args, help_cmd:, action: nil)
result = {
jobify: false,
quiet: false,
datastore_options: {},
action: action || mod.datastore['ACTION']
}
datastore_options = result[:datastore_options]
opts.parse(args) do |opt, _idx, val|
case opt
when '-e'
result[:encoder] = val
when '-f'
result[:force] = true
when '-j'
result[:jobify] = true
when '-J'
result[:jobify] = false
when '-n'
result[:nop] = val
when '-o'
if val.nil?
print_error('Missing OptionStr value')
help_cmd.call result
return
end
val << '=' unless val.include?('=')
val.split(',').each do |opt|
name, value = opt.split('=', 2)
append_datastore_option(datastore_options, name, value)
end
when '-p'
result[:payload] = val
when '-r'
result[:reload_libs] = true
when '-t'
result[:target] = val.to_i
when '-z'
result[:background] = true
when '-a'
result[:action] = val
when '-q'
result[:quiet] = true
when '-h'
help_cmd.call result
return
else
if val && val[0] == '-'
help_cmd.call result
return
end
if resembles_datastore_assignment?(val)
name, val = val.split('=', 2)
append_datastore_option(datastore_options, name, val)
elsif resembles_rhost_value?(val)
append_datastore_option(datastore_options, 'RHOSTS', val)
else
print_error("Invalid argument #{val}")
help_cmd.call result
return
end
end
end
result
end
def resembles_datastore_assignment?(val)
return false unless val
valid_option_regex = /^(\w|::)+=.*/
valid_option_regex.match?(val)
end
def resembles_rhost_value?(val)
return false unless val
::Addressable::URI.parse(val)
true
rescue ::Addressable::URI::InvalidURIError => _e
false
end
def append_datastore_option(datastore_options, name, value)
if name.casecmp?('RHOST') || name.casecmp?('RHOSTS')
new_value = quote_whitespaced_value(value)
if !datastore_options['RHOSTS']
datastore_options['RHOSTS'] = new_value
else
datastore_options['RHOSTS'] = "#{datastore_options['RHOSTS']} #{new_value}"
end
else
datastore_options[name.upcase] = value
end
datastore_options
end
# Wraps values which contain spaces in quotes to ensure it's handled correctly later
def quote_whitespaced_value(val)
val.include?(' ') ? "\"#{val}\"" : val
end
end
end
end
end