# -*- coding: binary -*- require 'uri' require 'digest' require 'net/winrm/connection' module Msf module Exploit::Remote::WinRM include Exploit::Remote::NTLM::Client include Exploit::Remote::HttpClient include Msf::Exploit::Remote::Kerberos::Ticket::Storage include Msf::Exploit::Remote::Kerberos::ServiceAuthenticator::Options # # 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 authentication', '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_advanced_options( [ *kerberos_storage_options(protocol: 'Winrm'), *kerberos_auth_options(protocol: 'Winrm', auth_methods: Msf::Exploit::Remote::AuthOption::WINRM_OPTIONS), ], ) register_autofilter_ports([ 80,443,5985,5986 ]) register_autofilter_services(%W{ winrm }) end def check_winrm_parameters if datastore['Winrm::Auth'] == Msf::Exploit::Remote::AuthOption::KERBEROS fail_with(Msf::Exploit::Failure::BadConfig, 'The Winrm::Rhostname option is required when using Kerberos authentication.') if datastore['Winrm::Rhostname'].blank? fail_with(Msf::Exploit::Failure::BadConfig, 'The DOMAIN option is required when using Kerberos authentication.') if datastore['DOMAIN'].blank? offered_etypes = Msf::Exploit::Remote::AuthOption.as_default_offered_etypes(datastore['Winrm::KrbOfferedEncryptionTypes']) fail_with(Msf::Exploit::Failure::BadConfig, 'At least one encryption type is required when using Kerberos authentication.') if offered_etypes.empty? else fail_with(Msf::Exploit::Failure::BadConfig, 'The PASSWORD option is required unless using Kerberos authentication.') if datastore['PASSWORD'].blank? end end # Sets up a connection to a WinRM server, based on the datastore parameters # May use NTLM or Kerberos auth, depending on the params # @return [Net::MsfWinRM::RexWinRMConnection] Connection to the WinRM server def create_winrm_connection rhost = datastore['RHOST'] rport = datastore['RPORT'] uri = datastore['URI'] ssl = datastore['SSL'] schema = ssl ? 'https' : 'http' endpoint = URI.join("#{schema}://#{rhost}:#{rport}", uri) opts = { endpoint: endpoint, host: rhost, port: rport, proxies: datastore['Proxies'], uri: uri, ssl: ssl, transport: :rexhttp, no_ssl_peer_verification: true, operation_timeout: 1, timeout: 20, retry_limit: 1, realm: datastore['DOMAIN'] } case datastore['Winrm::Auth'] when Msf::Exploit::Remote::AuthOption::KERBEROS kerberos_authenticator = Msf::Exploit::Remote::Kerberos::ServiceAuthenticator::HTTP.new( host: datastore['DomainControllerRhost'].blank? ? nil : datastore['DomainControllerRhost'], hostname: datastore['Winrm::Rhostname'], proxies: datastore['Proxies'], realm: datastore['DOMAIN'], username: datastore['USERNAME'], password: datastore['PASSWORD'], timeout: 20, # datastore['timeout'] framework: framework, framework_module: self, cache_file: datastore['Winrm::Krb5Ccname'].blank? ? nil : datastore['Winrm::Krb5Ccname'], offered_etypes: Msf::Exploit::Remote::AuthOption.as_default_offered_etypes(datastore['Winrm::KrbOfferedEncryptionTypes']), mutual_auth: true, use_gss_checksum: true ) opts = opts.merge({ user: '', # Need to provide it, otherwise the WinRM module complains password: '', # Need to provide it, otherwise the WinRM module complains kerberos_authenticator: kerberos_authenticator, vhost: datastore['RHOSTNAME'] }) else opts = opts.merge({ user: datastore['USERNAME'], password: datastore['PASSWORD'] }) end return Net::MsfWinRM::RexWinRMConnection.new(opts) end # Make an unauthenticated request to the WinRM server # @param [Integer] timeout Timeout for the request in seconds # @return [Rex::Proto::Http::Response] The HTTP response from an unauthenticated request def make_unauthenticated_request(timeout = 20) opts = { 'uri' => datastore['URI'], 'method' => 'POST', 'data' => Rex::Text.rand_text_alpha(8), 'ctype' => "application/soap+xml;charset=UTF-8" } send_request_cgi(opts,timeout) end # Parse the available auth methods from a WinRM response # @param [Rex::Proto::Http::Response] resp The HTTP response from the WinRM server # @return [Array] The auth methods parsed from the HTTP response 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 # Parse out the results from a WQL query # @param [Hash] The response from a call to Connection.run_wql # @return [Rex::Text::Table] The results from the WQL query in table form def parse_wql_hash(response) columns = [] rows = [] fragments = response[:xml_fragment] fragments.each do |fragment| row_data = [] fragment.keys.each do |key| unless key.starts_with?('@') # xmlns stuff columns << key.to_s row_data << fragment[key] end end rows << row_data end columns.uniq! response_data = Rex::Text::Table.new( 'Header' => "#{datastore['WQL']} (#{rhost})", 'Indent' => 1, 'Columns' => columns ) rows.each do |row| response_data << row end return response_data end # The namespace for WQL queries # @return [String] The WMI namespace def wmi_namespace return datastore['NAMESPACE'] if datastore['NAMESPACE'] return @namespace_override if @namespace_override return "root/cimv2" end end end