ipmi

package
v0.0.191 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 14, 2026 License: Apache-2.0 Imports: 5 Imported by: 0

Documentation

Overview

Package ipmi implements IPMI 1.5 and IPMI 2.0 (RMCP+) framing primitives used by the networkscan discovery plugin to send the three pre-auth probes — Get-Channel-Auth-Capabilities, Cipher-Zero Open Session (CVE-2013-4031), and RAKP-1/RAKP-2 (CVE-2013-4786).

The wire formats are defined by the IPMI 2.0 specification, primarily section 13 (RMCP and IPMI session framing) and section 13.20 (RMCP+ session formats and Open Session / RAKP exchange).

Index

Constants

View Source
const (
	PrivilegeHighest       byte = 0x00 // BMC decides
	PrivilegeCallback      byte = 0x01
	PrivilegeUser          byte = 0x02
	PrivilegeOperator      byte = 0x03
	PrivilegeAdministrator byte = 0x04
)

Privilege level requests for RMCP+ Open Session.

View Source
const (
	RMCPVersion   byte = 0x06 // RMCP version 1.0
	RMCPSeqNoACK  byte = 0xFF // Sequence value meaning "no RMCP-level ACK requested"
	RMCPClassIPMI byte = 0x07 // Message class: IPMI
)

RMCP wrapper constants. RMCP is the outer transport for both IPMI 1.5 and IPMI 2.0 sessions on UDP/623.

View Source
const (
	AuthTypeNone     byte = 0x00
	AuthTypeMD2      byte = 0x01
	AuthTypeMD5      byte = 0x02
	AuthTypeStraight byte = 0x04
	AuthTypeOEM      byte = 0x05
	AuthTypeRMCPPlus byte = 0x06
)

IPMI session auth-type values for the v1.5 session header. For the outer wrapper of an unauthenticated v1.5 message and for the RMCP+ envelope this value is either AuthTypeNone (v1.5) or AuthTypeRMCPPlus (v2.0).

View Source
const (
	PayloadTypeOpenSessionRequest  byte = 0x10
	PayloadTypeOpenSessionResponse byte = 0x11
	PayloadTypeRAKPMessage1        byte = 0x12
	PayloadTypeRAKPMessage2        byte = 0x13
	PayloadTypeRAKPMessage3        byte = 0x14
	PayloadTypeRAKPMessage4        byte = 0x15
)

IPMI 2.0 RMCP+ payload types. The payload-type byte sits at offset 5 of the v2.0 session header. We only need the open-session and RAKP types for the deep probe.

View Source
const (
	RMCPPlusStatusNoErrors                   byte = 0x00
	RMCPPlusStatusInsufficientResources      byte = 0x01
	RMCPPlusStatusInvalidSessionID           byte = 0x02
	RMCPPlusStatusInvalidPayloadType         byte = 0x03
	RMCPPlusStatusInvalidAuthAlgorithm       byte = 0x04
	RMCPPlusStatusInvalidIntegrityAlgorithm  byte = 0x05
	RMCPPlusStatusNoMatchingAuthPayload      byte = 0x06
	RMCPPlusStatusNoMatchingIntegrityPayload byte = 0x07
	RMCPPlusStatusInvalidSessionIDOpenSess   byte = 0x08
	RMCPPlusStatusInvalidRoleField           byte = 0x09
	RMCPPlusStatusUnauthorizedRole           byte = 0x0A
	RMCPPlusStatusInsufficientResourcesRole  byte = 0x0B
	RMCPPlusStatusInvalidNameLength          byte = 0x0C
	RMCPPlusStatusUnauthorizedName           byte = 0x0D
)

RMCP+ status codes. Status 0x00 is success; any non-zero value indicates a session-level error from the BMC (per IPMI 2.0 §13.24).

View Source
const (
	AuthAlgorithmRAKPNone     byte = 0x00
	AuthAlgorithmRAKPHMACSHA1 byte = 0x01

	IntegrityAlgorithmNone       byte = 0x00
	IntegrityAlgorithmHMACSHA196 byte = 0x01

	ConfidentialityAlgorithmNone      byte = 0x00
	ConfidentialityAlgorithmAESCBC128 byte = 0x01
)

Cipher suite component algorithm IDs (IPMI 2.0 §13.28). We hardcode the values we care about; Cipher Suite 0 is "all algorithms NONE", Cipher Suite 3 is HMAC-SHA1 / HMAC-SHA1-96 / AES-CBC-128.

View Source
const (
	RMCPHeaderSize          = 4  // 0x06, reserved, seq, class
	IPMI15SessionHeaderSize = 10 // authtype(1) + seq(4) + sid(4) + msglen(1)
	IPMI20SessionHeaderSize = 12 // authtype(1) + payload_type(1) + sid(4) + seq(4) + payload_len(2)
)

RMCP/IPMI session-header sizes. Useful as named constants instead of the magic numbers the parsers would otherwise be peppered with.

View Source
const HMACSHA1AuthCodeSize = 20

HMACSHA1AuthCodeSize is the length of the RAKP-2 key-exchange- authentication-code when the negotiated algorithm is HMAC-SHA1 (the algorithm CVE-2013-4786 disclosures use).

View Source
const HashcatRoleByte = rakp1NameLookupAdmin

HashcatRoleByte is the role byte we feed into FormatHashcatLine. It must match what we put on the wire in RAKP-1 or the salt won't reproduce the BMC's HMAC inputs.

View Source
const MaxUsernameLength = 16

MaxUsernameLength is the IPMI 2.0 spec maximum (§22.32). Anything longer is rejected client-side rather than producing a malformed RAKP-1.

View Source
const OpenSessionPayloadSize = 32

OpenSessionPayloadSize is the on-the-wire size of the Open Session Request payload (per IPMI 2.0 §13.17).

View Source
const RAKPNonceSize = 16

RAKPNonceSize is the fixed size of the 16-byte random nonces both sides contribute to the RAKP exchange (per IPMI 2.0 §13.20).

Variables

View Source
var DefaultUsernameGuesses = []string{"admin", "root", "Administrator", "ADMIN", "USERID"}

DefaultUsernameGuesses is the built-in list of vendor defaults the RAKP existence oracle probes when the caller doesn't supply one. Covers Dell iDRAC ("root"), Supermicro ("ADMIN"), HP iLO/MegaRAC ("admin"), the IPMI spec example user ("Administrator"), and IBM / Lenovo / classic IPMI ("USERID").

View Source
var ErrTruncatedResponse = errors.New("ipmi: response truncated")

ErrTruncatedResponse signals that the response is too short to parse the field the caller wants. Plugin code uses errors.Is to decide whether to keep going with a partial result or fail.

Functions

func BuildCipherZeroOpenSessionRequest

func BuildCipherZeroOpenSessionRequest(messageTag byte, consoleSessionID uint32) []byte

BuildCipherZeroOpenSessionRequest is the CVE-2013-4031 specialisation of BuildOpenSessionRequest with all three algorithms set to NONE. A BMC that returns status 0x00 here is critically misconfigured.

func BuildGetChannelAuthCapabilitiesRequest

func BuildGetChannelAuthCapabilitiesRequest() []byte

BuildGetChannelAuthCapabilitiesRequest constructs the IPMI v1.5 "Get Channel Authentication Capabilities" message used as the always-run discovery probe. The channel field has bit 7 set so the BMC returns IPMI 2.0 extended capabilities (the bytes Auth-Type- Support-2/3 we need to detect RMCP+ support).

func BuildIPMI15SessionHeader

func BuildIPMI15SessionHeader(msgLen byte) []byte

BuildIPMI15SessionHeader builds the unauthenticated IPMI v1.5 session header used for Get-Channel-Auth-Capabilities. msgLen is the length of the IPMB message bytes that will follow this header.

func BuildIPMI20SessionHeader

func BuildIPMI20SessionHeader(payloadType byte, sessionID, sessionSeq uint32, payloadLen uint16) []byte

BuildIPMI20SessionHeader builds the RMCP+ session header used for open-session and RAKP exchanges. payloadType is one of the PayloadType* constants. The payload itself follows the returned header bytes. sessionID and sessionSeq are both zero for the open-session and RAKP-1 messages we send (we have no established session yet); the BMC also returns zero in its responses for these payload types.

func BuildOpenSessionRequest

func BuildOpenSessionRequest(messageTag byte, requestedPrivilege byte, consoleSessionID uint32,
	authAlg, integrityAlg, confidentialityAlg byte) []byte

BuildOpenSessionRequest assembles the RMCP+ Open Session Request pre-RAKP, picking the cipher suite components from the supplied algorithm IDs. For cipher zero pass 0x00 for all three; for the HMAC-SHA1 / SHA1-96 / AES-CBC-128 suite (cipher suite 3, used by the RAKP existence-oracle probe) pass 0x01 for all three. The consoleSessionID must be non-zero and is what the BMC will echo.

func BuildRAKPMessage1

func BuildRAKPMessage1(messageTag byte, bmcSessionID uint32, consoleNonce [RAKPNonceSize]byte, username string) ([]byte, error)

BuildRAKPMessage1 assembles the RAKP-1 payload. bmcSessionID is the session ID the BMC chose in its Open Session Response. The requested-role byte uses name-only lookup + Administrator so the BMC will respond with the HMAC even if the username has lower privileges than admin.

func BuildRMCPHeader

func BuildRMCPHeader() []byte

BuildRMCPHeader builds the 4-byte RMCP envelope header used by every IPMI message we send on UDP/623.

func CopyAuthTypeSupport1

func CopyAuthTypeSupport1(resp []byte) (byte, bool)

CopyAuthTypeSupport1 returns the raw Auth-Type-Support-1 byte from the response, used by the plugin to populate the backward-compatible authType string field as a hex literal (e.g. "0x15"). Returns 0x00 and false if the response is too short.

func FormatHashcatLine

func FormatHashcatLine(username string, bmcSessionID, consoleSessionID uint32,
	consoleNonce, bmcNonce, bmcGUID []byte, role byte, hmac []byte) string

FormatHashcatLine produces a hashcat -m 7300 (IPMI2 RAKP HMAC-SHA1) formatted blob from the captured RAKP material. The format `username:salt:hmac` is documented by hashcat's example_hashes.md and matches what Metasploit's ipmi_dumphashes module emits.

salt = SID_M || SID_C || Rm || Rc || GUIDc || ROLEm || ULENGTHm || UNAMEM, all hex-lowercase, and hmac is the 20-byte HMAC-SHA1 hex-lowercase.

func GenerateConsoleNonce

func GenerateConsoleNonce() ([RAKPNonceSize]byte, error)

GenerateConsoleNonce returns 16 cryptographically random bytes for the Rm field of RAKP-1. Random data prevents BMC-side replay.

func GenerateConsoleSessionID

func GenerateConsoleSessionID() (uint32, error)

GenerateConsoleSessionID returns a non-zero 32-bit identifier for the console-side of the Open Session exchange. Must be non-zero.

func IPMBChecksum

func IPMBChecksum(data []byte) byte

IPMBChecksum returns the IPMB two's-complement checksum over the given bytes. The IPMI spec defines this as `(-sum) mod 256`, which for a uint8 is equivalent to the bitwise two's-complement (^sum + 1).

func ParseIPMI20Payload

func ParseIPMI20Payload(buf []byte, expectedPayloadType byte) ([]byte, error)

ParseIPMI20Payload validates the RMCP envelope + RMCP+ session header on an inbound v2.0 response and returns the payload bytes (the part after the session header). It enforces that the payload type matches what the caller is expecting.

func ParseRMCPHeader

func ParseRMCPHeader(buf []byte) error

ParseRMCPHeader validates the 4-byte RMCP envelope on the inbound response. It returns an error if the version or class field is wrong, indicating the response is not an IPMI message.

Types

type AuthCapabilities

type AuthCapabilities struct {
	// Channel number echoed by the BMC (response data byte 1).
	ChannelNumber byte

	// Auth-Type-Support-1 (response data byte 2):
	//   bit 0 None / 1 MD2 / 2 MD5 / 4 Straight password / 5 OEM
	//   bit 7 = IPMI 2.0 extended caps available
	AuthNone, AuthMD2, AuthMD5, AuthStraight, AuthOEM bool
	IPMI20ExtendedCapabilities                        bool

	// Auth-Type-Support-2 (response data byte 3) — user-class flags.
	KgSet                  bool
	PerMessageAuthDisabled bool
	UserLevelAuthDisabled  bool
	NonNullUsernameEnabled bool
	NullUsernameEnabled    bool
	AnonymousLoginEnabled  bool

	// Auth-Type-Support-3 (response data byte 4, IPMI 2.0 only) —
	// supported session-level protocols.
	IPMI15Supported bool
	IPMI20Supported bool

	// OEM identifier (response data bytes 5-7, LSB-first 24-bit).
	OEMID uint32

	// Tracking which optional bitmaps actually appeared in the response.
	// Bitmap1Parsed is always true on a successful return; Bitmap2Parsed
	// covers Auth-Type-Support-2 (Anonymous/Null/etc + per-msg flags);
	// Bitmap3Parsed covers Auth-Type-Support-3 (IPMI 1.5/2.0 support);
	// OEMIDParsed covers the OEM ID bytes 5-7.
	Bitmap1Parsed bool
	Bitmap2Parsed bool
	Bitmap3Parsed bool
	OEMIDParsed   bool
}

AuthCapabilities is the parsed Get-Channel-Auth-Capabilities response. The Bitmap*Parsed booleans report whether the source byte for each group of fields was actually present in the response: a v1.5-only BMC may legitimately truncate after Auth-Type-Support-1 and the caller must distinguish "field is false" from "we never read the byte." Bitmap1 is always parsed on a successful return.

func ParseAuthCapabilities

func ParseAuthCapabilities(resp []byte) (AuthCapabilities, error)

ParseAuthCapabilities parses the raw UDP response to a Get-Channel-Auth-Capabilities request. The IPMI 2.0 spec lays the reply out as:

RMCP header  (4 bytes)
IPMI v1.5 session header  (10 bytes, session ID == 0)
IPMB message length       (1 byte)
IPMB response  (varies):
  rqAddr, netfn|lun, chk1, rsAddr, rsSeq|lun, cmd echo (0x38), cc,
  channel#, auth_type_support_1, auth_type_support_2,
  auth_type_support_3, OEM_id[3], OEM_aux, chk2

All of the bits the deep probe cares about live in bytes 22-24 of the response (channel + the three auth-type-support bytes). If we can read those, we return a fully-populated struct. If only the classic v1.5 byte (auth_type_support_1) is available, we return a partial struct.

The wire offsets here are absolute into the response buffer so the caller does not have to know how the IPMB message length byte stacks with the session header.

func (AuthCapabilities) SupportsCipherZero

func (c AuthCapabilities) SupportsCipherZero() bool

SupportsCipherZero returns true if the BMC advertises the "None" authentication type. A BMC that advertises None for the requested privilege level may also accept the RMCP+ Open Session Request with cipher suite 0 — but we still send the actual probe in cipherzero.go because the bitmap is only the channel default; per-user configuration can override it.

func (AuthCapabilities) Version

func (c AuthCapabilities) Version() string

Version returns "2.0" if the bitmap reports IPMI 2.0 support and "1.5" otherwise. This replaces the hand-rolled (and broken) `response[19] & 0x02` check the old plugin used.

type OpenSessionResponse

type OpenSessionResponse struct {
	MessageTag             byte
	StatusCode             byte
	MaxPrivilegeGranted    byte
	ConsoleSessionID       uint32
	BMCSessionID           uint32
	NegotiatedAuthAlg      byte
	NegotiatedIntegrityAlg byte
	NegotiatedConfAlg      byte
}

OpenSessionResponse is the parsed Open Session Response payload. Only the fields the deep probe needs are exposed.

func ParseOpenSessionResponse

func ParseOpenSessionResponse(resp []byte) (OpenSessionResponse, error)

ParseOpenSessionResponse reads the payload returned by the BMC for an Open Session Request. A truncated response is treated as not-accepted (status != 0) rather than an error so the plugin can keep moving.

func (OpenSessionResponse) Accepted

func (r OpenSessionResponse) Accepted() bool

Accepted reports whether the BMC returned a "no errors" status. This is the headline finding for the cipher-zero probe.

type RAKPMessage2

type RAKPMessage2 struct {
	MessageTag          byte
	StatusCode          byte
	ConsoleSessionID    uint32
	BMCNonce            [RAKPNonceSize]byte
	BMCGUID             [16]byte
	KeyExchangeAuthCode []byte // 20 bytes for HMAC-SHA1
}

RAKPMessage2 is the parsed RAKP-2 response payload. The KeyExchangeAuthCode field is the HMAC-SHA1 that CVE-2013-4786 discloses pre-auth — it is keyed with the user's password.

func ParseRAKPMessage2

func ParseRAKPMessage2(resp []byte) (RAKPMessage2, error)

ParseRAKPMessage2 reads the RAKP-2 payload returned by the BMC.

func (RAKPMessage2) Success

func (m RAKPMessage2) Success() bool

Success reports whether the RAKP-2 came back with status 0x00 — the indicator that the username exists and the BMC returned a useful HMAC. Status 0x0D ("unauthorized name") means the user does not exist; that's still useful (it confirms the absence) but the hash fields will be empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL