Files
metasploit-gs/lib/msf/ui/console/command_dispatcher/exploit.rb
T

335 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::ModuleArgumentParsing
include Msf::Ui::Console::ModuleOptionTabCompletion
#
# 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 ::Msf::OptionValidateError => e
::Msf::Ui::Formatter::OptionValidateError.print_error(mod, e)
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)
return false unless (args = parse_exploit_opts(args))
any_session = false
force = args[:force] || false
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
mod_with_opts = mod.replicant
mod_with_opts.datastore.import_options_from_hash(args[:datastore_options])
rhosts = mod_with_opts.datastore['RHOSTS']
has_rhosts_option = mod.options.include?('RHOSTS') ||
mod.options.include?('RHOST') ||
mod.options.include?('rhost') ||
mod.options.include?('rhosts')
opts = {
'Encoder' => args[:encoder] || mod_with_opts.datastore['ENCODER'],
'Payload' => args[:payload] || mod_with_opts.datastore['PAYLOAD'],
'Target' => args[:target] || mod_with_opts.datastore['TARGET'],
'Nop' => args[:nop] || mod_with_opts.datastore['NOP'],
'LocalInput' => driver.input,
'LocalOutput' => driver.output,
'RunAsJob' => args[:jobify] || mod_with_opts.passive?,
'Background' => args[:background] || false,
'Force' => force,
'Quiet' => args[:quiet] || false
}
begin
mod_with_opts.validate
rescue ::Msf::OptionValidateError => e
::Msf::Ui::Formatter::OptionValidateError.print_error(mod_with_opts, e)
return false
end
if rhosts && has_rhosts_option
rhosts_walker = Msf::RhostsWalker.new(rhosts, mod_with_opts.datastore)
rhosts_walker_count = rhosts_walker.count
rhosts_walker = rhosts_walker.to_enum
end
# For multiple targets exploit attempts.
if rhosts_walker && rhosts_walker_count > 1
opts[:multi] = true
rhosts_walker.with_index do |datastore, index|
nmod = mod_with_opts.replicant
nmod.datastore.merge!(datastore)
# If rhost is the last target, let exploit handler stop.
is_last_target = (index + 1) == rhosts_walker_count
opts["multi"] = false if is_last_target
# Catch the interrupt exception to stop the whole module during exploit
begin
print_status("Exploiting target #{datastore['RHOSTS']}")
session = exploit_single(nmod, opts)
rescue ::Interrupt
print_status("Stopping exploiting current target #{datastore['RHOSTS']}...")
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
nmod = mod_with_opts.replicant
if rhosts_walker && rhosts_walker_count == 1
nmod.datastore.merge!(rhosts_walker.next)
end
session = exploit_single(nmod, 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'] && nmod.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 #{nmod.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 launch.
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_with_opts.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_module_run_or_check_usage(command: :run, options: @@exploit_opts)
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
alias cmd_rerun_tabs cmd_run_tabs
alias cmd_rexploit_tabs cmd_exploit_tabs
def cmd_rexploit_help
print_module_run_or_check_usage(
command: :rexploit,
description: 'Reloads a module, stopping any associated job, and launches an exploitation attempt.',
options: @@exploit_opts
)
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?
# @param [Msf::Module] mod
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|
if mod.datastore.is_a?(Msf::DataStoreWithFallbacks)
payload_defaults = { 'PAYLOAD' => payload }
# Set LHOST if this is a reverse payload
if payload.index('reverse')
payload_defaults['LHOST'] = lhost
end
mod.datastore.import_defaults_from_hash(payload_defaults, imported_by: 'choose_payload')
else
mod.datastore['PAYLOAD'] = payload
# Set LHOST if this is a reverse payload
if payload.index('reverse')
mod.datastore['LHOST'] = lhost
end
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_bash',
'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