@@ -157,7 +157,8 @@ def initialize(
157
157
credential = nil
158
158
if cache_file . present?
159
159
# the cache file is only used for loading credentials, it is *not* written to
160
- credential = load_credential_from_file ( cache_file , sname : nil , sname_hostname : @hostname )
160
+ load_sname_hostname_credential_result = load_credential_from_file ( cache_file , sname : nil , sname_hostname : @hostname )
161
+ credential = load_sname_hostname_credential_result [ :credential ]
161
162
serviceclass = build_spn . name_string . first
162
163
if credential && credential . server . components [ 0 ] != serviceclass
163
164
old_sname = credential . server . components . snapshot . join ( '/' )
@@ -168,9 +169,18 @@ def initialize(
168
169
ticket . sname . name_string [ 0 ] = serviceclass
169
170
credential . ticket = ticket . encode
170
171
elsif credential . nil? && hostname . present?
171
- credential = load_credential_from_file ( cache_file , sname : "krbtgt/#{ hostname . split ( '.' , 2 ) . last } " )
172
+ load_sname_krbtgt_hostname_credential_result = load_credential_from_file ( cache_file , sname : "krbtgt/#{ hostname . split ( '.' , 2 ) . last } " )
173
+ credential = load_sname_krbtgt_hostname_credential_result [ :credential ]
172
174
end
173
175
if credential . nil?
176
+ vprint_error ( "Failed to load a usable credential from ticket file: #{ cache_file } " )
177
+ vprint_error ( "Attempt: finding a valid credential in #{ cache_file } for sname: nil, and sname_hostname: #{ @hostname } :" )
178
+ vprint_error ( load_sname_hostname_credential_result [ :filter_reasons ] . join ( "\n " ) . indent ( 2 ) )
179
+
180
+ if load_sname_krbtgt_hostname_credential_result
181
+ vprint_error ( "Attempt: Failed finding a valid credential in #{ cache_file } for sname: #{ "krbtgt/#{ hostname . split ( '.' , 2 ) . last } " } " )
182
+ vprint_error ( load_sname_krbtgt_hostname_credential_result [ :filter_reasons ] . join ( "\n " ) . indent ( 2 ) )
183
+ end
174
184
raise ::Rex ::Proto ::Kerberos ::Model ::Error ::KerberosError . new ( "Failed to load a usable credential from ticket file: #{ cache_file } " )
175
185
end
176
186
print_status ( "Loaded a credential from ticket file: #{ cache_file } " )
@@ -255,6 +265,7 @@ def authenticate(options = {})
255
265
elsif options [ :credential ]
256
266
auth_context = authenticate_via_krb5_ccache_credential_tgs ( options [ :credential ] , options )
257
267
else
268
+ # TODO: If krbcachemode=none is set, should this be getting used?
258
269
pkcs12_storage = Msf ::Exploit ::Remote ::Pkcs12 ::Storage . new ( framework : framework , framework_module : framework_module )
259
270
pkcs12_results = pkcs12_storage . pkcs12 (
260
271
workspace : workspace ,
@@ -362,7 +373,7 @@ def build_spn(options = {})
362
373
# @return [Rex::Proto::Kerberos::CredentialCache::Krb5CcacheCredential] The ccache credential
363
374
def request_tgt_only ( options = { } )
364
375
if options [ :cache_file ]
365
- credential = load_credential_from_file ( options [ :cache_file ] )
376
+ credential = load_credential_from_file ( options [ :cache_file ] ) [ :credential ]
366
377
else
367
378
credential = get_cached_credential (
368
379
options . merge (
@@ -1055,67 +1066,92 @@ def get_cached_credential(options = {})
1055
1066
)
1056
1067
end
1057
1068
1058
- # Load a credential object from a file for authentication. Credentials in the file will be filtered by multiple
1069
+ # Load a credential object from a file or database entry for authentication. Credentials in the credential cache will be filtered by multiple
1059
1070
# attributes including their timestamps to ensure that the returned credential appears usable.
1060
1071
#
1061
- # @param [String] file_path The file path to load a credential object from
1072
+ # @param [path] path The file path to load a credential object from or database string reference
1062
1073
# @return [Rex::Proto::Kerberos::CredentialCache::Krb5CacheCredential] the credential object for authentication
1063
- def load_credential_from_file ( file_path , options = { } )
1064
- unless File . readable? ( file_path . to_s )
1065
- wlog ( "Failed to load ticket file '#{ file_path } ' (file not readable)" )
1066
- return nil
1067
- end
1074
+ def load_credential_from_file ( path , options = { } )
1075
+ # Load a database reference or a path
1076
+ if path . start_with? ( 'id:' )
1077
+ id = path . delete_prefix ( 'id:' )
1078
+ storage = Msf ::Exploit ::Remote ::Kerberos ::Ticket ::Storage ::ReadOnly . new ( framework : framework )
1079
+ cache = storage . tickets ( { id : id } ) . first &.ccache
1080
+ if !cache
1081
+ wlog ( "Invalid cache id #{ id } provided" ) unless cache
1082
+ return { credential : nil }
1083
+ end
1084
+ else
1085
+ unless File . readable? ( file_path . to_s )
1086
+ wlog ( "Failed to load ticket file '#{ file_path } ' (file not readable)" )
1087
+ return nil
1088
+ end
1068
1089
1069
- begin
1070
- cache = Rex ::Proto ::Kerberos ::CredentialCache ::Krb5Ccache . read ( File . binread ( file_path ) )
1071
- rescue StandardError => e
1072
- elog ( "Failed to load ticket file '#{ file_path } ' (parsing failed)" , error : e )
1073
- return nil
1090
+ begin
1091
+ cache = Rex ::Proto ::Kerberos ::CredentialCache ::Krb5Ccache . read ( File . binread ( file_path ) )
1092
+ rescue StandardError => e
1093
+ elog ( "Failed to load ticket file '#{ file_path } ' (parsing failed)" , error : e )
1094
+ return nil
1095
+ end
1074
1096
end
1075
1097
1076
1098
sname = options . fetch ( :sname ) { build_spn &.to_s }
1077
1099
sname_hostname = options . fetch ( :sname_hostname , nil )
1078
1100
now = Time . now . utc
1079
1101
1102
+ filter_reasons = [ ]
1103
+
1080
1104
cache . credentials . to_ary . each . with_index ( 1 ) do |credential , index |
1081
1105
tkt_start = credential . starttime == Time . at ( 0 ) . utc ? credential . authtime : credential . starttime
1082
1106
tkt_end = credential . endtime
1083
1107
1084
1108
unless tkt_start < now
1085
- wlog ( "Filtered credential #{ file_path } ##{ index } reason: Ticket start time is before now (start: #{ tkt_start } )" )
1109
+ filter_reason = "Filtered credential #{ path } ##{ index } reason: Ticket start time is before now (start: #{ tkt_start } )"
1110
+ filter_reasons << filter_reason
1111
+ wlog ( filter_reason )
1086
1112
next
1087
1113
end
1088
1114
1089
1115
unless now < tkt_end
1090
- wlog ( "Filtered credential #{ file_path } ##{ index } reason: Ticket is expired (expiration: #{ tkt_end } )" )
1116
+ wlog ( "Filtered credential #{ path } ##{ index } reason: Ticket is expired (expiration: #{ tkt_end } )" )
1117
+ filter_reasons << filter_reason
1118
+ wlog ( filter_reason )
1091
1119
next
1092
1120
end
1093
1121
1094
1122
unless !@realm || @realm . casecmp? ( credential . server . realm . to_s )
1095
- wlog ( "Filtered credential #{ file_path } ##{ index } reason: Realm (#{ @realm } ) does not match (realm: #{ credential . server . realm } )" )
1123
+ filter_reason = "Filtered credential #{ path } ##{ index } reason: Realm (#{ @realm } ) does not match (realm: #{ credential . server . realm } )"
1124
+ filter_reasons << filter_reason
1125
+ wlog ( filter_reason )
1096
1126
next
1097
1127
end
1098
1128
1099
1129
unless !sname || sname . to_s . casecmp? ( credential . server . components . snapshot . join ( '/' ) )
1100
- wlog ( "Filtered credential #{ file_path } ##{ index } reason: SPN (#{ sname } ) does not match (spn: #{ credential . server . components . snapshot . join ( '/' ) } )" )
1130
+ filter_reason = "Filtered credential #{ path } ##{ index } reason: SPN (#{ sname } ) does not match (spn: #{ credential . server . components . snapshot . join ( '/' ) } )"
1131
+ filter_reasons << filter_reason
1132
+ wlog ( filter_reason )
1101
1133
next
1102
1134
end
1103
1135
1104
1136
unless !sname_hostname ||
1105
1137
sname_hostname . to_s . downcase == credential . server . components [ 1 ] . downcase ||
1106
1138
sname_hostname . to_s . downcase . ends_with? ( '.' + credential . server . components [ 1 ] . downcase )
1107
- wlog ( "Filtered credential #{ file_path } ##{ index } reason: SPN (#{ sname_hostname } ) hostname does not match (spn: #{ credential . server . components . snapshot . join ( '/' ) } )" )
1139
+ filter_reason = "Filtered credential #{ path } ##{ index } reason: SPN (#{ sname_hostname } ) hostname does not match (spn: #{ credential . server . components . snapshot . join ( '/' ) } )"
1140
+ filter_reasons << filter_reason
1141
+ wlog ( filter_reason )
1108
1142
next
1109
1143
end
1110
1144
1111
1145
unless !@username || @username . casecmp? ( credential . client . components . last . to_s )
1112
- wlog ( "Filtered credential #{ file_path } ##{ index } reason: Username (#{ @username } ) does not match (username: #{ credential . client . components . last } )" )
1146
+ filter_reason = "Filtered credential #{ path } ##{ index } reason: Username (#{ @username } ) does not match (username: #{ credential . client . components . last } )"
1147
+ filter_reasons << filter_reason
1148
+ wlog ( filter_reason )
1113
1149
next
1114
1150
end
1115
1151
1116
- return credential
1152
+ return { credential : credential , filter_reasons : filter_reasons }
1117
1153
end
1118
1154
1119
- nil
1155
+ { credential : nil , filter_reasons : filter_reasons }
1120
1156
end
1121
1157
end
0 commit comments