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/spec/modules/auxiliary/admin/kerberos/keytab_spec.rb
Views: 11789
1
require 'rspec'
2
3
RSpec.describe 'kerberos keytab' do
4
include_context 'Msf::UIDriver'
5
include_context 'Msf::DBManager'
6
include_context 'Msf::Simple::Framework#modules loading'
7
8
let(:subject) do
9
load_and_create_module(
10
module_type: 'auxiliary',
11
reference_name: 'admin/kerberos/keytab'
12
)
13
end
14
15
=begin
16
Generated with heimdal ktutil; which has two additional bytes at the end for a 32-bit kvno and flags which are
17
not present in the mit format
18
19
rm -f heimdal.keytab
20
ktutil --keytab=./heimdal.keytab --verbose add --password=password [email protected] --enctype=aes256-cts-hmac-sha1-96 --kvno=1
21
ktutil --keytab=./heimdal.keytab --verbose add --password=password [email protected] --enctype=aes128-cts-hmac-sha1-96 --kvno=1
22
ktutil --keytab=./heimdal.keytab --verbose add --password=password [email protected] --enctype=arcfour-hmac-md5 --kvno=1
23
ktutil --keytab=./heimdal.keytab --verbose list
24
25
ruby -r 'active_support/core_ext/array' -e 'puts File.binread("./heimdal.keytab").bytes.map { |x| "\\x#{x.to_s(16).rjust(2, "0")}" }.in_groups_of(16).map { |row| "\"#{row.join("")}\"" }.join(" \\ \n")'
26
=end
27
let(:valid_keytab) do
28
"\x05\x02\x00\x00\x00\x54\x00\x01\x00\x0c\x44\x4f\x4d\x41\x49\x4e" \
29
"\x2e\x4c\x4f\x43\x41\x4c\x00\x0d\x41\x64\x6d\x69\x6e\x69\x73\x74" \
30
"\x72\x61\x74\x6f\x72\x00\x00\x00\x01\x63\x38\x7e\x21\x01\x00\x12" \
31
"\x00\x20\xc4\xa3\xf3\x1d\x64\xaf\xa6\x48\xa6\xd0\x8d\x07\x76\x56" \
32
"\x3e\x12\x38\xb9\x76\xd0\xb9\x0f\x79\xea\x07\x21\x94\x36\x82\x94" \
33
"\xe9\x29\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x44\x00\x01" \
34
"\x00\x0c\x44\x4f\x4d\x41\x49\x4e\x2e\x4c\x4f\x43\x41\x4c\x00\x0d" \
35
"\x41\x64\x6d\x69\x6e\x69\x73\x74\x72\x61\x74\x6f\x72\x00\x00\x00" \
36
"\x01\x63\x38\x7e\x21\x01\x00\x11\x00\x10\xba\xba\x43\xa8\xb9\x7b" \
37
"\xac\xa1\x53\xbd\x54\xb2\xf0\x77\x4a\xd7\x00\x00\x00\x01\x00\x00" \
38
"\x00\x00\x00\x00\x00\x44\x00\x01\x00\x0c\x44\x4f\x4d\x41\x49\x4e" \
39
"\x2e\x4c\x4f\x43\x41\x4c\x00\x0d\x41\x64\x6d\x69\x6e\x69\x73\x74" \
40
"\x72\x61\x74\x6f\x72\x00\x00\x00\x01\x63\x38\x7e\x21\x01\x00\x17" \
41
"\x00\x10\x88\x46\xf7\xea\xee\x8f\xb1\x17\xad\x06\xbd\xd8\x30\xb7" \
42
"\x58\x6c\x00\x00\x00\x01\x00\x00\x00\x00"
43
end
44
let(:keytab_file) { Tempfile.new('keytab') }
45
46
before(:each) do
47
Timecop.freeze(Time.parse('Jul 15, 2022 12:33:40.000000000 GMT'))
48
subject.datastore['VERBOSE'] = false
49
allow(driver).to receive(:input).and_return(driver_input)
50
allow(driver).to receive(:output).and_return(driver_output)
51
subject.init_ui(driver_input, driver_output)
52
end
53
54
after(:each) do
55
Timecop.return
56
end
57
58
describe '#add_keytab_entry' do
59
context 'when the keytab file does not exist' do
60
before(:each) do
61
File.delete(keytab_file.path)
62
subject.datastore['KEYTAB_FILE'] = keytab_file.path
63
end
64
65
context 'when supplying a key with aes126 encryption type' do
66
it 'creates a new keytab' do
67
subject.datastore['PRINCIPAL'] = 'Administrator'
68
subject.datastore['REALM'] = 'DOMAIN.LOCAL'
69
subject.datastore['KVNO'] = 1
70
subject.datastore['ENCTYPE'] = 'AES256'
71
subject.datastore['KEY'] = 'c4a3f31d64afa648a6d08d0776563e1238b976d0b90f79ea072194368294e929'
72
subject.add_keytab_entry
73
74
subject.list_keytab_entries
75
expect(@combined_output.join("\n")).to match_table <<~TABLE
76
keytab saved to #{keytab_file.path}
77
Keytab entries
78
==============
79
80
kvno type principal hash date
81
---- ---- --------- ---- ----
82
1 18 (AES256) [email protected] c4a3f31d64afa648a6d08d0776563e1238b976d0b90f79ea072194368294e929 #{Time.parse('1970-01-01 00:00:00 +0000').to_time}
83
TABLE
84
end
85
end
86
87
context 'when supplying a password with the ALL encryption type specified' do
88
it 'creates a new keytab' do
89
subject.datastore['PRINCIPAL'] = 'Administrator'
90
subject.datastore['REALM'] = 'DOMAIN.LOCAL'
91
subject.datastore['KVNO'] = 1
92
subject.datastore['ENCTYPE'] = 'ALL'
93
subject.datastore['PASSWORD'] = 'password'
94
subject.add_keytab_entry
95
96
subject.list_keytab_entries
97
expect(@combined_output.join("\n")).to match_table <<~TABLE
98
keytab saved to #{keytab_file.path}
99
Keytab entries
100
==============
101
102
kvno type principal hash date
103
---- ---- --------- ---- ----
104
1 3 (DES_CBC_MD5) [email protected] 89d3b923d6a7195e #{Time.parse('1970-01-01 00:00:00 +0000').to_time}
105
1 16 (DES3_CBC_SHA1) [email protected] 341994e0ba5b1a20d640911cda23c137b637d51a6416d6cb #{Time.parse('1970-01-01 00:00:00 +0000').to_time}
106
1 23 (RC4_HMAC) [email protected] 8846f7eaee8fb117ad06bdd830b7586c #{Time.parse('1970-01-01 00:00:00 +0000').to_time}
107
1 17 (AES128) [email protected] baba43a8b97baca153bd54b2f0774ad7 #{Time.parse('1970-01-01 00:00:00 +0000').to_time}
108
1 18 (AES256) [email protected] c4a3f31d64afa648a6d08d0776563e1238b976d0b90f79ea072194368294e929 #{Time.parse('1970-01-01 00:00:00 +0000').to_time}
109
110
TABLE
111
end
112
end
113
114
context 'when supplying a password with aes256 encryption type' do
115
it 'creates a new keytab' do
116
subject.datastore['PRINCIPAL'] = 'Administrator'
117
subject.datastore['REALM'] = 'DOMAIN.LOCAL'
118
subject.datastore['KVNO'] = 1
119
subject.datastore['ENCTYPE'] = 'AES256'
120
subject.datastore['PASSWORD'] = 'password'
121
subject.add_keytab_entry
122
123
subject.list_keytab_entries
124
expect(@combined_output.join("\n")).to match_table <<~TABLE
125
keytab saved to #{keytab_file.path}
126
Keytab entries
127
==============
128
129
kvno type principal hash date
130
---- ---- --------- ---- ----
131
1 18 (AES256) [email protected] c4a3f31d64afa648a6d08d0776563e1238b976d0b90f79ea072194368294e929 #{Time.parse('1970-01-01 00:00:00 +0000').to_time}
132
TABLE
133
end
134
end
135
end
136
137
context 'when the keytab file exists' do
138
before(:each) do
139
File.binwrite(keytab_file.path, valid_keytab)
140
subject.datastore['KEYTAB_FILE'] = keytab_file.path
141
end
142
143
context 'when supplying a password with aes256 encryption type' do
144
it 'updates the existing keytab' do
145
subject.datastore['PRINCIPAL'] = 'Administrator'
146
subject.datastore['REALM'] = 'DOMAIN.LOCAL'
147
subject.datastore['KVNO'] = 1
148
subject.datastore['ENCTYPE'] = 'AES256'
149
subject.datastore['PASSWORD'] = 'password'
150
subject.add_keytab_entry
151
152
subject.list_keytab_entries
153
expect(@combined_output.join("\n")).to match_table <<~TABLE
154
keytab saved to #{keytab_file.path}
155
Keytab entries
156
==============
157
158
kvno type principal hash date
159
---- ---- --------- ---- ----
160
1 18 (AES256) [email protected] c4a3f31d64afa648a6d08d0776563e1238b976d0b90f79ea072194368294e929 #{Time.parse('2022-10-01 17:51:29 +0000').to_time}
161
1 17 (AES128) [email protected] baba43a8b97baca153bd54b2f0774ad7 #{Time.parse('2022-10-01 17:51:29 +0000').to_time}
162
1 23 (RC4_HMAC) [email protected] 8846f7eaee8fb117ad06bdd830b7586c #{Time.parse('2022-10-01 17:51:29 +0000').to_time}
163
1 18 (AES256) [email protected] c4a3f31d64afa648a6d08d0776563e1238b976d0b90f79ea072194368294e929 #{Time.parse('1970-01-01 00:00:00 +0000').to_time}
164
TABLE
165
end
166
end
167
end
168
end
169
170
describe '#list_keytab_entries' do
171
context 'when the keytab file does not exist' do
172
it 'raises a config error' do
173
expect { subject.list_keytab_entries }.to raise_error Msf::Auxiliary::Failed, /Invalid key tab file/
174
end
175
end
176
177
context 'when the keytab file exists' do
178
before(:each) do
179
File.binwrite(keytab_file.path, valid_keytab)
180
subject.datastore['KEYTAB_FILE'] = keytab_file.path
181
end
182
183
it 'lists the available keytab entries' do
184
subject.list_keytab_entries
185
expect(@combined_output.join("\n")).to match_table <<~TABLE
186
Keytab entries
187
==============
188
189
kvno type principal hash date
190
---- ---- --------- ---- ----
191
1 18 (AES256) [email protected] c4a3f31d64afa648a6d08d0776563e1238b976d0b90f79ea072194368294e929 #{Time.parse('2022-10-01 17:51:29 +0000').to_time}
192
1 17 (AES128) [email protected] baba43a8b97baca153bd54b2f0774ad7 #{Time.parse('2022-10-01 17:51:29 +0000').to_time}
193
1 23 (RC4_HMAC) [email protected] 8846f7eaee8fb117ad06bdd830b7586c #{Time.parse('2022-10-01 17:51:29 +0000').to_time}
194
195
TABLE
196
end
197
end
198
end
199
200
describe '#export_keytab_entries' do
201
context 'when the keytab file does not exist' do
202
before(:each) do
203
File.delete(keytab_file.path)
204
subject.datastore['KEYTAB_FILE'] = keytab_file.path
205
framework.db.delete_credentials(ids: (framework.db.creds || []).map(&:id))
206
end
207
208
after(:each) do
209
framework.db.delete_credentials(ids: (framework.db.creds || []).map(&:id))
210
end
211
212
context 'when there is no database active' do
213
before(:each) do
214
allow(subject.framework.db).to receive(:active).and_return(false)
215
end
216
217
it 'notifies the user that there is no database active' do
218
subject.export_keytab_entries
219
220
expect(@combined_output.join("\n")).to match_table <<~TABLE
221
export not available, because the database is not active.
222
TABLE
223
end
224
end
225
226
context 'when there are no kerberos or ntlm creds present in the database' do
227
it 'notifies the user that there are no entries to export' do
228
subject.export_keytab_entries
229
230
expect(@combined_output.join("\n")).to match_table <<~TABLE
231
No entries to export
232
keytab saved to #{keytab_file.path}
233
234
TABLE
235
end
236
end
237
238
context 'when there are kerberos and ntlm creds present in the database' do
239
def report_creds(
240
user, hash, type: :ntlm_hash, jtr_format: '', realm_key: nil, realm_value: nil,
241
rhost: '192.0.2.2', rport: '445', myworkspace_id: nil, module_fullname: nil
242
)
243
service_data = {
244
address: rhost,
245
port: rport,
246
service_name: 'smb',
247
protocol: 'tcp',
248
workspace_id: myworkspace_id
249
}
250
credential_data = {
251
module_fullname: module_fullname,
252
origin_type: :service,
253
private_data: hash,
254
private_type: type,
255
jtr_format: jtr_format,
256
username: user
257
}.merge(service_data)
258
credential_data[:realm_key] = realm_key if realm_key
259
credential_data[:realm_value] = realm_value if realm_value
260
261
cl = framework.db.create_credential_and_login(credential_data)
262
cl.respond_to?(:core_id) ? cl.core_id : nil
263
end
264
265
before(:each) do
266
report_creds(
267
'user_without_realm', 'aad3b435b51404eeaad3b435b51404ee:e02bc503339d51f71d913c245d35b50b',
268
type: :ntlm_hash, module_fullname: subject.fullname, myworkspace_id: framework.db.default_workspace.id
269
)
270
report_creds(
271
'user_with_realm', 'aad3b435b51404eeaad3b435b51404ee:32ede47af254546a82b1743953cc4950',
272
type: :ntlm_hash, module_fullname: subject.fullname, myworkspace_id: framework.db.default_workspace.id,
273
realm_key: Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN, realm_value: 'example.local'
274
)
275
krb_key = {
276
enctype: Rex::Proto::Kerberos::Crypto::Encryption::AES256,
277
salt: "DEMO.LOCALuser_with_krbkey".b,
278
key: 'c4a3f31d64afa648a6d08d0776563e1238b976d0b90f79ea072194368294e929'
279
}
280
report_creds(
281
'user_with_krbkey', Metasploit::Credential::KrbEncKey.build_data(**krb_key),
282
type: :krb_enc_key, module_fullname: subject.fullname, myworkspace_id: framework.db.default_workspace.id,
283
realm_key: Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN, realm_value: 'demo.local'
284
)
285
end
286
287
it 'exports the creds' do
288
subject.export_keytab_entries
289
subject.list_keytab_entries
290
291
expect(@combined_output.join("\n")).to match_table <<~TABLE
292
keytab saved to #{keytab_file.path}
293
Keytab entries
294
==============
295
296
kvno type principal hash date
297
---- ---- --------- ---- ----
298
1 23 (RC4_HMAC) user_without_realm@ e02bc503339d51f71d913c245d35b50b #{Time.parse('1970-01-01 01:00:00 +0100').to_time}
299
1 23 (RC4_HMAC) [email protected] 32ede47af254546a82b1743953cc4950 #{Time.parse('1970-01-01 01:00:00 +0100').to_time}
300
1 18 (AES256) [email protected] 63346133663331643634616661363438613664303864303737363536336531323338623937366430623930663739656130373231393433363832393465393239 #{Time.parse('1970-01-01 01:00:00 +0100').to_time}
301
302
TABLE
303
end
304
end
305
end
306
end
307
end
308
309