a9804727d5
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.
355 lines
11 KiB
Ruby
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
|