Add database ref opts for kerberos and pkcs12
This commit is contained in:
+1
-1
@@ -683,4 +683,4 @@ DEPENDENCIES
|
||||
yard
|
||||
|
||||
BUNDLED WITH
|
||||
2.5.10
|
||||
2.5.22
|
||||
|
||||
@@ -158,7 +158,7 @@ class Msf::Exploit::Remote::Kerberos::ServiceAuthenticator::Base
|
||||
if cache_file.present?
|
||||
# the cache file is only used for loading credentials, it is *not* written to
|
||||
load_sname_hostname_credential_result = load_credential_from_file(cache_file, sname: nil, sname_hostname: @hostname)
|
||||
credential = load_sname_hostname_credential_result[:credential]
|
||||
credential = load_sname_hostname_credential_result&.fetch(:credential, nil)
|
||||
serviceclass = build_spn&.name_string&.first
|
||||
if credential && credential.server.components[0] != serviceclass
|
||||
old_sname = credential.server.components.snapshot.join('/')
|
||||
@@ -170,12 +170,14 @@ class Msf::Exploit::Remote::Kerberos::ServiceAuthenticator::Base
|
||||
credential.ticket = ticket.encode
|
||||
elsif credential.nil? && hostname.present?
|
||||
load_sname_krbtgt_hostname_credential_result = load_credential_from_file(cache_file, sname: "krbtgt/#{hostname.split('.', 2).last}")
|
||||
credential = load_sname_krbtgt_hostname_credential_result[:credential]
|
||||
credential = load_sname_krbtgt_hostname_credential_result&.fetch(:credential, nil)
|
||||
end
|
||||
if credential.nil?
|
||||
print_error("Failed to load a usable credential from ticket file: #{cache_file}")
|
||||
if load_sname_hostname_credential_result
|
||||
print_error("Attempt failed to find a valid credential in #{cache_file} for #{load_sname_hostname_credential_result[:filter].map { |k, v| "#{k}=#{v.inspect}" }.join(', ')}:")
|
||||
print_error(load_sname_hostname_credential_result[:filter_reasons].join("\n").indent(2))
|
||||
end
|
||||
|
||||
if load_sname_krbtgt_hostname_credential_result
|
||||
print_error("Attempt failed to find a valid credential in #{cache_file} for #{load_sname_krbtgt_hostname_credential_result[:filter].map { |k, v| "#{k}=#{v.inspect}" }.join(', ')}")
|
||||
@@ -1065,14 +1067,25 @@ class Msf::Exploit::Remote::Kerberos::ServiceAuthenticator::Base
|
||||
)
|
||||
end
|
||||
|
||||
# Load a credential object from a file for authentication. Credentials in the file will be filtered by multiple
|
||||
# Load a credential object from a file or database entry for authentication. Credentials in the credential cache will be filtered by multiple
|
||||
# attributes including their timestamps to ensure that the returned credential appears usable.
|
||||
#
|
||||
# @param [String] path The path to load a credential object from
|
||||
# @return [Hash] :credential [Rex::Proto::Kerberos::CredentialCache::Krb5CacheCredential] the credential object for authentication
|
||||
# @return [Hash] :filter_reasons [Array<String>] the reasons for filtering tickets
|
||||
def load_credential_from_file(path, options = {})
|
||||
# Load a database reference or a path
|
||||
if path&.start_with?('id:')
|
||||
id = path.delete_prefix('id:')
|
||||
storage = Msf::Exploit::Remote::Kerberos::Ticket::Storage::ReadOnly.new(framework: framework)
|
||||
cache = storage.tickets({ id: id }).first&.ccache
|
||||
unless cache
|
||||
wlog("Invalid cache id #{id} provided")
|
||||
return { credential: nil }
|
||||
end
|
||||
else
|
||||
unless File.readable?(path.to_s)
|
||||
wlog("Failed to load ticket file '#{path}' (file not readable)")
|
||||
return nil
|
||||
end
|
||||
|
||||
@@ -1082,6 +1095,7 @@ class Msf::Exploit::Remote::Kerberos::ServiceAuthenticator::Base
|
||||
elog("Failed to load ticket file '#{path}' (parsing failed)", error: e)
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
sname = options.fetch(:sname) { build_spn&.to_s }
|
||||
sname_hostname = options.fetch(:sname_hostname, nil)
|
||||
|
||||
@@ -41,7 +41,7 @@ module Msf::Exploit::Remote::Kerberos::ServiceAuthenticator::Options
|
||||
[false, 'The resolvable rhost for the Domain Controller'],
|
||||
conditions: option_conditions
|
||||
),
|
||||
Msf::OptPath.new(
|
||||
Msf::OptKerberosCredentialCache.new(
|
||||
"#{protocol}::Krb5Ccname",
|
||||
[false, 'The ccache file to use for kerberos authentication', nil],
|
||||
conditions: option_conditions
|
||||
|
||||
@@ -40,7 +40,7 @@ module Msf
|
||||
Opt::Proxies,
|
||||
*kerberos_storage_options(protocol: 'LDAP'),
|
||||
*kerberos_auth_options(protocol: 'LDAP', auth_methods: Msf::Exploit::Remote::AuthOption::LDAP_OPTIONS),
|
||||
Msf::OptPath.new('LDAP::CertFile', [false, 'The path to the PKCS12 (.pfx) certificate file to authenticate with'], conditions: ['LDAP::Auth', '==', Msf::Exploit::Remote::AuthOption::SCHANNEL]),
|
||||
Msf::OptPkcs12Cert.new('LDAP::CertFile', [false, 'The path to the PKCS12 (.pfx) certificate file to authenticate with'], conditions: ['LDAP::Auth', '==', Msf::Exploit::Remote::AuthOption::SCHANNEL]),
|
||||
OptFloat.new('LDAP::ConnectTimeout', [true, 'Timeout for LDAP connect', 10.0]),
|
||||
OptEnum.new('LDAP::Signing', [true, 'Use signed and sealed (encrypted) LDAP', 'auto', %w[ disabled auto required ]])
|
||||
]
|
||||
|
||||
@@ -20,11 +20,20 @@ module Msf::Exploit::Remote::Pkcs12
|
||||
# @param [String] cert_pass The certificate password
|
||||
# @param [String] workspace The workspace to restrict searches to
|
||||
def read_pkcs12_cert_path(cert_path, cert_pass = '', workspace: nil)
|
||||
if cert_path&.start_with?('id:')
|
||||
core = framework.db.creds({ workspace: workspace, id: cert_path.delete_prefix('id:') }).first
|
||||
raise Msf::ValidationError, 'Invalid cert id provided' unless core
|
||||
raise Msf::ValidationError, 'Invalid cert id provided - not a pkcs12 credential' unless core.private.type == 'Metasploit::Credential::Pkcs12'
|
||||
|
||||
data = Base64.decode64(core.private.data)
|
||||
else
|
||||
is_readable = ::File.file?(cert_path) && ::File.readable?(cert_path)
|
||||
raise Msf::ValidationError, 'Failed to load the PFX certificate file. The path was not a readable file.' unless is_readable
|
||||
data = File.binread(cert_path)
|
||||
end
|
||||
|
||||
begin
|
||||
# TODO: Is it possible to read the cert pass from the db?
|
||||
pkcs12 = OpenSSL::PKCS12.new(data, cert_pass)
|
||||
rescue StandardError => e
|
||||
raise Msf::ValidationError, "Failed to load the PFX file (#{e})"
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
# -*- coding: binary -*-
|
||||
|
||||
module Msf
|
||||
###
|
||||
#
|
||||
# Opt that can be reference a database Id or a file on disk; Valid examples:
|
||||
# - /tmp/foo.txt
|
||||
# - id:123
|
||||
###
|
||||
class OptDatabaseRefOrPath < OptBase
|
||||
def normalize(value)
|
||||
return value if value.nil? || value.to_s.empty? || value.start_with?('id:')
|
||||
|
||||
File.expand_path(value)
|
||||
end
|
||||
|
||||
def validate_on_assignment?
|
||||
false
|
||||
end
|
||||
|
||||
# Generally, 'value' should be a file that exists, or an integer database id.
|
||||
def valid?(value, check_empty: true, datastore: nil)
|
||||
return false if check_empty && empty_required_value?(value)
|
||||
|
||||
if value && !value.empty?
|
||||
if value.start_with?('id:')
|
||||
return value.match?(/^id:\d+$/)
|
||||
end
|
||||
|
||||
unless File.exist?(File.expand_path(value))
|
||||
return false
|
||||
end
|
||||
end
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,14 @@
|
||||
# -*- coding: binary -*-
|
||||
|
||||
module Msf
|
||||
###
|
||||
#
|
||||
# Pkcs12 cert that can either exist on disk, or as a database core ID
|
||||
#
|
||||
###
|
||||
class OptKerberosCredentialCache < OptDatabaseRefOrPath
|
||||
def type
|
||||
'kerberos_credential_cache'
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,14 @@
|
||||
# -*- coding: binary -*-
|
||||
|
||||
module Msf
|
||||
###
|
||||
#
|
||||
# Pkcs12 cert that can either exist on disk, or as a database core ID
|
||||
#
|
||||
###
|
||||
class OptPkcs12Cert < OptDatabaseRefOrPath
|
||||
def type
|
||||
'pkcs12_cert'
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -344,7 +344,7 @@ class Creds
|
||||
set_rhosts = false
|
||||
truncate = true
|
||||
|
||||
cred_table_columns = [ 'host', 'origin' , 'service', 'public', 'private', 'realm', 'private_type', 'JtR Format', 'cracked_password' ]
|
||||
cred_table_columns = [ 'id', 'host', 'origin' , 'service', 'public', 'private', 'realm', 'private_type', 'JtR Format', 'cracked_password' ]
|
||||
delete_count = 0
|
||||
search_term = nil
|
||||
|
||||
@@ -506,7 +506,8 @@ class Creds
|
||||
service_info = build_service_info(service)
|
||||
end
|
||||
cracked_password_val = cracked_password_core&.private&.data.to_s
|
||||
tbl << [
|
||||
row = [
|
||||
core.id,
|
||||
host,
|
||||
origin,
|
||||
service_info,
|
||||
@@ -517,6 +518,7 @@ class Creds
|
||||
jtr_val,
|
||||
cracked_password_val
|
||||
]
|
||||
tbl << row
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ class MetasploitModule < Msf::Auxiliary
|
||||
OptString.new('DOMAIN', [ false, 'The Fully Qualified Domain Name (FQDN). Ex: mydomain.local' ]),
|
||||
OptString.new('USERNAME', [ false, 'The domain user' ]),
|
||||
OptString.new('PASSWORD', [ false, 'The domain user\'s password' ]),
|
||||
OptPath.new('CERT_FILE', [ false, 'The PKCS12 (.pfx) certificate file to authenticate with' ]),
|
||||
OptPkcs12Cert.new('CERT_FILE', [ false, 'The PKCS12 (.pfx) certificate file to authenticate with' ]),
|
||||
OptString.new('CERT_PASSWORD', [ false, 'The certificate file\'s password' ]),
|
||||
OptString.new(
|
||||
'NTHASH', [
|
||||
@@ -76,7 +76,7 @@ class MetasploitModule < Msf::Auxiliary
|
||||
],
|
||||
conditions: %w[ACTION == GET_TGS]
|
||||
),
|
||||
OptPath.new(
|
||||
OptKerberosCredentialCache.new(
|
||||
'Krb5Ccname', [
|
||||
false,
|
||||
'The Kerberos TGT to use when requesting the service ticket. If unset, the database will be checked'
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
# -*- coding:binary -*-
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Msf::OptKerberosCredentialCache do
|
||||
it_behaves_like 'a database ref or path option', expected_type: 'kerberos_credential_cache'
|
||||
end
|
||||
@@ -0,0 +1,7 @@
|
||||
# -*- coding:binary -*-
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Msf::OptPkcs12Cert do
|
||||
it_behaves_like 'a database ref or path option', expected_type: 'pkcs12_cert'
|
||||
end
|
||||
@@ -18,6 +18,14 @@ RSpec.describe Msf::Ui::Console::CommandDispatcher::Creds do
|
||||
it { is_expected.to respond_to :creds_add }
|
||||
it { is_expected.to respond_to :creds_search }
|
||||
|
||||
before(:each) do
|
||||
# Replace the incremental database ID to ensure deterministic tests
|
||||
allow_any_instance_of(Rex::Text::WrappedTable).to receive(:<<).and_wrap_original do |original, row|
|
||||
row_without_id = ['id'] + row.dup[1..]
|
||||
original.call row_without_id
|
||||
end
|
||||
end
|
||||
|
||||
describe '#cmd_creds' do
|
||||
let(:username) { 'thisuser' }
|
||||
let(:password) { 'thispass' }
|
||||
@@ -70,9 +78,9 @@ RSpec.describe Msf::Ui::Console::CommandDispatcher::Creds do
|
||||
Credentials
|
||||
===========
|
||||
|
||||
host origin service public private realm private_type JtR Format cracked_password
|
||||
---- ------ ------- ------ ------- ----- ------------ ---------- ----------------
|
||||
thisuser thispass Password
|
||||
id host origin service public private realm private_type JtR Format cracked_password
|
||||
-- ---- ------ ------- ------ ------- ----- ------------ ---------- ----------------
|
||||
id thisuser thispass Password
|
||||
|
||||
TABLE
|
||||
end
|
||||
@@ -83,8 +91,8 @@ RSpec.describe Msf::Ui::Console::CommandDispatcher::Creds do
|
||||
Credentials
|
||||
===========
|
||||
|
||||
host origin service public private realm private_type JtR Format cracked_password
|
||||
---- ------ ------- ------ ------- ----- ------------ ---------- ----------------
|
||||
id host origin service public private realm private_type JtR Format cracked_password
|
||||
-- ---- ------ ------- ------ ------- ----- ------------ ---------- ----------------
|
||||
|
||||
TABLE
|
||||
end
|
||||
@@ -96,9 +104,9 @@ RSpec.describe Msf::Ui::Console::CommandDispatcher::Creds do
|
||||
Credentials
|
||||
===========
|
||||
|
||||
host origin service public private realm private_type JtR Format cracked_password
|
||||
---- ------ ------- ------ ------- ----- ------------ ---------- ----------------
|
||||
nonblank_pass Password
|
||||
id host origin service public private realm private_type JtR Format cracked_password
|
||||
-- ---- ------ ------- ------ ------- ----- ------------ ---------- ----------------
|
||||
id nonblank_pass Password
|
||||
|
||||
TABLE
|
||||
end
|
||||
@@ -110,9 +118,9 @@ RSpec.describe Msf::Ui::Console::CommandDispatcher::Creds do
|
||||
Credentials
|
||||
===========
|
||||
|
||||
host origin service public private realm private_type JtR Format cracked_password
|
||||
---- ------ ------- ------ ------- ----- ------------ ---------- ----------------
|
||||
nonblank_user Password
|
||||
id host origin service public private realm private_type JtR Format cracked_password
|
||||
-- ---- ------ ------- ------ ------- ----- ------------ ---------- ----------------
|
||||
id nonblank_user Password
|
||||
|
||||
TABLE
|
||||
end
|
||||
@@ -127,8 +135,8 @@ RSpec.describe Msf::Ui::Console::CommandDispatcher::Creds do
|
||||
Credentials
|
||||
===========
|
||||
|
||||
host origin service public private realm private_type JtR Format cracked_password
|
||||
---- ------ ------- ------ ------- ----- ------------ ---------- ----------------
|
||||
id host origin service public private realm private_type JtR Format cracked_password
|
||||
-- ---- ------ ------- ------ ------- ----- ------------ ---------- ----------------
|
||||
|
||||
TABLE
|
||||
end
|
||||
@@ -140,8 +148,8 @@ RSpec.describe Msf::Ui::Console::CommandDispatcher::Creds do
|
||||
Credentials
|
||||
===========
|
||||
|
||||
host origin service public private realm private_type JtR Format cracked_password
|
||||
---- ------ ------- ------ ------- ----- ------------ ---------- ----------------
|
||||
id host origin service public private realm private_type JtR Format cracked_password
|
||||
-- ---- ------ ------- ------ ------- ----- ------------ ---------- ----------------
|
||||
|
||||
TABLE
|
||||
end
|
||||
@@ -166,9 +174,9 @@ RSpec.describe Msf::Ui::Console::CommandDispatcher::Creds do
|
||||
Credentials
|
||||
===========
|
||||
|
||||
host origin service public private realm private_type JtR Format cracked_password
|
||||
---- ------ ------- ------ ------- ----- ------------ ---------- ----------------
|
||||
this_username some_hash Nonreplayable hash this_cracked_password
|
||||
id host origin service public private realm private_type JtR Format cracked_password
|
||||
-- ---- ------ ------- ------ ------- ----- ------------ ---------- ----------------
|
||||
id this_username some_hash Nonreplayable hash this_cracked_password
|
||||
TABLE
|
||||
end
|
||||
it "should show the user given passwords on private column instead of cracked_password column" do
|
||||
@@ -177,9 +185,9 @@ RSpec.describe Msf::Ui::Console::CommandDispatcher::Creds do
|
||||
Credentials
|
||||
===========
|
||||
|
||||
host origin service public private realm private_type JtR Format cracked_password
|
||||
---- ------ ------- ------ ------- ----- ------------ ---------- ----------------
|
||||
thisuser thispass Password
|
||||
id host origin service public private realm private_type JtR Format cracked_password
|
||||
-- ---- ------ ------- ------ ------- ----- ------------ ---------- ----------------
|
||||
id thisuser thispass Password
|
||||
|
||||
TABLE
|
||||
end
|
||||
@@ -263,9 +271,9 @@ RSpec.describe Msf::Ui::Console::CommandDispatcher::Creds do
|
||||
Credentials
|
||||
===========
|
||||
|
||||
host origin service public private realm private_type JtR Format cracked_password
|
||||
---- ------ ------- ------ ------- ----- ------------ ---------- ----------------
|
||||
thisuser thispass Password
|
||||
id host origin service public private realm private_type JtR Format cracked_password
|
||||
-- ---- ------ ------- ------ ------- ----- ------------ ---------- ----------------
|
||||
id thisuser thispass Password
|
||||
|
||||
TABLE
|
||||
end
|
||||
@@ -288,10 +296,10 @@ RSpec.describe Msf::Ui::Console::CommandDispatcher::Creds do
|
||||
Credentials
|
||||
===========
|
||||
|
||||
host origin service public private realm private_type JtR Format cracked_password
|
||||
---- ------ ------- ------ ------- ----- ------------ ---------- ----------------
|
||||
thisuser thispass Password
|
||||
this_username this_cracked_password Password
|
||||
id host origin service public private realm private_type JtR Format cracked_password
|
||||
-- ---- ------ ------- ------ ------- ----- ------------ ---------- ----------------
|
||||
id thisuser thispass Password
|
||||
id this_username this_cracked_password Password
|
||||
|
||||
TABLE
|
||||
end
|
||||
@@ -305,9 +313,9 @@ RSpec.describe Msf::Ui::Console::CommandDispatcher::Creds do
|
||||
Credentials
|
||||
===========
|
||||
|
||||
host origin service public private realm private_type JtR Format cracked_password
|
||||
---- ------ ------- ------ ------- ----- ------------ ---------- ----------------
|
||||
thisuser 1443d06412d8c0e6e72c57ef50f76a05:27c433245e4763d074d30a05aae0af2c NTLM hash
|
||||
id host origin service public private realm private_type JtR Format cracked_password
|
||||
-- ---- ------ ------- ------ ------- ----- ------------ ---------- ----------------
|
||||
id thisuser 1443d06412d8c0e6e72c57ef50f76a05:27c433245e4763d074d30a05aae0af2c NTLM hash
|
||||
|
||||
TABLE
|
||||
end
|
||||
@@ -321,9 +329,9 @@ RSpec.describe Msf::Ui::Console::CommandDispatcher::Creds do
|
||||
Credentials
|
||||
===========
|
||||
|
||||
host origin service public private realm private_type JtR Format cracked_password
|
||||
---- ------ ------- ------ ------- ----- ------------ ---------- ----------------
|
||||
thisuser asdf Nonreplayable hash
|
||||
id host origin service public private realm private_type JtR Format cracked_password
|
||||
-- ---- ------ ------- ------ ------- ----- ------------ ---------- ----------------
|
||||
id thisuser asdf Nonreplayable hash
|
||||
|
||||
TABLE
|
||||
end
|
||||
@@ -338,9 +346,9 @@ RSpec.describe Msf::Ui::Console::CommandDispatcher::Creds do
|
||||
Credentials
|
||||
===========
|
||||
|
||||
host origin service public private realm private_type JtR Format cracked_password
|
||||
---- ------ ------- ------ ------- ----- ------------ ---------- ----------------
|
||||
#{private_str} Pkcs12 (pfx)
|
||||
id host origin service public private realm private_type JtR Format cracked_password
|
||||
-- ---- ------ ------- ------ ------- ----- ------------ ---------- ----------------
|
||||
id #{private_str} Pkcs12 (pfx)
|
||||
|
||||
TABLE
|
||||
end
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
# -*- coding:binary -*-
|
||||
|
||||
RSpec.shared_examples_for "a database ref or path option" do |options|
|
||||
valid_values = [
|
||||
{ value: __FILE__, normalized: __FILE__ },
|
||||
{ value: '~', normalized: ::File.expand_path('~') },
|
||||
{ value: 'id:1', normalized: 'id:1' },
|
||||
]
|
||||
invalid_values = [
|
||||
{ value: '0.1' },
|
||||
{ value: '-1' },
|
||||
{ value: '65536' },
|
||||
{ value: '$' },
|
||||
{ value: 'id:-1' },
|
||||
{ value: 'id:' },
|
||||
]
|
||||
|
||||
it_behaves_like "an option", valid_values, invalid_values, options.fetch(:expected_type)
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user