CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/auxiliary/admin/kerberos/keytab.rb
Views: 1904
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
def initialize(info = {})
8
super(
9
update_info(
10
info,
11
'Name' => 'Kerberos keytab utilities',
12
'Description' => %q{
13
Utilities for interacting with keytab files, which can store the hashed passwords of one or
14
more principals.
15
16
Discovered keytab files can be used to generate Kerberos Ticket Granting Tickets, or bruteforced
17
offline.
18
19
Keytab files can be also useful for decrypting Kerberos traffic using Wireshark dissectors,
20
including the krbtgt encrypted blobs if the AES password hash is used.
21
},
22
'Author' => [
23
'alanfoster' # Metasploit Module
24
],
25
'References' => [
26
],
27
'License' => MSF_LICENSE,
28
'Notes' => {
29
'Stability' => [],
30
'SideEffects' => [],
31
'Reliability' => []
32
},
33
'Actions' => [
34
['LIST', { 'Description' => 'List the entries in the keytab file' }],
35
['ADD', { 'Description' => 'Add a new entry to the keytab file' }],
36
['EXPORT', { 'Description' => 'Export the current database creds to the keytab file' }]
37
],
38
'DefaultAction' => 'LIST',
39
'DefaultOptions' => {
40
'VERBOSE' => true
41
}
42
)
43
)
44
45
supported_encryption_names = ['ALL']
46
supported_encryption_names += Rex::Proto::Kerberos::Crypto::Encryption::SUPPORTED_ENCRYPTIONS
47
.map { |id| Rex::Proto::Kerberos::Crypto::Encryption.const_name(id) }
48
49
register_options(
50
[
51
OptString.new('KEYTAB_FILE', [true, 'The keytab file to manipulate']),
52
OptString.new('PRINCIPAL', [false, 'The kerberos principal name']),
53
OptString.new('REALM', [false, 'The kerberos realm']),
54
OptEnum.new('ENCTYPE', [false, 'The enctype to use. If a password is specified this can set to \'ALL\'', supported_encryption_names[0], supported_encryption_names]),
55
OptString.new('KEY', [false, 'The key to use. If not specified, the key will be generated from the password']),
56
OptString.new('PASSWORD', [false, 'The password. If not specified, the KEY option will be used']),
57
OptString.new('SALT', [false, 'The salt to use when creating a key from the password. If not specified, this will be generated from the principal name']),
58
OptInt.new('KVNO', [true, 'The kerberos key version number', 1]),
59
OptEnum.new('OUTPUT_FORMAT', [true, 'The output format to use for listing keytab entries', 'table', %w[csv table]]),
60
]
61
)
62
end
63
64
def run
65
if datastore['KEYTAB_FILE'].blank?
66
fail_with(Failure::BadConfig, 'KEYTAB_FILE must be set to a non-empty string')
67
end
68
69
case action.name
70
when 'LIST'
71
list_keytab_entries
72
when 'ADD'
73
add_keytab_entry
74
when 'EXPORT'
75
export_keytab_entries
76
end
77
end
78
79
# Export the keytab entries from the database into the given keytab file. The keytab file will be created if it did not previously exist.
80
def export_keytab_entries
81
unless framework.db.active
82
print_error('export not available, because the database is not active.')
83
return
84
end
85
86
keytab_path = datastore['KEYTAB_FILE']
87
keytab = read_or_initialize_keytab(keytab_path)
88
89
# Kerberos encryption keys, most likely extracted from running secrets dump
90
kerberos_key_creds = framework.db.creds(type: 'Metasploit::Credential::KrbEncKey')
91
keytab_entries = kerberos_key_creds.map do |cred|
92
[
93
cred.id,
94
{
95
realm: cred.realm.value,
96
components: cred.public.username.split('/'),
97
name_type: Rex::Proto::Kerberos::Model::NameType::NT_PRINCIPAL,
98
timestamp: Time.at(0).utc,
99
vno8: datastore['KVNO'],
100
vno: datastore['KVNO'],
101
keyblock: {
102
enctype: cred.private.enctype,
103
data: cred.private.key
104
}
105
}
106
]
107
end
108
109
# Additionally append NTHASH values, which don't require a salt
110
nthash_creds = framework.db.creds(type: 'Metasploit::Credential::NTLMHash')
111
keytab_entries += nthash_creds.map do |cred|
112
nthash = cred.private.to_s.split(':').last
113
[
114
cred.id,
115
{
116
realm: cred.realm&.value.to_s,
117
components: cred.public.username.split('/'),
118
name_type: Rex::Proto::Kerberos::Model::NameType::NT_PRINCIPAL,
119
timestamp: Time.at(0).utc,
120
vno8: datastore['KVNO'],
121
vno: datastore['KVNO'],
122
keyblock: {
123
enctype: Rex::Proto::Kerberos::Crypto::Encryption::RC4_HMAC,
124
data: [nthash].pack('H*')
125
}
126
}
127
]
128
end
129
130
if keytab_entries.empty?
131
print_status('No entries to export')
132
end
133
134
keytab.key_entries.concat(keytab_entries.sort_by { |id, _entry| id }.to_h.values)
135
write_keytab(keytab_path, keytab)
136
end
137
138
# Add keytab entries into the given keytab file. The keytab file will be created if it did not previously exist.
139
def add_keytab_entry
140
keytab_path = datastore['KEYTAB_FILE']
141
keytab = read_or_initialize_keytab(keytab_path)
142
143
principal = datastore['PRINCIPAL']
144
fail_with(Failure::BadConfig, 'PRINCIPAL must be set to a non-empty string') if principal.blank?
145
146
realm = datastore['REALM']
147
fail_with(Failure::BadConfig, 'REALM must be set to a non-empty string') if realm.blank?
148
149
if /[[:lower:]]/.match(realm)
150
print_warning("REALM option has lowercase letters present - this may not work as expected for Window's Active Directory environments which uses a uppercase domain")
151
end
152
153
keyblocks = []
154
if datastore['KEY'].present?
155
fail_with(Failure::BadConfig, 'enctype ALL not supported when KEY is set') if datastore['ENCTYPE'] == 'ALL'
156
157
keyblocks << {
158
enctype: Rex::Proto::Kerberos::Crypto::Encryption.value_for(datastore['ENCTYPE']),
159
data: [datastore['KEY']].pack('H*')
160
}
161
elsif datastore['PASSWORD'].present?
162
password = datastore['PASSWORD']
163
salt = datastore['SALT']
164
if salt.blank?
165
salt = "#{realm}#{principal.split('/')[0]}"
166
vprint_status("Generating key with salt: #{salt}. The SALT option can be set manually")
167
end
168
169
if datastore['ENCTYPE'] == 'ALL'
170
enctypes = Rex::Proto::Kerberos::Crypto::Encryption::SUPPORTED_ENCRYPTIONS
171
else
172
enctypes = [Rex::Proto::Kerberos::Crypto::Encryption.value_for(datastore['ENCTYPE'])]
173
end
174
175
enctypes.each do |enctype|
176
encryptor = Rex::Proto::Kerberos::Crypto::Encryption.from_etype(enctype)
177
keyblocks << {
178
enctype: enctype,
179
data: encryptor.string_to_key(password, salt)
180
}
181
end
182
else
183
fail_with(Failure::BadConfig, 'KEY or PASSWORD required to add a new entry')
184
end
185
186
keytab_entries = keyblocks.map do |keyblock|
187
{
188
realm: realm,
189
components: principal.split('/'),
190
name_type: Rex::Proto::Kerberos::Model::NameType::NT_PRINCIPAL,
191
timestamp: Time.at(0).utc,
192
vno8: datastore['KVNO'],
193
vno: datastore['KVNO'],
194
keyblock: keyblock
195
}
196
end
197
keytab.key_entries.concat(keytab_entries)
198
write_keytab(keytab_path, keytab)
199
end
200
201
# List the keytab entries within the keytab file
202
def list_keytab_entries
203
if datastore['KEYTAB_FILE'].blank? || !File.exist?(datastore['KEYTAB_FILE'])
204
fail_with(Failure::BadConfig, 'Invalid key tab file')
205
end
206
207
tbl = Rex::Text::Table.new(
208
'Header' => 'Keytab entries',
209
'Indent' => 1,
210
'WordWrap' => false,
211
'Columns' => %w[
212
kvno
213
type
214
principal
215
hash
216
date
217
]
218
)
219
220
keytab = File.binread(datastore['KEYTAB_FILE'])
221
keytab = Rex::Proto::Kerberos::Keytab::Krb5Keytab.read(keytab)
222
keytab.key_entries.each do |entry|
223
keyblock = entry.keyblock
224
tbl << [
225
entry.vno,
226
enctype_name(keyblock.enctype),
227
entry.principal,
228
keyblock.data.unpack1('H*'),
229
entry.timestamp,
230
]
231
end
232
233
case datastore['OUTPUT_FORMAT']
234
when 'table'
235
print_line(tbl.to_s)
236
when 'csv'
237
print_line(tbl.to_csv)
238
else
239
print_line(tbl.to_s)
240
end
241
end
242
243
# @param [Object] id
244
# @see Rex::Proto::Kerberos::Crypto::Encryption
245
def enctype_name(id)
246
name = Rex::Proto::Kerberos::Crypto::Encryption.const_name(id)
247
name ? "#{id.to_s.ljust(2)} (#{name})" : id.to_s
248
end
249
250
private
251
252
# @param [String] keytab_path the keytab path
253
# @return [Rex::Proto::Kerberos::Keytab::Keytab]
254
def read_or_initialize_keytab(keytab_path)
255
return Rex::Proto::Kerberos::Keytab::Krb5Keytab.read(File.binread(keytab_path)) if File.exist?(keytab_path)
256
257
Rex::Proto::Kerberos::Keytab::Krb5Keytab.new
258
end
259
260
# @param [String] keytab_path the keytab path
261
# @param [Rex::Proto::Kerberos::Keytab::Keytab] keytab
262
def write_keytab(keytab_path, keytab)
263
File.binwrite(keytab_path, keytab.to_binary_s)
264
print_good "keytab saved to #{keytab_path}"
265
266
if datastore['VERBOSE']
267
list_keytab_entries
268
end
269
end
270
end
271
272