diff --git a/lib/msf/core/exploit/remote/kerberos/service_authenticator/base.rb b/lib/msf/core/exploit/remote/kerberos/service_authenticator/base.rb index 01c70ccac6f4..daef70e9fb63 100644 --- a/lib/msf/core/exploit/remote/kerberos/service_authenticator/base.rb +++ b/lib/msf/core/exploit/remote/kerberos/service_authenticator/base.rb @@ -1036,7 +1036,8 @@ def get_cached_credential(options = {}) host: options.fetch(:host) { rhost }, client: options.fetch(:username) { self.username }, server: options.fetch(:sname, nil), - realm: options.fetch(:realm) { self.realm } + realm: options.fetch(:realm) { self.realm }, + offered_etypes: options.fetch(:offered_etypes) { self.offered_etypes } ) end diff --git a/lib/msf/core/exploit/remote/kerberos/ticket/storage/read_mixin.rb b/lib/msf/core/exploit/remote/kerberos/ticket/storage/read_mixin.rb index bf07dff9ef7f..b2c67df6d33a 100644 --- a/lib/msf/core/exploit/remote/kerberos/ticket/storage/read_mixin.rb +++ b/lib/msf/core/exploit/remote/kerberos/ticket/storage/read_mixin.rb @@ -6,9 +6,19 @@ def load_credential(options = {}) return nil unless active_db? now = Time.now.utc - tickets(options) do |ticket| - next if ticket.expired?(now) - + available_tickets = tickets(options).select do |ticket| + !ticket.expired?(now) + end + if options[:offered_etypes].present? + # Prefer etypes mentioned first + options[:offered_etypes].each do |etype| + available_tickets.each do |t| + if t.enctype == etype + return t.ccache.credentials.first + end + end + end + else return ticket.ccache.credentials.first end @@ -17,10 +27,18 @@ def load_credential(options = {}) # (see Base#tickets) def tickets(options = {}, &block) - objects(options).map do |stored_loot| + mapped = objects(options).map do |stored_loot| stored_ticket = StoredTicket.new(stored_loot) - block.call(stored_ticket) if block_given? - stored_ticket + end + + mapped.select do |stored_ticket| + # If we were provided a set of etypes to look for, restrict to that + if options[:offered_etypes].nil? || options[:offered_etypes].include?(stored_ticket.enctype) + block.call(stored_ticket) if block_given? + true + else + false + end end end end diff --git a/lib/msf/core/exploit/remote/kerberos/ticket/storage/stored_ticket.rb b/lib/msf/core/exploit/remote/kerberos/ticket/storage/stored_ticket.rb index c953af429302..2aade9a47451 100644 --- a/lib/msf/core/exploit/remote/kerberos/ticket/storage/stored_ticket.rb +++ b/lib/msf/core/exploit/remote/kerberos/ticket/storage/stored_ticket.rb @@ -34,6 +34,10 @@ def starttime credential.starttime end + def enctype + credential.keyblock.enctype + end + # @return [Rex::Proto::Kerberos::CredentialCache::Krb5Ccache] def ccache @ccache ||= Rex::Proto::Kerberos::CredentialCache::Krb5Ccache.read(loot.data) diff --git a/lib/msf/ui/console/command_dispatcher/db/klist.rb b/lib/msf/ui/console/command_dispatcher/db/klist.rb index 776f002dd1d6..706c5d89ddb3 100644 --- a/lib/msf/ui/console/command_dispatcher/db/klist.rb +++ b/lib/msf/ui/console/command_dispatcher/db/klist.rb @@ -100,7 +100,7 @@ def cmd_klist(*args) else tbl = Rex::Text::Table.new( { - 'Columns' => ['id', 'host', 'principal', 'sname', 'issued', 'status', 'path'], + 'Columns' => ['id', 'host', 'principal', 'sname', 'enctype', 'issued', 'status', 'path'], 'SortIndex' => -1, # For now, don't perform any word wrapping on the table as it breaks the workflow of # copying file paths and pasting them into applications @@ -111,6 +111,7 @@ def cmd_klist(*args) ticket.host_address, ticket.principal, ticket.sname, + Rex::Proto::Kerberos::Crypto::Encryption.const_name(ticket.enctype), ticket.starttime, ticket_status(ticket), ticket.path diff --git a/spec/lib/msf/ui/console/command_dispatcher/db/klist_spec.rb b/spec/lib/msf/ui/console/command_dispatcher/db/klist_spec.rb index 4fe4442663d4..d51739d50b8f 100644 --- a/spec/lib/msf/ui/console/command_dispatcher/db/klist_spec.rb +++ b/spec/lib/msf/ui/console/command_dispatcher/db/klist_spec.rb @@ -207,10 +207,10 @@ def as_ccache(data) expect(table_without_ids(@output.join("\n"))).to match_table <<~TABLE Kerberos Cache ============== - id host principal sname issued status path - -- ---- --------- ----- ------ ------ ---- - [id] 192.0.2.2 Administrator@WINDOMAIN.LOCAL krbtgt/WINDOMAIN.LOCAL@WINDOMAIN.LOCAL #{Time.parse('2022-11-28 15:51:29 +0000').to_time} active #{valid_ccache_path} - [id] 192.0.2.24 Administrator@ADF3.LOCAL krbtgt/ADF3.LOCAL@ADF3.LOCAL #{Time.parse('2022-12-16 12:05:05 +0000').to_time} >>expired<< #{expired_ccache_path} + id host principal sname enctype issued status path + -- ---- --------- ----- ------- ------ ------ ---- + [id] 192.0.2.2 Administrator@WINDOMAIN.LOCAL krbtgt/WINDOMAIN.LOCAL@WINDOMAIN.LOCAL AES256 #{Time.parse('2022-11-28 15:51:29 +0000').to_time} active #{valid_ccache_path} + [id] 192.0.2.24 Administrator@ADF3.LOCAL krbtgt/ADF3.LOCAL@ADF3.LOCAL AES256 #{Time.parse('2022-12-16 12:05:05 +0000').to_time} >>expired<< #{expired_ccache_path} TABLE end end @@ -221,9 +221,9 @@ def as_ccache(data) expect(table_without_ids(@output.join("\n"))).to match_table <<~TABLE Kerberos Cache ============== - id host principal sname issued status path - -- ---- --------- ----- ------ ------ ---- - [id] 192.0.2.2 Administrator@WINDOMAIN.LOCAL krbtgt/WINDOMAIN.LOCAL@WINDOMAIN.LOCAL #{Time.parse('2022-11-28 15:51:29 +0000').to_time} active #{valid_ccache_path} + id host principal sname enctype issued status path + -- ---- --------- ----- ------- ------ ------ ---- + [id] 192.0.2.2 Administrator@WINDOMAIN.LOCAL krbtgt/WINDOMAIN.LOCAL@WINDOMAIN.LOCAL AES256 #{Time.parse('2022-11-28 15:51:29 +0000').to_time} active #{valid_ccache_path} TABLE end end @@ -296,10 +296,10 @@ def as_ccache(data) expect(table_without_ids(@output.join("\n"))).to match_table <<~TABLE Kerberos Cache ============== - id host principal sname issued status path - -- ---- --------- ----- ------ ------ ---- - [id] 192.0.2.2 Administrator@WINDOMAIN.LOCAL krbtgt/WINDOMAIN.LOCAL@WINDOMAIN.LOCAL #{Time.parse('2022-11-28 15:51:29 +0000').to_time} active #{old_valid_ccache_path} - [id] 192.0.2.24 Administrator@ADF3.LOCAL krbtgt/ADF3.LOCAL@ADF3.LOCAL #{Time.parse('2022-12-16 12:05:05 +0000').to_time} >>expired<< #{old_expired_ccache_path} + id host principal sname enctype issued status path + -- ---- --------- ----- ------- ------ ------ ---- + [id] 192.0.2.2 Administrator@WINDOMAIN.LOCAL krbtgt/WINDOMAIN.LOCAL@WINDOMAIN.LOCAL AES256 #{Time.parse('2022-11-28 15:51:29 +0000').to_time} active #{old_valid_ccache_path} + [id] 192.0.2.24 Administrator@ADF3.LOCAL krbtgt/ADF3.LOCAL@ADF3.LOCAL AES256 #{Time.parse('2022-12-16 12:05:05 +0000').to_time} >>expired<< #{old_expired_ccache_path} Deleted 2 entries TABLE expect(kerberos_ticket_storage.tickets.length).to eq(0) @@ -312,9 +312,9 @@ def as_ccache(data) expect(table_without_ids(@output.join("\n"))).to match_table <<~TABLE Kerberos Cache ============== - id host principal sname issued status path - -- ---- --------- ----- ------ ------ ---- - [id] 192.0.2.2 Administrator@WINDOMAIN.LOCAL krbtgt/WINDOMAIN.LOCAL@WINDOMAIN.LOCAL #{Time.parse('2022-11-28 15:51:29 +0000').to_time} active #{valid_ccache_path} + id host principal sname enctype issued status path + -- ---- --------- ----- ------- ------ ------ ---- + [id] 192.0.2.2 Administrator@WINDOMAIN.LOCAL krbtgt/WINDOMAIN.LOCAL@WINDOMAIN.LOCAL AES256 #{Time.parse('2022-11-28 15:51:29 +0000').to_time} active #{valid_ccache_path} TABLE end end @@ -325,10 +325,10 @@ def as_ccache(data) expect(table_without_ids(@output.join("\n"))).to match_table <<~TABLE Kerberos Cache ============== - id host principal sname issued status path - -- ---- --------- ----- ------ ------ ---- - [id] 192.0.2.2 Administrator@WINDOMAIN.LOCAL krbtgt/WINDOMAIN.LOCAL@WINDOMAIN.LOCAL #{Time.parse('2022-11-28 15:51:29 +0000').to_time} active #{valid_ccache_path} - [id] 192.0.2.24 Administrator@ADF3.LOCAL krbtgt/ADF3.LOCAL@ADF3.LOCAL #{Time.parse('2022-12-16 12:05:05 +0000').to_time} >>expired<< #{expired_ccache_path} + id host principal sname enctype issued status path + -- ---- --------- ----- ------- ------ ------ ---- + [id] 192.0.2.2 Administrator@WINDOMAIN.LOCAL krbtgt/WINDOMAIN.LOCAL@WINDOMAIN.LOCAL AES256 #{Time.parse('2022-11-28 15:51:29 +0000').to_time} active #{valid_ccache_path} + [id] 192.0.2.24 Administrator@ADF3.LOCAL krbtgt/ADF3.LOCAL@ADF3.LOCAL AES256 #{Time.parse('2022-12-16 12:05:05 +0000').to_time} >>expired<< #{expired_ccache_path} TABLE end end @@ -339,10 +339,10 @@ def as_ccache(data) expect(table_without_ids(@output.join("\n"))).to match_table <<~TABLE Kerberos Cache ============== - id host principal sname issued status path - -- ---- --------- ----- ------ ------ ---- - [id] 192.0.2.2 Administrator@WINDOMAIN.LOCAL krbtgt/WINDOMAIN.LOCAL@WINDOMAIN.LOCAL #{Time.parse('2022-11-28 15:51:29 +0000').to_time} active #{valid_ccache_path} - [id] 192.0.2.24 Administrator@ADF3.LOCAL krbtgt/ADF3.LOCAL@ADF3.LOCAL #{Time.parse('2022-12-16 12:05:05 +0000').to_time} >>expired<< #{expired_ccache_path} + id host principal sname enctype issued status path + -- ---- --------- ----- ------- ------ ------ ---- + [id] 192.0.2.2 Administrator@WINDOMAIN.LOCAL krbtgt/WINDOMAIN.LOCAL@WINDOMAIN.LOCAL AES256 #{Time.parse('2022-11-28 15:51:29 +0000').to_time} active #{valid_ccache_path} + [id] 192.0.2.24 Administrator@ADF3.LOCAL krbtgt/ADF3.LOCAL@ADF3.LOCAL AES256 #{Time.parse('2022-12-16 12:05:05 +0000').to_time} >>expired<< #{expired_ccache_path} TABLE end end @@ -353,10 +353,10 @@ def as_ccache(data) expect(table_without_ids(@output.join("\n"))).to match_table <<~TABLE Kerberos Cache ============== - id host principal sname issued status path - -- ---- --------- ----- ------ ------ ---- - [id] 192.0.2.2 Administrator@WINDOMAIN.LOCAL krbtgt/WINDOMAIN.LOCAL@WINDOMAIN.LOCAL #{Time.parse('2022-11-28 15:51:29 +0000').to_time} active #{valid_ccache_path} - [id] 192.0.2.24 Administrator@ADF3.LOCAL krbtgt/ADF3.LOCAL@ADF3.LOCAL #{Time.parse('2022-12-16 12:05:05 +0000').to_time} >>expired<< #{expired_ccache_path} + id host principal sname enctype issued status path + -- ---- --------- ----- ------- ------ ------ ---- + [id] 192.0.2.2 Administrator@WINDOMAIN.LOCAL krbtgt/WINDOMAIN.LOCAL@WINDOMAIN.LOCAL AES256 #{Time.parse('2022-11-28 15:51:29 +0000').to_time} active #{valid_ccache_path} + [id] 192.0.2.24 Administrator@ADF3.LOCAL krbtgt/ADF3.LOCAL@ADF3.LOCAL AES256 #{Time.parse('2022-12-16 12:05:05 +0000').to_time} >>expired<< #{expired_ccache_path} TABLE end end @@ -367,10 +367,10 @@ def as_ccache(data) expect(table_without_ids(@output.join("\n"))).to match_table <<~TABLE Kerberos Cache ============== - id host principal sname issued status path - -- ---- --------- ----- ------ ------ ---- - [id] 192.0.2.2 Administrator@WINDOMAIN.LOCAL krbtgt/WINDOMAIN.LOCAL@WINDOMAIN.LOCAL #{Time.parse('2022-11-28 15:51:29 +0000').to_time} active #{valid_ccache_path} - [id] 192.0.2.24 Administrator@ADF3.LOCAL krbtgt/ADF3.LOCAL@ADF3.LOCAL #{Time.parse('2022-12-16 12:05:05 +0000').to_time} >>expired<< #{expired_ccache_path} + id host principal sname enctype issued status path + -- ---- --------- ----- ------- ------ ------ ---- + [id] 192.0.2.2 Administrator@WINDOMAIN.LOCAL krbtgt/WINDOMAIN.LOCAL@WINDOMAIN.LOCAL AES256 #{Time.parse('2022-11-28 15:51:29 +0000').to_time} active #{valid_ccache_path} + [id] 192.0.2.24 Administrator@ADF3.LOCAL krbtgt/ADF3.LOCAL@ADF3.LOCAL AES256 #{Time.parse('2022-12-16 12:05:05 +0000').to_time} >>expired<< #{expired_ccache_path} TABLE end end @@ -418,10 +418,10 @@ def as_ccache(data) expect(table_without_ids(@output.join("\n"))).to match_table <<~TABLE Kerberos Cache ============== - id host principal sname issued status path - -- ---- --------- ----- ------ ------ ---- - [id] 192.0.2.2 Administrator@WINDOMAIN.LOCAL krbtgt/WINDOMAIN.LOCAL@WINDOMAIN.LOCAL #{Time.parse('2022-11-28 15:51:29 +0000').to_time} inactive #{valid_ccache_path} - [id] 192.0.2.24 Administrator@ADF3.LOCAL krbtgt/ADF3.LOCAL@ADF3.LOCAL #{Time.parse('2022-12-16 12:05:05 +0000').to_time} >>expired<< #{expired_ccache_path} + id host principal sname enctype issued status path + -- ---- --------- ----- ------- ------ ------ ---- + [id] 192.0.2.2 Administrator@WINDOMAIN.LOCAL krbtgt/WINDOMAIN.LOCAL@WINDOMAIN.LOCAL AES256 #{Time.parse('2022-11-28 15:51:29 +0000').to_time} inactive #{valid_ccache_path} + [id] 192.0.2.24 Administrator@ADF3.LOCAL krbtgt/ADF3.LOCAL@ADF3.LOCAL AES256 #{Time.parse('2022-12-16 12:05:05 +0000').to_time} >>expired<< #{expired_ccache_path} Deactivated 2 entries TABLE end @@ -439,10 +439,10 @@ def as_ccache(data) expect(table_without_ids(@output.join("\n"))).to match_table <<~TABLE Kerberos Cache ============== - id host principal sname issued status path - -- ---- --------- ----- ------ ------ ---- - [id] 192.0.2.2 Administrator@WINDOMAIN.LOCAL krbtgt/WINDOMAIN.LOCAL@WINDOMAIN.LOCAL #{Time.parse('2022-11-28 15:51:29 +0000').to_time} active #{valid_ccache_path} - [id] 192.0.2.24 Administrator@ADF3.LOCAL krbtgt/ADF3.LOCAL@ADF3.LOCAL #{Time.parse('2022-12-16 12:05:05 +0000').to_time} >>expired<< #{expired_ccache_path} + id host principal sname enctype issued status path + -- ---- --------- ----- ------- ------ ------ ---- + [id] 192.0.2.2 Administrator@WINDOMAIN.LOCAL krbtgt/WINDOMAIN.LOCAL@WINDOMAIN.LOCAL AES256 #{Time.parse('2022-11-28 15:51:29 +0000').to_time} active #{valid_ccache_path} + [id] 192.0.2.24 Administrator@ADF3.LOCAL krbtgt/ADF3.LOCAL@ADF3.LOCAL AES256 #{Time.parse('2022-12-16 12:05:05 +0000').to_time} >>expired<< #{expired_ccache_path} Activated 2 entries TABLE end @@ -457,9 +457,9 @@ def as_ccache(data) expect(table_without_ids(@output.join("\n"))).to match_table <<~TABLE Kerberos Cache ============== - id host principal sname issued status path - -- ---- --------- ----- ------ ------ ---- - [id] 192.0.2.2 Administrator@WINDOMAIN.LOCAL krbtgt/WINDOMAIN.LOCAL@WINDOMAIN.LOCAL #{Time.parse('2022-11-28 15:51:29 +0000').to_time} active #{old_valid_ccache_path} + id host principal sname enctype issued status path + -- ---- --------- ----- ------- ------ ------ ---- + [id] 192.0.2.2 Administrator@WINDOMAIN.LOCAL krbtgt/WINDOMAIN.LOCAL@WINDOMAIN.LOCAL AES256 #{Time.parse('2022-11-28 15:51:29 +0000').to_time} active #{old_valid_ccache_path} Deleted 1 entry TABLE expect(kerberos_ticket_storage.tickets.length).to eq(1) @@ -472,9 +472,9 @@ def as_ccache(data) expect(table_without_ids(@output.join("\n"))).to match_table <<~TABLE Kerberos Cache ============== - id host principal sname issued status path - -- ---- --------- ----- ------ ------ ---- - [id] 192.0.2.2 Administrator@WINDOMAIN.LOCAL krbtgt/WINDOMAIN.LOCAL@WINDOMAIN.LOCAL #{Time.parse('2022-11-28 15:51:29 +0000').to_time} inactive #{valid_ccache_path} + id host principal sname enctype issued status path + -- ---- --------- ----- ------- ------ ------ ---- + [id] 192.0.2.2 Administrator@WINDOMAIN.LOCAL krbtgt/WINDOMAIN.LOCAL@WINDOMAIN.LOCAL AES256 #{Time.parse('2022-11-28 15:51:29 +0000').to_time} inactive #{valid_ccache_path} Deactivated 1 entry TABLE end