Skip to content

Commit ab57ec1

Browse files
authored
Land rapid7#20264, adds processing of KERB-SUPERSEDED-BY-USER error message
Process and propagate KERB-SUPERSEDED-BY-USER error details
2 parents e3206fb + b40dbe8 commit ab57ec1

File tree

6 files changed

+121
-20
lines changed

6 files changed

+121
-20
lines changed

lib/metasploit/framework/login_scanner/kerberos.rb

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,11 @@ def self.login_status_for_kerberos_error(krb_err)
8787
# It doesn't appear to be documented anywhere, but Microsoft gives us a bit
8888
# of extra information in the e-data section
8989
begin
90-
pa_data_entry = krb_err.res.e_data_as_pa_data_entry
91-
if pa_data_entry && pa_data_entry.type == Rex::Proto::Kerberos::Model::PreAuthType::PA_PW_SALT
90+
pa_data_entry = krb_err.res.e_data_as_pa_data.find do |pa_data|
91+
pa_data.type == Rex::Proto::Kerberos::Model::PreAuthType::PA_PW_SALT
92+
end
93+
94+
if pa_data_entry
9295
pw_salt = pa_data_entry.decoded_value
9396
if pw_salt.nt_status
9497
case pw_salt.nt_status.value
@@ -107,7 +110,7 @@ def self.login_status_for_kerberos_error(krb_err)
107110
Metasploit::Model::Login::Status::DISABLED
108111
end
109112
else
110-
Metasploit::Model::Login::Status::DISABLED
113+
Metasploit::Model::Login::Status::DISABLED
111114
end
112115
rescue Rex::Proto::Kerberos::Model::Error::KerberosDecodingError
113116
# Could be a non-MS implementation?

lib/rex/proto/kerberos/model.rb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,9 @@ module NameType
5151
NT_UID = 5
5252
end
5353

54-
# From padata - https://www.iana.org/assignments/kerberos-parameters/kerberos-parameters.xhtml
54+
# See:
55+
# * https://www.iana.org/assignments/kerberos-parameters/kerberos-parameters.xhtml#pre-authentication
56+
# * https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-kile/ae60c948-fda8-45c2-b1d1-a71b484dd1f7
5557

5658
module PreAuthType
5759
PA_TGS_REQ = 1
@@ -65,6 +67,7 @@ module PreAuthType
6567
PA_FOR_USER = 129
6668
PA_SUPPORTED_ETYPES = 165
6769
PA_PAC_OPTIONS = 167
70+
KERB_SUPERSEDED_BY_USER = 170
6871
end
6972

7073
module AuthorizationDataType

lib/rex/proto/kerberos/model/error.rb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,19 @@ def message_for(error_code)
171171
now = Time.now
172172
skew = (res.stime - now).abs.to_i
173173
return "#{error_code}. Local time: #{now}, Server time: #{res.stime}, off by #{skew} seconds"
174+
elsif error_code == ErrorCodes::KDC_ERR_CLIENT_REVOKED && res&.respond_to?(:e_data) && res.e_data.present?
175+
begin
176+
pa_datas = res.e_data_as_pa_data
177+
rescue OpenSSL::ASN1::ASN1Error
178+
else
179+
pa_data_entry = pa_datas.find do |pa_data|
180+
pa_data.type == Rex::Proto::Kerberos::Model::PreAuthType::KERB_SUPERSEDED_BY_USER
181+
end
182+
183+
if pa_data_entry
184+
error_code = "#{error_code}. This account has been superseded by #{pa_data_entry.decoded_value}."
185+
end
186+
end
174187
end
175188

176189
"Kerberos Error - #{error_code}"
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# -*- coding: binary -*-
2+
3+
module Rex
4+
module Proto
5+
module Kerberos
6+
module Model
7+
# This class provides a representation of a Kerberos KERB-SUPERSEDED-BY-USER
8+
# message as defined in [MS-KILE 2.2.13](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-kile/79170b21-ad15-4a1b-99c4-84b3992d9e70).
9+
class KerbSupersededByUser < Element
10+
11+
attr_accessor :principal_name
12+
13+
attr_accessor :realm
14+
15+
def ==(other)
16+
realm == other.realm && principal_name == other.principal_name
17+
end
18+
19+
def to_s
20+
"#{principal_name}@#{realm}"
21+
end
22+
23+
def decode(input)
24+
case input
25+
when String
26+
decode_string(input)
27+
when OpenSSL::ASN1::Sequence
28+
decode_asn1(input)
29+
else
30+
raise ::Rex::Proto::Kerberos::Model::Error::KerberosDecodingError, 'Failed to decode KerbSupersededByUser, invalid input'
31+
end
32+
33+
self
34+
end
35+
36+
def encode
37+
principal_name_asn1 = OpenSSL::ASN1::ASN1Data.new([encode_principal_name], 1, :CONTEXT_SPECIFIC)
38+
realm_asn1 = OpenSSL::ASN1::ASN1Data.new([encode_realm], 2, :CONTEXT_SPECIFIC)
39+
seq = OpenSSL::ASN1::Sequence.new([principal_name_asn1, realm_asn1])
40+
41+
seq.to_der
42+
end
43+
44+
private
45+
46+
def decode_string(input)
47+
asn1 = OpenSSL::ASN1.decode(input)
48+
49+
decode_asn1(asn1)
50+
end
51+
52+
# Decodes a Rex::Proto::Kerberos::Model::KerbSupersededByUser from an
53+
# OpenSSL::ASN1::Sequence
54+
#
55+
# @param input [OpenSSL::ASN1::Sequence] the input to decode from
56+
def decode_asn1(input)
57+
seq_values = input.value
58+
self.principal_name = decode_principal_name(seq_values[0])
59+
self.realm = decode_realm(seq_values[1])
60+
end
61+
62+
def decode_principal_name(input)
63+
PrincipalName.decode(input.value[0])
64+
end
65+
66+
# Decodes the realm from an OpenSSL::ASN1::ASN1Data
67+
#
68+
# @param input [OpenSSL::ASN1::ASN1Data] the input to decode from
69+
# @return [Array<String>]
70+
def decode_realm(input)
71+
input.value[0].value
72+
end
73+
74+
def encode_principal_name
75+
self.principal_name.encode
76+
end
77+
78+
def encode_realm
79+
OpenSSL::ASN1::OctetString.new(self.realm)
80+
end
81+
end
82+
end
83+
end
84+
end
85+
end

lib/rex/proto/kerberos/model/krb_error.rb

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -72,30 +72,24 @@ def encode
7272
raise ::NotImplementedError, 'KrbError encoding not supported'
7373
end
7474

75-
# Decodes the e_data field as an Array<PreAuthDataEntry>
75+
# Decodes the e_data field as an Array<PreAuthDataEntry>.
7676
#
7777
# @return [Array<Rex::Proto::Kerberos::Model::PreAuthDataEntry>]
7878
def e_data_as_pa_data
79+
return [] unless self.e_data
80+
7981
pre_auth = []
8082
decoded = OpenSSL::ASN1.decode(self.e_data)
81-
decoded.each do |pre_auth_data|
82-
pre_auth << Rex::Proto::Kerberos::Model::PreAuthDataEntry.decode(pre_auth_data)
83-
end
84-
85-
pre_auth
86-
end
8783

88-
# Decodes the e_data field as a PreAuthData
89-
#
90-
# @return [Rex::Proto::Kerberos::Model::PreAuthData]
91-
def e_data_as_pa_data_entry
92-
if self.e_data
93-
decoded = OpenSSL::ASN1.decode(self.e_data)
94-
Rex::Proto::Kerberos::Model::PreAuthDataEntry.decode(decoded)
84+
if decoded.first.tag_class == :UNIVERSAL && decoded.first.tag == 16
85+
decoded.each do |pre_auth_data|
86+
pre_auth << Rex::Proto::Kerberos::Model::PreAuthDataEntry.decode(pre_auth_data)
87+
end
9588
else
96-
# This is implementation-defined, so may be different in some cases
97-
nil
89+
pre_auth << Rex::Proto::Kerberos::Model::PreAuthDataEntry.decode(decoded)
9890
end
91+
92+
pre_auth
9993
end
10094

10195
private

lib/rex/proto/kerberos/model/pre_auth_data_entry.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@ def decoded_value
7676
when Rex::Proto::Kerberos::Model::PreAuthType::PA_FOR_USER
7777
decoded = OpenSSL::ASN1.decode(self.value)
7878
PreAuthForUser.decode(decoded)
79+
when Rex::Proto::Kerberos::Model::PreAuthType::KERB_SUPERSEDED_BY_USER
80+
decoded = OpenSSL::ASN1.decode(self.value)
81+
KerbSupersededByUser.decode(decoded)
7982
else
8083
# Unknown type - just ignore for now
8184
end

0 commit comments

Comments
 (0)