lib/msf/core/exploit/remote/winrm.rb
# -*- 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<String>] 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