Files
metasploit-gs/lib/msf/ui/console/command_dispatcher/exploit.rb
T
Spencer McIntyre a9804727d5 Update the VHOST datastore option for modules
This fixes handling the VHOST datastore option for modules that use the
HTTP Client mixin whereby the IP address was being used since RHOSTS is
resolved.
2021-01-11 10:31:36 -05:00

355 lines
11 KiB
Ruby

# -*- coding: binary -*-
module Msf
module Ui
module Console
module CommandDispatcher
###
#
# Exploit module command dispatcher.
#
###
class Exploit
include Msf::Ui::Console::ModuleCommandDispatcher
include Msf::Ui::Console::ModuleOptionTabCompletion
@@exploit_opts = Rex::Parser::Arguments.new(
"-e" => [ true, "The payload encoder to use. If none is specified, ENCODER is used." ],
"-f" => [ false, "Force the exploit to run regardless of the value of MinimumRank." ],
"-h" => [ false, "Help banner." ],
"-j" => [ false, "Run in the context of a job." ],
"-J" => [ false, "Force running in the foreground, even if passive." ],
"-n" => [ true, "The NOP generator to use. If none is specified, NOP is used." ],
"-o" => [ true, "A comma separated list of options in VAR=VAL format." ],
"-p" => [ true, "The payload to use. If none is specified, PAYLOAD is used." ],
"-t" => [ true, "The target index to use. If none is specified, TARGET is used." ],
"-z" => [ false, "Do not interact with the session after successful exploitation." ])
#
# Returns the hash of exploit module specific commands.
#
def commands
super.update({
"exploit" => "Launch an exploit attempt",
"rcheck" => "Reloads the module and checks if the target is vulnerable",
"rexploit" => "Reloads the module and launches an exploit attempt",
"run" => "Alias for exploit",
"recheck" => "Alias for rcheck",
"rerun" => "Alias for rexploit",
"reload" => "Just reloads the module"
})
end
#
# Returns the name of the command dispatcher.
#
def name
"Exploit"
end
#
# Launches an exploitation single attempt.
#
def exploit_single(mod, opts)
begin
session = mod.exploit_simple(opts)
rescue ::Interrupt
raise $!
rescue ::Exception => e
print_error("Exploit exception (#{mod.refname}): #{e.class} #{e}")
if e.class.to_s != 'Msf::OptionValidateError'
print_error("Call stack:")
e.backtrace.each do |line|
break if line =~ /lib.msf.base.simple/
print_error(" #{line}")
end
end
end
return session
end
#
# Tab completion for the run command
#
def cmd_run_tabs(str, words)
fmt = {
'-e' => [ framework.encoders.map { |refname, mod| refname } ],
'-f' => [ nil ],
'-h' => [ nil ],
'-j' => [ nil ],
'-J' => [ nil ],
'-n' => [ framework.nops.map { |refname, mod| refname } ],
'-o' => [ true ],
'-p' => [ framework.payloads.map { |refname, mod| refname } ],
'-t' => [ true ],
'-z' => [ nil ]
}
flags = tab_complete_generic(fmt, str, words)
options = tab_complete_option(str, words)
flags + options
end
#
# Tab completion for the exploit command
#
alias cmd_exploit_tabs cmd_run_tabs
#
# Launches exploitation attempts.
#
def cmd_exploit(*args)
force = false
module_opts = []
any_session = false
opts = {
'Encoder' => mod.datastore['ENCODER'],
'Payload' => mod.datastore['PAYLOAD'],
'Target' => mod.datastore['TARGET'],
'Nop' => mod.datastore['NOP'],
'OptionStr' => nil,
'LocalInput' => driver.input,
'LocalOutput' => driver.output,
'RunAsJob' => false,
'Background' => false,
'Force' => false
}
if mod.passive?
opts['RunAsJob'] = true
end
@@exploit_opts.parse(args) do |opt, idx, val|
case opt
when '-e'
opts['Encoder'] = val
when '-f'
force = true
when '-j'
opts['RunAsJob'] = true
when '-J'
opts['RunAsJob'] = false
when '-n'
opts['Nop'] = val
when '-o'
module_opts.push(val)
when '-p'
opts['Payload'] = val
when '-t'
opts['Target'] = val.to_i
when '-z'
opts['Background'] = true
when '-h'
cmd_exploit_help
return false
else
if val[0] != '-' && val.match?('=')
module_opts.push(val)
else
cmd_exploit_help
return false
end
end
end
unless module_opts.empty?
opts['OptionStr'] = module_opts.join(',')
end
minrank = RankingName.invert[framework.datastore['MinimumRank']] || 0
if minrank > mod.rank
if force
print_status("Forcing #{mod.refname} to run despite MinimumRank '#{framework.datastore['MinimumRank']}'")
ilog("Forcing #{mod.refname} to run despite MinimumRank '#{framework.datastore['MinimumRank']}'", 'core')
else
print_error("This exploit is below the minimum rank, '#{framework.datastore['MinimumRank']}'.")
print_error("If you really want to run it, do 'exploit -f' or")
print_error("setg MinimumRank to something lower ('manual' is")
print_error("the lowest and would allow running all exploits).")
return
end
end
rhosts = mod.datastore['RHOSTS']
if rhosts
rhosts_opt = Msf::OptAddressRange.new('RHOSTS')
rhosts_range = Rex::Socket::RangeWalker.new(rhosts_opt.normalize(rhosts))
end
# For multiple targets exploit attempts.
if rhosts_range && rhosts_range.length.to_i > 1
opts[:multi] = true
rhosts_range.each do |rhost|
nmod = mod.replicant
nmod.datastore['RHOST'] = rhost
nmod.datastore['VHOST'] = rhosts if (!Rex::Socket.is_ip_addr?(rhosts) && nmod.is_a?(Msf::Exploit::Remote::HttpClient) && nmod.datastore['VHOST'].nil?)
# If rhost is the last target, let exploit handler stop.
opts["multi"] = false if rhost == (Rex::Socket.addr_itoa(rhosts_range.ranges.first.stop))
# Catch the interrupt exception to stop the whole module during exploit
begin
print_status("Exploiting target #{rhost}")
session = exploit_single(nmod, opts)
rescue ::Interrupt
print_status("Stopping exploiting current target #{rhost}...")
print_status("Control-C again to force quit exploiting all targets.")
begin
Rex.sleep(1)
rescue ::Interrupt
raise $!
end
end
# If we were given a session, report it.
if session
print_status("Session #{session.sid} created in the background.")
any_session = true
end
end
# For single target or no rhosts option.
else
# avoid bug when the cidr of rhosts is 32, like 8.8.8.8/32
if rhosts_range && rhosts_range.length == 1
mod.datastore['RHOST'] = (Rex::Socket.addr_itoa(rhosts_range.ranges.first.start))
end
session = exploit_single(mod, opts)
# If we were given a session, let's see what we can do with it
if session
any_session = true
if !opts['Background'] && session.interactive?
# If we aren't told to run in the background and the session can be
# interacted with, start interacting with it by issuing the session
# interaction command.
print_line
driver.run_single("sessions -q -i #{session.sid}")
# Otherwise, log that we created a session
else
# Otherwise, log that we created a session
print_status("Session #{session.sid} created in the background.")
end
elsif opts['RunAsJob'] && mod.job_id
# Indicate if he exploit as a job, indicate such so the user doesn't
# wonder what's up.
print_status("Exploit running as background job #{mod.job_id}.")
# Worst case, the exploit ran but we got no session, bummer.
end
end
# If we didn't get any session and exploit ended luanch.
unless any_session
# If we didn't run a payload handler for this exploit it doesn't
# make sense to complain to the user that we didn't get a session
unless mod.datastore["DisablePayloadHandler"]
fail_msg = 'Exploit completed, but no session was created.'
print_status(fail_msg)
begin
framework.events.on_session_fail(fail_msg)
rescue ::Exception => e
wlog("Exception in on_session_open event handler: #{e.class}: #{e}")
wlog("Call Stack\n#{e.backtrace.join("\n")}")
end
end
end
end
alias cmd_run cmd_exploit
def cmd_exploit_help
print_line "Usage: exploit [options]"
print_line
print_line "Launches an exploitation attempt."
print @@exploit_opts.usage
end
alias cmd_run_help cmd_exploit_help
#
# Reloads an exploit module and checks the target to see if it's
# vulnerable.
#
def cmd_rcheck(*args)
reload()
cmd_check(*args)
end
alias cmd_recheck cmd_rcheck
#
# Reloads an exploit module and launches an exploit.
#
def cmd_rexploit(*args)
return cmd_rexploit_help if args.include? "-h"
# Stop existing job and reload the module
if reload(true)
# Delegate to the exploit command unless the reload failed
cmd_exploit(*args)
end
end
alias cmd_rerun cmd_rexploit
def cmd_rexploit_help
print_line "Usage: rexploit [options]"
print_line
print_line "Reloads a module, stopping any associated job, and launches an exploitation attempt."
print @@exploit_opts.usage
end
alias cmd_rerun_help cmd_rexploit_help
# Select a reasonable default payload and minimally configure it
# TODO: Move this somewhere better or make it more dynamic?
def self.choose_payload(mod)
compatible_payloads = mod.compatible_payloads(
excluded_platforms: ['Multi'] # We don't want to select a multi payload
).map(&:first)
# XXX: Determine LHOST based on RHOST or an arbitrary internet address
lhost = Rex::Socket.source_address(mod.datastore['RHOST'] || '50.50.50.50')
configure_payload = lambda do |payload|
mod.datastore['PAYLOAD'] = payload
# Set LHOST if this is a reverse payload
if payload.index('reverse')
mod.datastore['LHOST'] = lhost
end
payload
end
# If there is only one compatible payload, return it immediately
if compatible_payloads.length == 1
return configure_payload.call(compatible_payloads.first)
end
# XXX: This approach is subpar, and payloads should really be ranked!
preferred_payloads = [
# These payloads are generally reliable and common enough in practice
'/meterpreter/reverse_tcp',
'/shell/reverse_tcp',
'cmd/unix/reverse_netcat',
'cmd/windows/powershell_reverse_tcp',
# Fall back on a generic payload to autoselect a specific payload
'generic/shell_reverse_tcp',
'generic/shell_bind_tcp'
]
# XXX: This is not efficient in the slightest
preferred_payloads.each do |type|
payload = compatible_payloads.find { |name| name.end_with?(type) }
next unless payload
return configure_payload.call(payload)
end
nil
end
end
end end end end