Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make the DomainControllerRhost optional #18446

Merged
merged 2 commits into from
Dec 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
3 changes: 1 addition & 2 deletions lib/metasploit/framework/ldap/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,12 @@ def ldap_connect_opts(rhost, rport, connect_timeout, ssl: true, opts: {})
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],
Expand Down
24 changes: 21 additions & 3 deletions lib/msf/core/exploit/remote/kerberos/service_authenticator/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,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
Expand Down Expand Up @@ -183,6 +184,23 @@ def rport
port
end

def connect(options = {})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Placeholder comment: For something like a kerberos smb login bruteforcer, will this end making hundreds of DNS requests

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CachedResolver doesn't care how many requests you make, so long as you're within the record's TTL (or create a static entry after the first lookup in the brute mixin) it'll pull from the cache.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I was thinking that caching should be the resolvers responsibility if it needs to be implemented.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
Expand Down Expand Up @@ -213,7 +231,7 @@ def authenticate(options = {})
)
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}")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed the #{peer} - prefix from these messages about using cached credentials because they could occur before a connection was made meaning that the peer host may not have been populated leading to :88 - being used. Since communication doesn't always occur when using a cached credential, it doesn't make much sense to print the peer information anyways.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You know... now that we have intrinsic name lookup facilities in our socket library, maybe we could start treating valid hostnames as peers?

end
end
end
Expand Down Expand Up @@ -325,7 +343,7 @@ def request_tgt_only(options = {})
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

Expand All @@ -343,7 +361,7 @@ def request_tgt_only(options = {})
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

Expand Down
3 changes: 1 addition & 2 deletions lib/msf/core/exploit/remote/mssql.rb
Original file line number Diff line number Diff line change
Expand Up @@ -347,12 +347,11 @@ def mssql_login(user='sa', pass='', db='')

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,
Expand Down
3 changes: 1 addition & 2 deletions lib/msf/core/exploit/remote/smb/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -153,12 +153,11 @@ def smb_login(simple_client = self.simple)
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'],
Expand Down
3 changes: 1 addition & 2 deletions lib/msf/core/exploit/remote/winrm.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ 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?
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
Expand Down Expand Up @@ -80,7 +79,7 @@ def create_winrm_connection
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'],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down