176 lines
6.3 KiB
Ruby
176 lines
6.3 KiB
Ruby
# -*- 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
|