summaryrefslogtreecommitdiffstats
path: root/nselib/ipmi.lua
diff options
context:
space:
mode:
Diffstat (limited to 'nselib/ipmi.lua')
-rw-r--r--nselib/ipmi.lua306
1 files changed, 306 insertions, 0 deletions
diff --git a/nselib/ipmi.lua b/nselib/ipmi.lua
new file mode 100644
index 0000000..ce193f9
--- /dev/null
+++ b/nselib/ipmi.lua
@@ -0,0 +1,306 @@
+---
+-- A module implementing IPMI protocol (the code is a porting of the Metasploit ipmi scanner:
+-- https://github.com/rapid7/metasploit-framework/tree/master/modules/auxiliary/scanner/ipmi)
+--
+-- @class module
+-- @name ipmi
+-- @author "Claudiu Perta <claudiu.perta@gmail.com>"
+local stdnse = require "stdnse"
+local string = require "string"
+local rand = require "rand"
+
+_ENV = stdnse.module("ipmi", stdnse.seeall)
+
+local HAVE_SSL, openssl = pcall(require,"openssl")
+
+PAYLOADS = {
+ ["IPMI"] = 0,
+ ["PAYLOAD_SOL"] = 1,
+ ["RMCPPLUSOPEN_REQ"] = 0x10,
+ ["RMCPPLUSOPEN_REP"] = 0x11,
+ ["RAKP1"] = 0x12,
+ ["RAKP2"] = 0x13,
+ ["RAKP3"] = 0x14,
+ ["RAKP4"] = 0x15,
+}
+
+RMCP_ERRORS = {
+ [1] = "Insufficient resources to create new session \
+ (wait for existing sessions to timeout)",
+
+ -- Shouldn't occur.
+ [2] = "Invalid Session ID",
+
+ -- Shouldn't occur.
+ [3] = "Invalid payload type",
+
+ -- If these happen, we need to enhance our mechanism for detecting
+ -- supported auth algorithms.
+ [4] = "Invalid authentication algorithm",
+ [5] = "Invalid integrity algorithm",
+
+ [6] = "No matching authentication payload",
+ [7] = "No matching integrity payload",
+
+ -- This suggests the session was timed out while trying to negotiate,
+ -- shouldn't happen.
+ [8] = "Inactive Session ID",
+
+ [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",
+
+ -- Never observed, most likely a bug in xCAT or IPMI device.
+ [0x12] = "Illegal or unrecognized parameter",
+}
+
+channel_auth_request = function()
+ return (
+ "\x06\x00\xff\x07" .. -- Header
+ "\x00\x00\x00\x00" ..
+ "\x00\x00\x00\x00\x00\x09\x20\x18" ..
+ "\xc8\x81\x00\x38\x8e\x04\xb5"
+ )
+end
+
+rmcpplus_header = function (payload_type)
+ return (
+ "\x06\x00\xff\x07" .. -- RMCP Header
+ "\x06" .. -- RMCP+ Authentication Type
+ string.char(PAYLOADS[payload_type]) .. -- Payload Type
+ "\x00\x00\x00\x00" .. -- Session ID
+ "\x00\x00\x00\x00" -- Sequence Number
+ )
+end
+
+-- Open rmcpplus_request
+session_open_request = function(console_session_id)
+ local data = (
+ "\x00\x00" .. -- Maximum Access
+ "\x00\x00" .. -- Reserved
+ console_session_id ..
+ "\x00\x00\x00\x08" ..
+ "\x01\x00\x00\x00" ..
+ "\x01\x00\x00\x08" ..
+ "\x01\x00\x00\x00" .. -- HMAC-SHA1
+ "\x02\x00\x00\x08" ..
+ "\x01\x00\x00\x00" -- AES Encryption
+ )
+
+ return rmcpplus_header("RMCPPLUSOPEN_REQ") .. string.pack("<s2", data)
+end
+
+-- Open rmcpplus_request
+session_open_cipher_zero_request = function(console_session_id)
+ console_session_id = console_session_id or rand.random_string(4)
+
+ local data = (
+ "\x00\x00" .. -- Maximum Access
+ "\x00\x00" .. -- Reserved
+ console_session_id ..
+ "\x00\x00\x00\x08" ..
+ "\x00\x00\x00\x00" .. -- Cipher-zero
+ "\x01\x00\x00\x08" ..
+ "\x00\x00\x00\x00" .. -- Cipher-zero
+ "\x02\x00\x00\x08" ..
+ "\x00\x00\x00\x00" -- No Encryption
+ )
+
+ return rmcpplus_header("RMCPPLUSOPEN_REQ") .. string.pack("<s2", data)
+end
+
+rakp_1_request = function(bmc_session_id, console_random_id, username)
+ local data = string.pack(
+ "<Bxxx I c16 Bxx s1",
+ 0, -- Message Tag
+ bmc_session_id,
+ console_random_id,
+ 0x14, -- Privilege level
+ username
+ )
+
+ return rmcpplus_header("RAKP1") .. string.pack("<s2", data)
+end
+
+rakp_hmac_sha1_salt = function(
+ console_session_id,
+ bmc_session_id,
+ console_random_id,
+ bmc_random_id,
+ bmc_guid,
+ authorization_level,
+ username)
+
+ local salt = string.pack(
+ "c4 I c16c16c16 Bs1",
+ console_session_id,
+ bmc_session_id,
+ console_random_id,
+ bmc_random_id,
+ bmc_guid,
+ authorization_level,
+ username
+ )
+
+ return salt
+
+end
+
+verify_rakp_hmac_sha1 = function(salt, hash, password)
+ if not(HAVE_SSL) then
+ return false
+ end
+
+ local digest = openssl.hmac('sha1', password, salt)
+ return (digest == hash)
+end
+
+--[[
+Multi-byte fields in RMCP/ASF fields are specified as being transmitted in
+'Network Byte Order' - meaning most-significant byte first.
+RMCP and ASF-specified fields are therefore transferred most-significant byte
+first.
+The IPMI convention is to transfer multi-byte numeric fields least-significant
+Byte first. Therefore, unless otherwise specified:
+Data in the IPMI Session Header and IPMI Message fields are transmitted
+least-significant byte first.
+--]]
+
+parse_channel_auth_reply = function(reply)
+ local data = {}
+ local pos = 1
+ local value
+
+ data.rmcp_version,
+ data.rmcp_padding,
+ data.rmcp_sequence,
+ value, pos = string.unpack("<BBBB", reply, pos)
+
+ data.rmcp_mtype = ((value & 0x80) ~= 0)
+ data.rmcp_class = (value & 0x7F)
+
+ data.session_auth_type,
+ data.session_sequence,
+ data.session_id,
+ data.message_length,
+ data.ipmi_tgt_address,
+ data.ipmi_tgt_lun,
+ data.ipmi_header_checksum,
+ data.ipmi_src_address,
+ data.ipmi_src_lun,
+ data.ipmi_command,
+ data.ipmi_completion_code,
+ data.ipmi_channel,
+ value, pos = string.unpack("<BI4I4BBBBBBBBBB", reply, pos)
+
+ data.ipmi_compat_20 = ((value & 0x80) ~= 0)
+ data.ipmi_compat_reserved1 = ((value & 0x40) ~= 0)
+ data.ipmi_compat_oem_auth = ((value & 0x20) ~= 0)
+ data.ipmi_compat_password = ((value & 0x10) ~= 0)
+ data.ipmi_compat_reserved2 = ((value & 0x08) ~= 0)
+ data.ipmi_compat_md5 = ((value & 0x04) ~= 0)
+ data.ipmi_compat_md2 = ((value & 0x02) ~= 0)
+ data.ipmi_compat_none = ((value & 0x01) ~= 0)
+
+ value, pos = string.unpack("B", reply, pos)
+ data.ipmi_user_reserved1 = ((value >> 6) & 0x03)
+ data.ipmi_user_kg = ((value & 0x20) ~= 0)
+ data.ipmi_user_disable_message_auth = ((value & 0x10) ~= 0)
+ data.ipmi_user_disable_user_auth = ((value & 0x08) ~= 0)
+ data.ipmi_user_non_null = ((value & 0x04) ~= 0)
+ data.ipmi_user_null = ((value & 0x02) ~= 0)
+ data.ipmi_user_anonymous = ((value & 0x01) ~= 0)
+
+ value, pos = string.unpack("B", reply, pos)
+ data.ipmi_conn_reserved1 = ((value >> 2) & 0x3F)
+ data.ipmi_conn_20 = ((value & 0x02) ~= 0)
+ data.ipmi_conn_15 = ((value & 0x01) ~= 0)
+
+ -- 24 bits OEMID
+ data.ipmi_oem_id, pos = string.unpack("<I3", reply, pos)
+ data.ipmi_oem_data = reply:sub(pos)
+
+ return data
+end
+
+parse_open_session_reply = function(reply)
+ local data = {}
+ local pos = 1
+ local value
+
+ -- 4 bytes Header
+ data.rmcp_version,
+ data.rmcp_padding,
+ data.rmcp_sequence,
+ value, pos = string.unpack("BBBB", reply, pos)
+ -- bit 1
+ data.rmcp_mtype = ((value & 0x80) ~= 0)
+ -- bit [2:8]
+ data.rmcp_class = (value & 0x7F)
+
+ data.session_auth_type,
+ value, pos = string.unpack("BB", reply, pos)
+ -- bit 1
+ data.session_payload_encrypted = ((value & 0x80) ~= 0)
+ -- bit 2
+ data.session_payload_authenticated = ((value & 0x40) ~= 0)
+ -- bit [3:8]
+ data.session_payload_type = (value & 0x3F)
+
+ data.session_id,
+ data.session_sequence,
+ data.message_length,
+ data.ignored1,
+ data.error_code,
+ data.ignored2,
+ data.console_session_id,
+ data.bmc_session_id, pos = string.unpack("<I4I4I2BBI2I4I4", reply, pos)
+
+ return data
+end
+
+parse_rakp_1_reply = function(reply)
+ local data = {}
+ local pos = 1
+ local value
+
+ -- 4 bytes Header
+ data.rmcp_version,
+ data.rmcp_padding,
+ data.rmcp_sequence,
+ value, pos = string.unpack("BBBB", reply, pos)
+ -- bit 1
+ data.rmcp_mtype = ((value & 0x80) ~= 0)
+ -- bit [2:8]
+ data.rmcp_class = (value & 0x7F)
+
+ data.session_auth_type,
+ value, pos = string.unpack("BB", reply, pos)
+ -- bit 1
+ data.session_payload_encrypted = ((value & 0x80) ~= 0)
+ -- bit 2
+ data.session_payload_authenticated = ((value & 0x40) ~= 0)
+ -- bit [3:8]
+ data.session_payload_type = (value & 0x3F)
+
+ data.session_id,
+ data.session_sequence,
+ data.message_length,
+ data.ignored1,
+ data.error_code,
+ data.ignored2,
+ data.console_session_id,
+ data.bmc_random_id,
+ data.bmc_guid,
+ data.hmac_sha1, pos = string.unpack("<I4I4I2BBI2I4c16c16c20", reply, pos)
+
+ return data
+end
+
+return _ENV;