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(active_module, 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
|