# -*- coding: binary -*- require 'uri' require 'digest' require 'rex/proto/ntlm/crypt' require 'rex/proto/ntlm/constants' require 'rex/proto/ntlm/utils' require 'rex/proto/ntlm/exceptions' module Msf module Exploit::Remote::WinRM include Exploit::Remote::NTLM::Client include Exploit::Remote::HttpClient # # Constants # NTLM_CRYPT ||= Rex::Proto::NTLM::Crypt NTLM_CONST ||= Rex::Proto::NTLM::Constants NTLM_UTILS ||= Rex::Proto::NTLM::Utils NTLM_XCEPT ||= Rex::Proto::NTLM::Exceptions def initialize(info = {}) super register_options( [ Opt::RPORT(5985), OptString.new('DOMAIN', [ true, 'The domain to use for Windows authentification', 'WORKSTATION']), OptString.new('URI', [ true, "The URI of the WinRM service", "/wsman" ]), OptString.new('USERNAME', [ false, 'A specific username to authenticate as' ]), OptString.new('PASSWORD', [ false, 'A specific password to authenticate with' ]), ], self.class ) register_autofilter_ports([ 80,443,5985,5986 ]) register_autofilter_services(%W{ winrm }) end def winrm_poke(timeout = 20) send_winrm_request(Rex::Text.rand_text_alpha(8), timeout) end def parse_auth_methods(resp) return [] unless resp and resp.code == 401 methods = [] methods << "Negotiate" if resp.headers['WWW-Authenticate'].include? "Negotiate" methods << "Kerberos" if resp.headers['WWW-Authenticate'].include? "Kerberos" methods << "Basic" if resp.headers['WWW-Authenticate'].include? "Basic" return methods end def winrm_run_cmd(cmd, timeout=20) resp = send_winrm_request(winrm_open_shell_msg,timeout) if resp.nil? print_error "Recieved no reply from server" return nil end if resp.code == 401 print_error "Login failure! Recheck supplied credentials." return resp .code end unless resp.code == 200 print_error "Got unexpected response: \n #{resp.to_s}" retval = resp.code || 0 return retval end shell_id = winrm_get_shell_id(resp) resp = send_winrm_request(winrm_cmd_msg(cmd, shell_id),timeout) cmd_id = winrm_get_cmd_id(resp) resp = send_winrm_request(winrm_cmd_recv_msg(shell_id,cmd_id),timeout) streams = winrm_get_cmd_streams(resp) resp = send_winrm_request(winrm_terminate_cmd_msg(shell_id,cmd_id),timeout) resp = send_winrm_request(winrm_delete_shell_msg(shell_id)) return streams end def winrm_run_cmd_hanging(cmd, timeout=20) resp = send_winrm_request(winrm_open_shell_msg,timeout) if resp.nil? print_error "Recieved no reply from server" return nil end if resp.code == 401 print_error "Login failure! Recheck supplied credentials." return resp .code end unless resp.code == 200 print_error "Got unexpected response: \n #{resp.to_s}" retval = resp.code || 0 return retval end shell_id = winrm_get_shell_id(resp) resp = send_winrm_request(winrm_cmd_msg(cmd, shell_id),timeout) cmd_id = winrm_get_cmd_id(resp) resp = send_winrm_request(winrm_cmd_recv_msg(shell_id,cmd_id),timeout) streams = winrm_get_cmd_streams(resp) return streams end def winrm_wql_msg(wql) action = winrm_uri_action("wql") contents = winrm_header(action) + winrm_wql_body(wql) msg = winrm_envelope(contents) return msg end def winrm_open_shell_msg action = winrm_uri_action("create_shell") options = winrm_option_set([['WINRS_NOPROFILE', 'FALSE'], ['WINRS_CODEPAGE', '437']]) header_data = action + options contents = winrm_header(header_data) + winrm_open_shell_body msg = winrm_envelope(contents) return msg end def winrm_cmd_msg(cmd,shell_id) action = winrm_uri_action("send_cmd") options = winrm_option_set([['WINRS_CONSOLEMODE_STDIN', 'TRUE'], ['WINRS_SKIP_CMD_SHELL', 'FALSE']]) selectors = winrm_selector_set([['ShellId', shell_id]]) header_data = action + options + selectors contents = winrm_header(header_data) + winrm_cmd_body(cmd) msg = winrm_envelope(contents) return msg end def winrm_cmd_recv_msg(shell_id,cmd_id) action = winrm_uri_action("recv_cmd") selectors = winrm_selector_set([['ShellId', shell_id]]) header_data = action + selectors contents = winrm_header(header_data) + winrm_cmd_recv_body(cmd_id) msg = winrm_envelope(contents) return msg end def winrm_terminate_cmd_msg(shell_id,cmd_id) action = winrm_uri_action("signal_shell") selectors = winrm_selector_set([['ShellId', shell_id]]) header_data = action + selectors contents = winrm_header(header_data) + winrm_terminate_cmd_body(cmd_id) msg = winrm_envelope(contents) return msg end def winrm_delete_shell_msg(shell_id) action = winrm_uri_action("delete_shell") selectors = winrm_selector_set([['ShellId', shell_id]]) header_data = action + selectors contents = winrm_header(header_data) + winrm_empty_body msg = winrm_envelope(contents) return msg end def parse_wql_response(response) return nil if response.nil? xml = response.body columns = [] rows =[] rxml = REXML::Document.new(xml).root items = rxml.elements["///w:Items"] items.elements.to_a("///w:XmlFragment").each do |node| row_data = [] node.elements.to_a.each do |sub_node| columns << sub_node.name row_data << sub_node.text end rows << row_data end columns.uniq! response_data = Rex::Ui::Text::Table.new( 'Header' => "#{datastore['WQL']} (#{rhost})", 'Indent' => 1, 'Columns' => columns ) rows.each do |row| response_data << row end return response_data end def winrm_get_shell_id(response) return nil if response.nil? xml = response.body shell_id = REXML::Document.new(xml).elements["//w:Selector"].text end def winrm_get_cmd_id(response) return nil if response.nil? xml = response.body cmd_id = REXML::Document.new(xml).elements["//rsp:CommandId"].text end def winrm_get_cmd_streams(response) return nil if response.nil? streams = { 'stdout' => '', 'stderr' => '', } xml = response.body rxml = REXML::Document.new(xml).root rxml.elements.to_a("//rsp:Stream").each do |node| next if node.text.nil? streams[node.attributes['Name']] << Rex::Text.decode_base64(node.text) end return streams end def generate_uuid ::Rex::Proto::DCERPC::UUID.uuid_unpack(Rex::Text.rand_text(16)) end def accepts_ntlm_auth parse_auth_methods(winrm_poke).include? "Negotiate" end def target_url proto = "http" if rport == 5986 or datastore['SSL'] proto = "https" end if datastore['VHOST'] return "#{proto}://#{datastore ['VHOST']}:#{rport}#{@uri.to_s}" else return "#{proto}://#{rhost}:#{rport}#{@uri.to_s}" end end def wmi_namespace return datastore['NAMESPACE'] if datastore['NAMESPACE'] return @namespace_override if @namespace_override return "/root/cimv2/" end def send_winrm_request(data, timeout=20) opts = { 'uri' => datastore['URI'], 'method' => 'POST', 'data' => data, 'username' => datastore['USERNAME'], 'password' => datastore['PASSWORD'], 'ctype' => "application/soap+xml;charset=UTF-8" } send_request_cgi(opts,timeout) end private def winrm_option_set(options) xml = "" options.each do |option_pair| xml << winrm_option(*option_pair) end xml << "" return xml end def winrm_option(name,value) %Q{#{value}} end def winrm_selector_set(selectors) xml = "" selectors.each do |selector_pair| xml << winrm_selector(*selector_pair) end xml << "" return xml end def winrm_selector(name,value) %Q{#{value}} end def winrm_wql_body(wql) %Q{ 32000 #{wql} } end def winrm_open_shell_body %q{ stdin stdout stderr } end def winrm_cmd_body(cmd) %Q{ "#{cmd}" } end def winrm_cmd_recv_body(cmd_id) %Q{ stdout stderr } end def winrm_terminate_cmd_body(cmd_id) %Q{ http://schemas.microsoft.com/wbem/wsman/1/windows/shell/signal/terminate } end def winrm_empty_body %q{} end def winrm_envelope(data) %Q{ #{data} } end def winrm_header(data) %Q{ #{target_url} http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous 153600 uuid:#{generate_uuid} PT60S #{data} } end def winrm_uri_action(type) case type when "wql" return %Q{http://schemas.microsoft.com/wbem/wsman/1/wmi#{wmi_namespace}* http://schemas.xmlsoap.org/ws/2004/09/enumeration/Enumerate} when "create_shell" return %q{http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd http://schemas.xmlsoap.org/ws/2004/09/transfer/Create} when "send_cmd" return %q{http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Command} when "recv_cmd" return %q{http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Receive} when "signal_shell" return %q{http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Signal} when "delete_shell" return %q{http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd http://schemas.xmlsoap.org/ws/2004/09/transfer/Delete} end end end end