Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/lib/rex/proto/kerberos/kerberos_logger_subscriber.rb
74553 views
1
# -*- coding: binary -*-
2
3
require 'set'
4
require 'time'
5
require 'rex/proto/kerberos/model'
6
require 'rex/proto/kerberos/crypto'
7
require 'rex/proto/kerberos/kerberos_subscriber'
8
require 'rex/proto/kerberos/kerberos_readable_text_presenter'
9
require 'rex/proto/kerberos/credential_cache/krb5_ccache_presenter'
10
11
module Rex
12
module Proto
13
module Kerberos
14
# Logs Kerberos requests/responses
15
class KerberosLoggerSubscriber < KerberosSubscriber
16
def initialize(logger:)
17
super()
18
raise 'Incompatible logger' unless logger.respond_to?(:print_line) && logger.respond_to?(:datastore)
19
20
@logger = logger
21
end
22
23
# (see Rex::Proto::Kerberos::KerberosSubscriber#on_request)
24
def on_request(request)
25
return unless trace_enabled?
26
27
request_color, _response_color = trace_colors
28
print_header('Request', request)
29
@logger.print_line("%clr#{request_color}#{format_message(request)}%clr")
30
end
31
32
# (see Rex::Proto::Kerberos::KerberosSubscriber#on_response)
33
def on_response(response)
34
return unless trace_enabled?
35
36
_request_color, response_color = trace_colors
37
print_header('Response', response)
38
if response.nil?
39
@logger.print_line('No response received')
40
return
41
end
42
43
@logger.print_line("%clr#{response_color}#{format_message(response)}%clr")
44
end
45
46
# (see Rex::Proto::Kerberos::KerberosSubscriber#on_credential)
47
def on_credential(credential, source: nil)
48
return unless trace_enabled?
49
return if credential.nil?
50
51
print_credential_header(source)
52
@logger.print_line(format_credential(credential))
53
end
54
55
private
56
57
def trace_enabled?
58
@logger.datastore['KerberosTicketTrace']
59
end
60
61
def trace_colors
62
configured_trace_colors = @logger.datastore['KerberosTicketTraceColors']
63
# Keep HttpTrace-compatible default formatting: request/response color pair.
64
trace_colors = blank_value?(configured_trace_colors) ? 'red/blu' : configured_trace_colors
65
trace_colors += '/' if trace_colors.count('/') == 0
66
trace_colors.gsub('/', ' / ').split('/').map do |color|
67
blank_value?(color&.strip) ? '' : "%bld%#{color.strip}"
68
end
69
end
70
71
def print_header(direction, message)
72
@logger.print_line('#' * 20)
73
@logger.print_line("# Kerberos #{direction}: #{message_type_name(message)}")
74
@logger.print_line('#' * 20)
75
end
76
77
def print_credential_header(source)
78
@logger.print_line('#' * 20)
79
@logger.print_line("# Kerberos Credential#{source ? ": #{source}" : ''}")
80
@logger.print_line('#' * 20)
81
end
82
83
def message_type_name(message)
84
msg_type = message.msg_type if message.respond_to?(:msg_type)
85
case msg_type
86
when Rex::Proto::Kerberos::Model::AS_REQ
87
'AS-REQ'
88
when Rex::Proto::Kerberos::Model::AS_REP
89
'AS-REP'
90
when Rex::Proto::Kerberos::Model::TGS_REQ
91
'TGS-REQ'
92
when Rex::Proto::Kerberos::Model::TGS_REP
93
'TGS-REP'
94
when Rex::Proto::Kerberos::Model::AP_REQ
95
'AP-REQ'
96
when Rex::Proto::Kerberos::Model::AP_REP
97
'AP-REP'
98
when Rex::Proto::Kerberos::Model::KRB_ERROR
99
'KRB-ERROR'
100
when nil
101
'UNKNOWN'
102
else
103
"UNKNOWN (#{msg_type})"
104
end
105
end
106
107
def format_message(message)
108
return 'null' if message.nil?
109
110
if message.respond_to?(:attributes)
111
serialized_message = serialize_element(message)
112
readable_text_presenter.present(serialized_message)
113
else
114
# Fall back for non-model objects.
115
message.to_s
116
end
117
rescue StandardError => e
118
"Kerberos trace rendering error: #{e.class}: #{e.message}"
119
end
120
121
def format_credential(credential)
122
rendered_credential = ticket_presenter.present_cred(credential)
123
[
124
'Creds: 1',
125
" Credential[0]:\n#{rendered_credential.indent(4)}"
126
].join("\n")
127
rescue StandardError => e
128
"Credential presenter error: #{e.class}: #{e.message}"
129
end
130
131
def serialize_element(element)
132
element.attributes.each_with_object({}) do |attribute, output|
133
value = element.public_send(attribute)
134
next if value.nil?
135
136
output[serialized_attribute_key(element, attribute)] = serialize_value(value, element: element, attribute: attribute.to_sym)
137
end
138
end
139
140
def serialized_attribute_key(element, attribute)
141
if attribute == :options && element.is_a?(Rex::Proto::Kerberos::Model::ApReq)
142
'ap_options'
143
elsif attribute == :options && element.is_a?(Rex::Proto::Kerberos::Model::KdcRequestBody)
144
'kdc_options'
145
else
146
attribute.to_s
147
end
148
end
149
150
def serialize_value(value, element: nil, attribute: nil)
151
if value.respond_to?(:attributes)
152
# Recursively serialize nested Kerberos model objects.
153
serialize_element(value)
154
elsif kerberos_error_code?(value)
155
# Normalize ErrorCode-like objects to a compact structured form.
156
{
157
'name' => value.name,
158
'value' => value.value,
159
'description' => value.description
160
}
161
else
162
serialize_scalar_value(value, element: element, attribute: attribute)
163
end
164
end
165
166
def serialize_scalar_value(value, element: nil, attribute: nil)
167
case value
168
when Array
169
value.map { |entry| serialize_value(entry, element: element, attribute: attribute) }
170
when Set
171
value.to_a.map { |entry| serialize_value(entry, element: element, attribute: attribute) }
172
when Hash
173
value.each_with_object({}) do |(key, entry), output|
174
output[key.to_s] = serialize_value(entry)
175
end
176
when Rex::Proto::Kerberos::Model::KerberosFlags
177
{
178
'value' => value.to_i,
179
'flags' => value.enabled_flag_names.map(&:to_s)
180
}
181
when Time
182
value.utc.iso8601
183
when String
184
serialize_string(value)
185
when Symbol
186
value.to_s
187
when Integer
188
serialize_enum_value(value, element: element, attribute: attribute) || value
189
when Float, TrueClass, FalseClass, NilClass
190
value
191
else
192
value.to_s
193
end
194
end
195
196
def serialize_enum_value(value, element:, attribute:)
197
enum_name = case attribute
198
when :msg_type
199
message_type_name_for_value(value)
200
when :type
201
enum_type_name(value, element)
202
when :etype
203
enum_etype_name(value)
204
when :name_type
205
enum_name_type_name(value, element)
206
end
207
return nil if enum_name.nil?
208
209
"#{value} (#{enum_name})"
210
end
211
212
def message_type_name_for_value(msg_type)
213
case msg_type
214
when Rex::Proto::Kerberos::Model::AS_REQ
215
'AS-REQ'
216
when Rex::Proto::Kerberos::Model::AS_REP
217
'AS-REP'
218
when Rex::Proto::Kerberos::Model::TGS_REQ
219
'TGS-REQ'
220
when Rex::Proto::Kerberos::Model::TGS_REP
221
'TGS-REP'
222
when Rex::Proto::Kerberos::Model::AP_REQ
223
'AP-REQ'
224
when Rex::Proto::Kerberos::Model::AP_REP
225
'AP-REP'
226
when Rex::Proto::Kerberos::Model::KRB_ERROR
227
'KRB-ERROR'
228
else
229
'UNKNOWN'
230
end
231
end
232
233
def enum_type_name(value, element)
234
if element.is_a?(Rex::Proto::Kerberos::Model::PreAuthDataEntry)
235
const_name_for_value(Rex::Proto::Kerberos::Model::PreAuthType, value)
236
elsif element.is_a?(Rex::Proto::Kerberos::Model::EncryptionKey)
237
enum_etype_name(value)
238
end
239
end
240
241
def enum_etype_name(value)
242
Rex::Proto::Kerberos::Crypto::Encryption.const_name(value) || 'UNKNOWN'
243
end
244
245
def enum_name_type_name(value, element)
246
return nil unless element.is_a?(Rex::Proto::Kerberos::Model::PrincipalName)
247
248
const_name_for_value(Rex::Proto::Kerberos::Model::NameType, value)
249
end
250
251
def const_name_for_value(mod, value)
252
mod.constants.each do |const_name|
253
return const_name.to_s if mod.const_get(const_name) == value
254
rescue StandardError
255
next
256
end
257
258
'UNKNOWN'
259
end
260
261
def kerberos_error_code?(value)
262
value.respond_to?(:name) && value.respond_to?(:value) && value.respond_to?(:description)
263
end
264
265
def serialize_string(value)
266
return value if printable_string?(value)
267
268
# Expand binary/non-printable strings fully in hex.
269
"[binary #{value.bytesize} bytes: #{value.unpack1('H*')}]"
270
end
271
272
def ticket_presenter
273
@ticket_presenter ||= Rex::Proto::Kerberos::CredentialCache::Krb5CcachePresenter.new(nil)
274
end
275
276
def readable_text_presenter
277
@readable_text_presenter ||= Rex::Proto::Kerberos::KerberosReadableTextPresenter.new
278
end
279
280
def printable_string?(value)
281
utf8_value = value.dup.force_encoding(::Encoding::UTF_8)
282
utf8_value.valid_encoding? && utf8_value.match?(/\A[[:print:]\r\n\t ]*\z/)
283
rescue ::Encoding::CompatibilityError
284
false
285
end
286
287
def blank_value?(value)
288
# Avoid depending on ActiveSupport's `blank?` for this Rex-level helper.
289
return true if value.nil? || value == false
290
return value.strip.empty? if value.respond_to?(:strip)
291
return value.empty? if value.respond_to?(:empty?)
292
293
false
294
end
295
end
296
end
297
end
298
end
299
300