# -*- 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) opts = { 'uri' => datastore['URI'], 'data' => Rex::Text.rand_text_alpha(8) } c = connect(opts) to = opts[:timeout] || timeout ctype = "application/soap+xml;charset=UTF-8" resp, c = send_request_cgi(opts.merge({ 'uri' => opts['uri'], 'method' => 'POST', 'ctype' => ctype, 'data' => opts['data'] }), to) return resp 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,c = send_request_ntlm(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,c = send_request_ntlm(winrm_cmd_msg(cmd, shell_id),timeout) cmd_id = winrm_get_cmd_id(resp) resp,c = send_request_ntlm(winrm_cmd_recv_msg(shell_id,cmd_id),timeout) streams = winrm_get_cmd_streams(resp) resp,c = send_request_ntlm(winrm_terminate_cmd_msg(shell_id,cmd_id),timeout) resp,c = send_request_ntlm(winrm_delete_shell_msg(shell_id)) return streams end def winrm_run_cmd_hanging(cmd, timeout=20) resp,c = send_request_ntlm(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,c = send_request_ntlm(winrm_cmd_msg(cmd, shell_id),timeout) cmd_id = winrm_get_cmd_id(resp) resp,c = send_request_ntlm(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 send_request_ntlm(data, timeout = 20) opts = { 'uri' => datastore['URI'], 'data' => data, 'username' => datastore['USERNAME'], 'password' => datastore['PASSWORD'] } ntlm_options = { :signing => false, :usentlm2_session => datastore['NTLM::UseNTLM2_session'], :use_ntlmv2 => datastore['NTLM::UseNTLMv2'], :send_lm => datastore['NTLM::SendLM'], :send_ntlm => datastore['NTLM::SendNTLM'] } ntlmssp_flags = NTLM_UTILS.make_ntlm_flags(ntlm_options) workstation_name = Rex::Text.rand_text_alpha(rand(8)+1) domain_name = datastore['DOMAIN'] ntlm_message_1 = "NEGOTIATE " + Rex::Text::encode_base64(NTLM_UTILS::make_ntlmssp_blob_init( domain_name, workstation_name, ntlmssp_flags)) to = opts[:timeout] || timeout begin c = connect(opts) ctype = "application/soap+xml;charset=UTF-8" # First request to get the challenge r = c.request_cgi(opts.merge({ 'uri' => opts['uri'], 'method' => 'POST', 'ctype' => ctype, 'headers' => { 'Authorization' => ntlm_message_1}, 'data' => opts['data'] })) resp = c.send_recv(r, to) unless resp.kind_of? Rex::Proto::Http::Response return [nil,nil] end return [nil,nil] if resp.code == 404 return [nil,nil] unless resp.code == 401 && resp.headers['WWW-Authenticate'] # Get the challenge and craft the response ntlm_challenge = resp.headers['WWW-Authenticate'].match(/NEGOTIATE ([A-Z0-9\x2b\x2f=]+)/i)[1] return [nil,nil] unless ntlm_challenge #old and simplier method but not compatible with windows 7/2008r2 #ntlm_message_2 = Rex::Proto::NTLM::Message.decode64(ntlm_challenge) #ntlm_message_3 = ntlm_message_2.response( {:user => opts['username'],:password => opts['password']}, {:ntlmv2 => true}) ntlm_message_2 = Rex::Text::decode_base64(ntlm_challenge) blob_data = NTLM_UTILS.parse_ntlm_type_2_blob(ntlm_message_2) challenge_key = blob_data[:challenge_key] server_ntlmssp_flags = blob_data[:server_ntlmssp_flags] #else should raise an error #netbios name default_name = blob_data[:default_name] || '' #netbios domain default_domain = blob_data[:default_domain] || '' #dns name dns_host_name = blob_data[:dns_host_name] || '' #dns domain dns_domain_name = blob_data[:dns_domain_name] || '' #Client time chall_MsvAvTimestamp = blob_data[:chall_MsvAvTimestamp] || '' spnopt = {:use_spn => datastore['NTLM::SendSPN'], :name => self.rhost} resp_lm, resp_ntlm, client_challenge, ntlm_cli_challenge = NTLM_UTILS.create_lm_ntlm_responses(opts['username'], opts['password'], challenge_key, domain_name, default_name, default_domain, dns_host_name, dns_domain_name, chall_MsvAvTimestamp, spnopt, ntlm_options) ntlm_message_3 = NTLM_UTILS.make_ntlmssp_blob_auth(domain_name, workstation_name, opts['username'], resp_lm, resp_ntlm, '', ntlmssp_flags) ntlm_message_3 = Rex::Text::encode_base64(ntlm_message_3) # Send the response r = c.request_cgi(opts.merge({ 'uri' => opts['uri'], 'method' => 'POST', 'ctype' => ctype, 'headers' => { 'Authorization' => "NEGOTIATE #{ntlm_message_3}"}, 'data' => opts['data'] })) resp = c.send_recv(r, to, true) unless resp.kind_of? Rex::Proto::Http::Response return [nil,nil] end return [nil,nil] if resp.code == 404 return [resp,c] rescue ::Errno::EPIPE, ::Timeout::Error end 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 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