Jan 262013
 

Not all packets are equal.

If you send a single UDP packet to port 623 that contains an “Get Channel Authentication Capabilities” (see secion 22.13 of the IPMI v2 spec), you’ll get back a packet that has some interesting features.

You can get this by parsing the output of “ipmitool -v -v -H 10.0.0.1 -U user -P password lan print”, but more systems have python than ipmitool, so I wrote a python script to send the magic packet and parse the return. Data data everywhere, fun finding the information.

The script takes a IP/hosts as a single argument, will time out after 5 seconds if it doesn’t get a response. You really don’t want to see it come back with anonymous login enabled… interesting you can tell, per the spec, of problems w/o any auth.

1 packet, tens of bytes, 11 potential issues; best bang for the buck I’ve seen… perhaps ever.

– what version of IPMI is supported, 1.5, 2.0,. or both
– “none” authentication is supported
– MD2 auth is allowed
– use of the straight password/key auth supported
– OEM auth (maybe ok, maybe not)
– reserved auth bit in use
– KG key is set to default
– per message auth is disabled
– user level auth is disabled
– null usernames allowed
– anonymous login enabled and in use

#!/usr/bin/python

#
# do an Get Channel Authentication Capabilities (see p142 of the IPMI v 2 spec)
#
# Usage: $0 ip-address
#
# Sends request, tears up response, does a little sanity checking, prints out some stuff
#
# Lots of comments are quotes from the most recent IPMI 2.0 spec.
#

from socket import *
import sys

BYTE_SIZE = 8

try:
target = sys.argv[1]
except:
print("usage: %s target" % sys.argv[0])
exit(1)

# in seconds
timeout = 5
# udp
PORT = 623

# assume not until proven otherwise
ipmi15_support = 1
ipmi20_support = 0

# get chan auth packet... snuffled from tcpdump & wireshark traffic
payload = "\x06\x00\xff\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x20\x18\xc8\x81\x00\x38\x8e\x04\xb5"

#
# helper function, just looks up a position in a string, which
# is representing a byte of network data.
#
# The function returns the bit at position X; position 0 == least significant
#
# lots of assumptions! #1 is that byte is 8 bits, #2 is that the bits are in a certain order
#
def check_bit(byte, position):

if position > BYTE_SIZE:
print("out-of-byte-bounds (%s)" % position)
return(-1)

# print("\t\tchecking %s[%d]:" % (byte, position)),
byte = bin(byte)[2:].rjust(8, '0')[::-1]
# print("\tbits => " + byte),

#
# make a string of bits that make up the bytes; flip byte
# so bit[0] = LSD (least-sig-digit)
#
# print("\tB:"),
# print(byte)

if byte[position] == "1":
return(1)

return(0)

#
# all the action goes here... if get a response, rip it up,
# compare it to the IPMI spec to see if it makes sense.
#
def parse_response(source, packet):

# sum of problems found
problems = 0

# did we get something that looks IPMI-esque?
ipmi_support = ord(packet[3])

if check_bit(ipmi_support, 0):
ipmi20_support = 1
if check_bit(ipmi_support, 1):
ipmi15_support = 1

if not ipmi20_support and not ipmi15_support:
print("this doesn't support 1.5 or 2.0 IPMI, bailin' out")
exit(2)

#
# Channel Number
#
# Channel number that the Authentication Capabilities is being
# returned for. If the channel number in the request was set to
# Eh, this will return the channel number for the channel that the
# request was received on.
#
channel = ord(packet[0])

print("Channel %s:" % channel),

print("IPMI "),
if ipmi15_support:
print("1.5"),

if ipmi20_support:
print("2.0"),
print(" supported")

#
# Authentication Type Support
#
# Bit-by-bit breakdown of this byte

# [7] 1b = IPMI v2.0+ extended capabilities available.
# 0b = IPMI v1.5 support only.
#
# [6] reserved
#
# [5] OEM proprietary (per OEM identified by the IANA OEM ID in the RMCP Ping Response)
# [4] straight password / key
# [3] reserved
# [2] MD5
# [1] MD2
# [0] none
#
auth_support = ord(packet[1])

if check_bit(auth_support, 7):
# print("\tIPMI 2.0 extended data:")
if check_bit(auth_support, 0):
print("*\tNo auth is supported")
problems += 1
if check_bit(auth_support, 1):
print("*\tMD2 auth supported")
problems += 1
if check_bit(auth_support, 2):
print("\tMD5 auth supported")
if check_bit(auth_support, 4):
print("*\tstraight password/key auth supported")
problems += 1
if check_bit(auth_support, 5):
print("*\tOEM auth supported (maybe it's ok, maybe not)") # maybe good, maybe not
problems += 1
if check_bit(auth_support, 6):
print("*\tUsing funky reserved bit, maybe trouble?")

#
# next thingee... this byte is a bit overloaded, so
# it has a few things here.
#
# Bit-by-bit breakdown from spec:
#
# [7:6] - reserved
# [5] - KG status (two-key login status). Applies to v2.0/RMCP+ RAKP Authentication only. Otherwise, ignore as "reserved".
#
# 0b = KG is set to default (all zeros). User key KUID will be used in place of KG in RAKP
# 1b = KG is set to non-zero value. (Knowledge of both KG and user password (if not anonymous login) required for activating session.)
#
# Following bits apply to IPMI v1.5 and v2.0:
#
# [4] - Per-message Authentication status
# 0b = Per-message Authentication is enabled
# 1b = Per-message Authentication is disabled
#
# [3] - User Level Authentication status
# 0b = User Level Authentication is enabled
# 1b = User Level Authentication is disabled
#
# [2:0] - Anonymous Login status
#
# [2] 1b = Non-null usernames enabled. (One or more users are enabled that have non-null usernames).
#
# [1] 1b = Null usernames enabled (One or more users that have a null username, but non-null password, are presently enabled)
#
# [0] 1b = Anonymous Login enabled (A user that has a null username and null password is presently enabled)
#
auth_stuff = ord(packet[2])

#
# if !IPMI 2.0/RMCP+ RAKP ignore
#
if ipmi20_support:
if check_bit(auth_stuff, 5):
print("*\tKG key is set to default (all 0's)")
problems += 1
else:
print("\tKG key has been set to a non-zero value")

# both 1.5/2.0
if check_bit(auth_stuff, 4):
print("*\tPer message auth is disabled")
problems += 1
else:
print("\tPer message auth is enabled")

if check_bit(auth_stuff, 3):
print("*\tUser lvl auth is disabled")
problems += 1
else:
print("\tUser lvl auth is enabled")

if check_bit(auth_stuff, 2):
print("\tNon-null usernames enabled")
else:
print("*\tnull usernames allowed")
problems += 1

if check_bit(auth_stuff, 6):
print("*\tAnonymous login enabled (really bad: a user that has a null username & password is enabled!)")
problems += 1
else:
print("\tAnonymous login disabled")

if problems > 0:
print("%s\t%s problems found (marked with *'s)" % (target, problems))
else:
print("%s\tNo problems found" % target)

#
# create socket & bind to local port
#

udp = socket(AF_INET, SOCK_DGRAM)
sake = udp.getsockname()
udp.bind(sake)

# actually send packet
try:
udp.settimeout(timeout)

# if udp.sendto(" ", (target, PORT)) <= 0:
if udp.sendto(payload, (target, PORT)) <= 0:
print("couldn't send packet to %s" % target)

# catch response
data,addr = udp.recvfrom(512)

# skip the header
data = data[21:]

udp.close()

parse_response(addr[0], data)

#
# exceptions... this should also catch parsing stuff in parse_response
#
except Exception, e:
print("hmmm.... problems in paradise, tonto: %s, bailin'" % e)

Sorry, the comment form is closed at this time.