Land #18446, Make DomainControllerRhost optional

This PR makes the DomainControllerRhosts option optional,
even when auth is set to kerberos. This change requires
rapid7/rex-socket#64 which was released in the rex-socket 1.5.5 gem.
This commit is contained in:
Jack Heysel 2023-12-05 17:47:45 -05:00
commit 9f126a4d24
No known key found for this signature in database
GPG Key ID: D373F2C24A2A1E70
8 changed files with 61 additions and 18 deletions

View File

@ -418,7 +418,7 @@ GEM
metasm
rex-core
rex-text
rex-socket (0.1.54)
rex-socket (0.1.55)
rex-core
rex-sslscan (0.1.10)
rex-core

View File

@ -130,11 +130,13 @@ Required options:
* `${Prefix}::Rhostname` -- The hostname of the target system. This value should be either the hostname `WIN-MIJZ318SQH` or
the FQDN like `WIN-MIJZ318SQH.msflab.local`. i.e. `Smb::Rhostname=WIN-MIJZ318SQH.msflab.local`
* `${Prefix}Domain` -- The domain name of the target system, e.g. `msflab.local`. i.e. `SmbDomain=msflab.local`
* `DomainControllerRhost` -- The IP address of the domain controller to use for kerberos authentication. i.e. `DomainControllerRhost=192.168.123.13`
Optional options:
* `DomainControllerRhost` -- The IP address or hostname of the domain controller to use for Kerberos authentication.
i.e. `DomainControllerRhost=192.168.123.13`. If this value is not specified, Metasploit will look it up via the
realm's (the `${Prefix}Domain` option) SRV record in DNS.
* `${Prefix}::Krb5Ccname` -- The path to a CCACHE file to use for authentication. This is comparable to setting the
`KRB5CCNAME` environment variable for other tools. If specified, the tickets it contains will be used. i.e. `KRB5CCNAME=/path/to/Administrator.ccache`
`KRB5CCNAME` environment variable for other tools. If specified, the tickets it contains will be used. i.e. `KRB5CCNAME=/path/to/Administrator.ccache`.
* `KrbCacheMode` -- The cache storage mode to use, one of the following four options:
* `none` -- No cache storage is used, new tickets are requested and no tickets are stored.
* `read-only` -- Stored tickets from the cache will be used, but no new tickets are stored.

View File

@ -55,13 +55,12 @@ module Metasploit
when Msf::Exploit::Remote::AuthOption::KERBEROS
raise Msf::ValidationError, 'The Ldap::Rhostname option is required when using Kerberos authentication.' if opts[:ldap_rhostname].blank?
raise Msf::ValidationError, 'The DOMAIN option is required when using Kerberos authentication.' if opts[:domain].blank?
raise Msf::ValidationError, 'The DomainControllerRhost is required when using Kerberos authentication.' if opts[:domain_controller_rhost].blank?
offered_etypes = Msf::Exploit::Remote::AuthOption.as_default_offered_etypes(opts[:ldap_krb_offered_enc_types])
raise Msf::ValidationError, 'At least one encryption type is required when using Kerberos authentication.' if offered_etypes.empty?
kerberos_authenticator = Msf::Exploit::Remote::Kerberos::ServiceAuthenticator::LDAP.new(
host: opts[:domain_controller_rhost],
host: opts[:domain_controller_rhost].blank? ? nil : opts[:domain_controller_rhost],
hostname: opts[:ldap_rhostname],
realm: opts[:domain],
username: opts[:username],

View File

@ -90,6 +90,7 @@ class Msf::Exploit::Remote::Kerberos::ServiceAuthenticator::Base
:print_status,
:print_good,
:vprint_error,
:vprint_status,
:workspace
# Flags - https://datatracker.ietf.org/doc/html/rfc4121#section-4.1.1.1
@ -190,6 +191,23 @@ class Msf::Exploit::Remote::Kerberos::ServiceAuthenticator::Base
port
end
def connect(options = {})
unless options[:rhost]
unless (host = @host)
vprint_status("Using DNS to lookup the KDC for #{realm}...")
host = ::Rex::Socket.getresources("_kerberos._tcp.#{realm}", :SRV)&.sample
if host.nil?
raise ::Rex::Proto::Kerberos::Model::Error::KerberosError.new("Failed to lookup the KDC")
end
print_status("Using KDC #{host} for realm #{realm}")
@host = host
end
options[:rhost] = host
end
super(options)
end
# @param [Hash] options
# @option options [String] :credential An explicit credential object to use for authentication.
# @option options [Rex::Proto::Kerberos::Model::PrincipalName] :sname The target service principal name.
@ -225,7 +243,7 @@ class Msf::Exploit::Remote::Kerberos::ServiceAuthenticator::Base
)
end
if options[:credential]
print_status("#{peer} - Using cached credential for #{options[:credential].server} #{options[:credential].client}")
print_status("Using cached credential for #{options[:credential].server} #{options[:credential].client}")
end
end
end
@ -345,7 +363,7 @@ class Msf::Exploit::Remote::Kerberos::ServiceAuthenticator::Base
end
if credential
print_status("#{peer} - Using cached credential for #{credential.server} #{credential.client}")
print_status("Using cached credential for #{credential.server} #{credential.client}")
return credential
end
@ -363,7 +381,7 @@ class Msf::Exploit::Remote::Kerberos::ServiceAuthenticator::Base
def request_tgs_only(credential, options = {})
# load a cached TGS
if (ccache = get_cached_credential(options))
print_status("#{peer} - Using cached credential for #{ccache.server} #{ccache.client}")
print_status("Using cached credential for #{ccache.server} #{ccache.client}")
return ccache
end

View File

@ -347,12 +347,11 @@ module Exploit::Remote::MSSQL
fail_with(Msf::Exploit::Failure::BadConfig, 'The Mssql::Rhostname option is required when using Kerberos authentication.') if datastore['Mssql::Rhostname'].blank?
fail_with(Msf::Exploit::Failure::BadConfig, 'The DOMAIN option is required when using Kerberos authentication.') if datastore['DOMAIN'].blank?
fail_with(Msf::Exploit::Failure::BadConfig, 'The DomainControllerRhost is required when using Kerberos authentication.') if datastore['DomainControllerRhost'].blank?
offered_etypes = Msf::Exploit::Remote::AuthOption.as_default_offered_etypes(datastore['MssqlKrbOfferedEncryptionTypes'])
fail_with(Msf::Exploit::Failure::BadConfig, 'At least one encryption type is required when using Kerberos authentication.') if offered_etypes.empty?
kerberos_authenticator = Msf::Exploit::Remote::Kerberos::ServiceAuthenticator::MSSQL.new(
host: datastore['DomainControllerRhost'],
host: datastore['DomainControllerRhost'].blank? ? nil : datastore['DomainControllerRhost'],
hostname: datastore['Mssql::Rhostname'],
proxies: datastore['Proxies'],
mssql_port: rport,

View File

@ -153,12 +153,11 @@ module Msf
if datastore['SMB::Auth'] == Msf::Exploit::Remote::AuthOption::KERBEROS
fail_with(Msf::Exploit::Failure::BadConfig, 'The Smb::Rhostname option is required when using Kerberos authentication.') if datastore['Smb::Rhostname'].blank?
fail_with(Msf::Exploit::Failure::BadConfig, 'The SMBDomain option is required when using Kerberos authentication.') if datastore['SMBDomain'].blank?
fail_with(Msf::Exploit::Failure::BadConfig, 'The DomainControllerRhost is required when using Kerberos authentication.') if datastore['DomainControllerRhost'].blank?
offered_etypes = Msf::Exploit::Remote::AuthOption.as_default_offered_etypes(datastore['Smb::KrbOfferedEncryptionTypes'])
fail_with(Msf::Exploit::Failure::BadConfig, 'At least one encryption type is required when using Kerberos authentication.') if offered_etypes.empty?
kerberos_authenticator = Msf::Exploit::Remote::Kerberos::ServiceAuthenticator::SMB.new(
host: datastore['DomainControllerRhost'],
host: datastore['DomainControllerRhost'].blank? ? nil : datastore['DomainControllerRhost'],
hostname: datastore['Smb::Rhostname'],
proxies: datastore['Proxies'],
realm: datastore['SMBDomain'],

View File

@ -45,7 +45,6 @@ module Exploit::Remote::WinRM
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?
fail_with(Msf::Exploit::Failure::BadConfig, 'The DomainControllerRhost is required when using Kerberos authentication.') if datastore['DomainControllerRhost'].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
@ -80,7 +79,7 @@ module Exploit::Remote::WinRM
case datastore['Winrm::Auth']
when Msf::Exploit::Remote::AuthOption::KERBEROS
kerberos_authenticator = Msf::Exploit::Remote::Kerberos::ServiceAuthenticator::HTTP.new(
host: datastore['DomainControllerRhost'],
host: datastore['DomainControllerRhost'].blank? ? nil : datastore['DomainControllerRhost'],
hostname: datastore['Winrm::Rhostname'],
proxies: datastore['Proxies'],
realm: datastore['DOMAIN'],

View File

@ -2,18 +2,45 @@
require 'spec_helper'
RSpec.describe Msf::Exploit::Remote::Kerberos::ServiceAuthenticator::Base do
subject do
described_class.new(
let(:params) {
{
realm: 'demo.local',
hostname: 'mock_hostname',
username: 'mock_username',
password: 'mock_password',
host: 'mock_host',
host: '127.0.0.1',
port: 88,
timeout: 25,
framework: instance_double(::Msf::Framework),
framework_module: instance_double(::Msf::Module)
)
}
}
subject do
described_class.new(**params)
end
describe '#connect' do
before(:each) do
allow(params[:framework_module]).to receive(:framework)
allow(params[:framework_module]).to receive(:print_status)
allow(params[:framework_module]).to receive(:vprint_status)
end
context 'when host is nil' do
it 'resolves it to a hostname' do
expect(::Rex::Socket).to receive(:getresources).with("_kerberos._tcp.#{params[:realm]}", :SRV).and_return(['mock_host'])
instance = described_class.new(**params.merge(host: nil))
instance.connect
expect(instance.host).to eq('mock_host')
end
it 'raises a KerberosError on failure' do
expect(::Rex::Socket).to receive(:getresources).with("_kerberos._tcp.#{params[:realm]}", :SRV).and_return([])
instance = described_class.new(**params.merge(host: nil))
expect { instance.connect }.to raise_error(::Rex::Proto::Kerberos::Model::Error::KerberosError)
end
end
end
describe "#validate_response!" do