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
- Variables
- func BuildCipherZeroOpenSessionRequest(messageTag byte, consoleSessionID uint32) []byte
- func BuildGetChannelAuthCapabilitiesRequest() []byte
- func BuildIPMI15SessionHeader(msgLen byte) []byte
- func BuildIPMI20SessionHeader(payloadType byte, sessionID, sessionSeq uint32, payloadLen uint16) []byte
- func BuildOpenSessionRequest(messageTag byte, requestedPrivilege byte, consoleSessionID uint32, ...) []byte
- func BuildRAKPMessage1(messageTag byte, bmcSessionID uint32, consoleNonce [RAKPNonceSize]byte, ...) ([]byte, error)
- func BuildRMCPHeader() []byte
- func CopyAuthTypeSupport1(resp []byte) (byte, bool)
- func FormatHashcatLine(username string, bmcSessionID, consoleSessionID uint32, ...) string
- func GenerateConsoleNonce() ([RAKPNonceSize]byte, error)
- func GenerateConsoleSessionID() (uint32, error)
- func IPMBChecksum(data []byte) byte
- func ParseIPMI20Payload(buf []byte, expectedPayloadType byte) ([]byte, error)
- func ParseRMCPHeader(buf []byte) error
- type AuthCapabilities
- type OpenSessionResponse
- type RAKPMessage2
Constants ¶
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.
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.
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).
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.
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 RMCPPlusStatusInsufficientResourcesRole byte = 0x0B RMCPPlusStatusInvalidNameLength byte = 0x0C )
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).
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.
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.
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).
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.
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.
const OpenSessionPayloadSize = 32
OpenSessionPayloadSize is the on-the-wire size of the Open Session Request payload (per IPMI 2.0 §13.17).
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 ¶
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").
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 ¶
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 ¶
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 ¶
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 ¶
GenerateConsoleSessionID returns a non-zero 32-bit identifier for the console-side of the Open Session exchange. Must be non-zero.
func IPMBChecksum ¶
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 ¶
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 ¶
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.