Class: Rex::Proto::Gss::KerberosToken

Inherits:
Object
  • Object
show all
Extended by:
Asn1
Defined in:
lib/rex/proto/gss/kerberos_token.rb

Overview

Represents the RFC 1964 framing around an opaque Kerberos protocol message and provides the RFC 4178 SPNEGO operations needed to carry it.

The Kerberos message payload is deliberately not decoded. In particular, AP-REQ bytes can be extracted and rebuilt without changing the encrypted ticket or authenticator.

Defined Under Namespace

Classes: ParseError

Constant Summary collapse

TOK_ID_KRB_AP_REQ =

RFC 1964 section 1.1 token identifiers.

"\x01\x00".b
TOK_ID_KRB_AP_REP =
"\x02\x00".b
TOK_ID_KRB_ERROR =
"\x03\x00".b
KERBEROS_MECHANISM_OIDS =
[
  Rex::Proto::Gss::OID_KERBEROS_5.value,
  Rex::Proto::Gss::OID_MICROSOFT_KERBEROS_5.value
].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Asn1

unwrap_pseudo_asn1, wrap_pseudo_asn1

Constructor Details

#initialize(mechanism_oid:, token_id:, payload:) ⇒ KerberosToken

Returns a new instance of KerberosToken.

Parameters:

  • mechanism_oid (String)
  • token_id (String)
  • payload (String)


47
48
49
50
51
# File 'lib/rex/proto/gss/kerberos_token.rb', line 47

def initialize(mechanism_oid:, token_id:, payload:)
  @mechanism_oid = mechanism_oid
  @token_id = token_id
  @payload = payload
end

Instance Attribute Details

#mechanism_oidString (readonly)

Returns the GSS mechanism OID.

Returns:

  • (String)

    the GSS mechanism OID



36
37
38
# File 'lib/rex/proto/gss/kerberos_token.rb', line 36

def mechanism_oid
  @mechanism_oid
end

#payloadString (readonly)

Returns the opaque Kerberos message payload.

Returns:

  • (String)

    the opaque Kerberos message payload



42
43
44
# File 'lib/rex/proto/gss/kerberos_token.rb', line 42

def payload
  @payload
end

#token_idString (readonly)

Returns the two-byte RFC 1964 token identifier.

Returns:

  • (String)

    the two-byte RFC 1964 token identifier



39
40
41
# File 'lib/rex/proto/gss/kerberos_token.rb', line 39

def token_id
  @token_id
end

Class Method Details

.binary_string(value) ⇒ String?

Coerces protocol binary fields into binary strings.

Parameters:

  • value (String, #to_binary_s, #to_s)

Returns:

  • (String, nil)

Raises:

  • (ParseError)

    if the value cannot be represented as bytes



242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
# File 'lib/rex/proto/gss/kerberos_token.rb', line 242

def self.binary_string(value)
  return nil if value.nil?

  result = if value.is_a?(String)
             value
           elsif value.respond_to?(:to_binary_s)
             value.to_binary_s
           elsif value.respond_to?(:bytesize) && value.respond_to?(:to_s)
             value.to_s
           end

  unless result.is_a?(String)
    raise ParseError, "value of type #{value.class} cannot be converted to a binary string"
  end

  result.b
end

.build_gss_ap_req(ap_req_der, mechanism_oid: Rex::Proto::Gss::OID_KERBEROS_5) ⇒ String

Builds an RFC 1964 GSS-Kerberos token containing an opaque AP-REQ.

Parameters:

  • ap_req_der (String, #to_binary_s)

    encoded AP-REQ bytes

  • mechanism_oid (OpenSSL::ASN1::ObjectId, String) (defaults to: Rex::Proto::Gss::OID_KERBEROS_5)

    the Kerberos mechanism OID placed in the GSS wrapper

Returns:

  • (String)


191
192
193
194
195
196
# File 'lib/rex/proto/gss/kerberos_token.rb', line 191

def self.build_gss_ap_req(ap_req_der, mechanism_oid: Rex::Proto::Gss::OID_KERBEROS_5)
  mechanism = asn1_object_id(mechanism_oid)
  validate_kerberos_mechanism!(mechanism.value)
  ap_req_der = required_binary_string(ap_req_der, 'AP-REQ')
  wrap_pseudo_asn1(mechanism, TOK_ID_KRB_AP_REQ + ap_req_der)
end

.build_spnego_ap_req(ap_req_der) ⇒ String

Builds an RFC 4178 SPNEGO NegTokenInit containing a GSS-Kerberos AP-REQ. The AP-REQ payload is never decoded or modified.

Parameters:

  • ap_req_der (String, #to_binary_s)

Returns:

  • (String)


233
234
235
# File 'lib/rex/proto/gss/kerberos_token.rb', line 233

def self.build_spnego_ap_req(ap_req_der)
  build_spnego_init(build_gss_ap_req(ap_req_der))
end

.build_spnego_init(mech_token, mech_types: [Rex::Proto::Gss::OID_MICROSOFT_KERBEROS_5]) ⇒ String

Builds an RFC 4178 SPNEGO NegTokenInit around an existing mechanism token.

Parameters:

  • mech_token (String, #to_binary_s)
  • mech_types (Array<OpenSSL::ASN1::ObjectId, String>) (defaults to: [Rex::Proto::Gss::OID_MICROSOFT_KERBEROS_5])

    ordered initiator mechanism preferences

Returns:

  • (String)


205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
# File 'lib/rex/proto/gss/kerberos_token.rb', line 205

def self.build_spnego_init(mech_token, mech_types: [Rex::Proto::Gss::OID_MICROSOFT_KERBEROS_5])
  encoded_mech_types = mech_types.map { |mechanism| asn1_object_id(mechanism) }
  if encoded_mech_types.empty?
    raise ParseError, 'SPNEGO NegTokenInit requires at least one mechanism type'
  end

  mech_token = required_binary_string(mech_token, 'SPNEGO mechanism token')

  OpenSSL::ASN1::ASN1Data.new([
    Rex::Proto::Gss::OID_SPNEGO,
    OpenSSL::ASN1::ASN1Data.new([
      OpenSSL::ASN1::Sequence.new([
        OpenSSL::ASN1::ASN1Data.new([
          OpenSSL::ASN1::Sequence.new(encoded_mech_types)
        ], 0, :CONTEXT_SPECIFIC),
        OpenSSL::ASN1::ASN1Data.new([
          OpenSSL::ASN1::OctetString.new(mech_token)
        ], 2, :CONTEXT_SPECIFIC)
      ])
    ], 0, :CONTEXT_SPECIFIC)
  ], 0, :APPLICATION).to_der
end

.extract_ap_req(token) ⇒ String

Extracts an opaque AP-REQ from a bare GSS-Kerberos token or an RFC 4178 SPNEGO NegTokenInit.

Parameters:

  • token (String, #to_binary_s)

Returns:

  • (String)

    the byte-identical AP-REQ payload

Raises:

  • (ParseError)

    if the token is not a Kerberos AP-REQ



132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/rex/proto/gss/kerberos_token.rb', line 132

def self.extract_ap_req(token)
  blob = binary_string(token)
  mechanism, encapsulated_token = unwrap_pseudo_asn1(blob)

  kerberos_token = if mechanism.value == Rex::Proto::Gss::OID_SPNEGO.value
                     spnego = parse_spnego_init(blob)
                     mech_token = spnego[:mech_token]
                     raise ParseError, 'SPNEGO NegTokenInit does not contain a mechanism token' if mech_token.nil?
                     raise ParseError, 'SPNEGO mechanism token must not be empty' if mech_token.empty?

                     begin
                       parse(mech_token)
                     rescue ParseError => e
                       raise ParseError, "SPNEGO mechanism token is not a valid Kerberos token: #{e.message}"
                     end
                   else
                     parse_unwrapped(mechanism, encapsulated_token)
                   end

  unless kerberos_token.ap_req?
    raise ParseError, "GSS-Kerberos token is not an AP-REQ (token ID #{kerberos_token.token_id_hex})"
  end
  if kerberos_token.payload.empty?
    raise ParseError, 'GSS-Kerberos AP-REQ payload is empty'
  end

  kerberos_token.payload
rescue ParseError
  raise
rescue OpenSSL::ASN1::ASN1Error, TypeError => e
  raise ParseError, "unable to extract Kerberos AP-REQ: #{e.message}"
end

.kerberos_ap_req?(token) ⇒ Boolean

Tests whether a GSS or SPNEGO token contains a Kerberos AP-REQ.

Parameters:

  • token (String, #to_binary_s)

Returns:

  • (Boolean)


181
182
183
# File 'lib/rex/proto/gss/kerberos_token.rb', line 181

def self.kerberos_ap_req?(token)
  !try_extract_ap_req(token).nil?
end

.parse(token) ⇒ KerberosToken

Parses a bare GSS-Kerberos token and validates its mechanism OID.

Parameters:

  • token (String, #to_binary_s)

Returns:

Raises:

  • (ParseError)

    if the token is malformed or is not a supported Kerberos mechanism



59
60
61
62
63
64
65
66
67
# File 'lib/rex/proto/gss/kerberos_token.rb', line 59

def self.parse(token)
  blob = binary_string(token)
  mechanism, encapsulated_token = unwrap_pseudo_asn1(blob)
  parse_unwrapped(mechanism, encapsulated_token)
rescue ParseError
  raise
rescue OpenSSL::ASN1::ASN1Error, TypeError => e
  raise ParseError, "unable to parse GSS-Kerberos token: #{e.message}"
end

.parse_spnego_init(token) ⇒ Hash

Parses an RFC 4178 SPNEGO NegTokenInit.

Parameters:

  • token (String, #to_binary_s)

Returns:

  • (Hash)

    SPNEGO mechanism and optimistic-token metadata

Raises:

  • (ParseError)

    if the token is malformed or does not use the SPNEGO mechanism OID



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/rex/proto/gss/kerberos_token.rb', line 75

def self.parse_spnego_init(token)
  spnego = Rex::Proto::Gss::SpnegoNegTokenInit.parse(binary_string(token))
  mechanism_oid = spnego[:gssapi][:oid].value
  unless mechanism_oid == Rex::Proto::Gss::OID_SPNEGO.value
    raise ParseError, "unsupported GSS mechanism OID #{mechanism_oid.inspect}"
  end

  mech_types = []
  index = 0
  while (mech_type = spnego.mech_type_list[index])
    mech_types << mech_type.value
    index += 1
  end
  if mech_types.empty?
    raise ParseError, 'SPNEGO NegTokenInit requires at least one mechanism type'
  end

  neg_token_init = spnego[:gssapi][:neg_token_init]
  {
    mechanism_oid: mechanism_oid,
    mech_types: mech_types,
    preferred_mech: mech_types.first,
    req_flags: neg_token_init[:context_flags]&.value,
    mech_token: spnego.mech_token,
    mech_list_mic: neg_token_init[:mech_list_mic]&.value
  }.compact
rescue ParseError
  raise
rescue RASN1::Error, TypeError => e
  raise ParseError, "unable to parse SPNEGO NegTokenInit: #{e.message}"
end

.parse_spnego_response(token) ⇒ Hash

Parses an RFC 4178 SPNEGO NegTokenResp.

Parameters:

  • token (String, #to_binary_s)

Returns:

  • (Hash)

    SPNEGO negotiation result and response-token metadata

Raises:



112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/rex/proto/gss/kerberos_token.rb', line 112

def self.parse_spnego_response(token)
  spnego = Rex::Proto::Gss::SpnegoNegTokenTarg.parse(binary_string(token))
  {
    neg_state: spnego_neg_state_name(spnego.neg_result),
    supported_mech: spnego.supported_mech,
    response_token: spnego.response_token,
    mech_list_mic: spnego.mech_list_mic
  }.compact
rescue ParseError
  raise
rescue RASN1::Error, TypeError => e
  raise ParseError, "unable to parse SPNEGO NegTokenResp: #{e.message}"
end

.try_extract_ap_req(token) ⇒ String?

Attempts to extract an AP-REQ without raising for an unsupported or malformed token. This is useful when dispatching between GSS mechanisms, such as Kerberos and NTLM.

Parameters:

  • token (String, #to_binary_s)

Returns:

  • (String, nil)

    the opaque AP-REQ payload, or nil



171
172
173
174
175
# File 'lib/rex/proto/gss/kerberos_token.rb', line 171

def self.try_extract_ap_req(token)
  extract_ap_req(token)
rescue ParseError
  nil
end

Instance Method Details

#ap_req?Boolean

Returns whether this token contains an AP-REQ.

Returns:

  • (Boolean)

    whether this token contains an AP-REQ



280
281
282
# File 'lib/rex/proto/gss/kerberos_token.rb', line 280

def ap_req?
  token_id == TOK_ID_KRB_AP_REQ
end

#token_id_hexString

Returns hexadecimal token identifier.

Returns:

  • (String)

    hexadecimal token identifier



261
262
263
# File 'lib/rex/proto/gss/kerberos_token.rb', line 261

def token_id_hex
  token_id.unpack1('H*')
end

#token_typeString

Returns readable Kerberos token type.

Returns:

  • (String)

    readable Kerberos token type



266
267
268
269
270
271
272
273
274
275
276
277
# File 'lib/rex/proto/gss/kerberos_token.rb', line 266

def token_type
  case token_id
  when TOK_ID_KRB_AP_REQ
    'AP-REQ'
  when TOK_ID_KRB_AP_REP
    'AP-REP'
  when TOK_ID_KRB_ERROR
    'KRB-ERROR'
  else
    "UNKNOWN (#{token_id_hex})"
  end
end