mirror of
https://github.com/nmap/nmap.git
synced 2025-12-12 02:39:03 +00:00
Claudiu's IPMI scripts from GSoC 2014
This commit is contained in:
@@ -1,5 +1,14 @@
|
|||||||
# Nmap Changelog ($Id$); -*-text-*-
|
# Nmap Changelog ($Id$); -*-text-*-
|
||||||
|
|
||||||
|
o [NSE] New script: ipmi-version retrieves protocol version and authentication
|
||||||
|
options from ASF-RMCP (IPMI) services. [Claudiu Perta]
|
||||||
|
|
||||||
|
o [NSE] New script: ipmi-cipher-zero checks IPMI services for Cipher Zero
|
||||||
|
support, which allows connection without a password. [Claudiu Perta]
|
||||||
|
|
||||||
|
o [NSE] New script: ipmi-brute performs authentication brute-forcing on IPMI
|
||||||
|
services. [Claudiu Perta]
|
||||||
|
|
||||||
o [NSE][GH#352] New script: mqtt-subscribe connects to a MQTT broker, subscribes to
|
o [NSE][GH#352] New script: mqtt-subscribe connects to a MQTT broker, subscribes to
|
||||||
topics, and lists the messages received. [Mak Kolybabi]
|
topics, and lists the messages received. [Mak Kolybabi]
|
||||||
|
|
||||||
|
|||||||
307
nselib/ipmi.lua
Normal file
307
nselib/ipmi.lua
Normal file
@@ -0,0 +1,307 @@
|
|||||||
|
---
|
||||||
|
-- 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 bin = require "bin"
|
||||||
|
local bit = require "bit"
|
||||||
|
local math = require "math"
|
||||||
|
local stdnse = require "stdnse"
|
||||||
|
local string = require "string"
|
||||||
|
local table = require "table"
|
||||||
|
|
||||||
|
_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 bin.pack("<AP", rmcpplus_header("RMCPPLUSOPEN_REQ"), data)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Open rmcpplus_request
|
||||||
|
session_open_cipher_zero_request = function(console_session_id)
|
||||||
|
console_session_id = console_session_id or stdnse.generate_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 bin.pack("<AP", rmcpplus_header("RMCPPLUSOPEN_REQ"), data)
|
||||||
|
end
|
||||||
|
|
||||||
|
rakp_1_request = function(bmc_session_id, console_random_id, username)
|
||||||
|
local data = bin.pack(
|
||||||
|
"<AAIAAAp",
|
||||||
|
"\x00", -- Message Tag
|
||||||
|
"\x00\x00\x00", -- Reserved
|
||||||
|
bmc_session_id,
|
||||||
|
console_random_id,
|
||||||
|
"\x14", -- Privilege level
|
||||||
|
"\x00\x00", -- Reserved
|
||||||
|
username
|
||||||
|
)
|
||||||
|
|
||||||
|
return bin.pack("<AP", rmcpplus_header("RAKP1"), 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 = bin.pack(
|
||||||
|
"AIAAACp",
|
||||||
|
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
|
||||||
|
|
||||||
|
parse_channel_auth_reply = function(reply)
|
||||||
|
local data = {}
|
||||||
|
local pos = 0
|
||||||
|
local value
|
||||||
|
|
||||||
|
pos, data["rmcp_version"] = bin.unpack("<C", reply, pos)
|
||||||
|
pos, data["rmcp_padding"] = bin.unpack("<C", reply, pos)
|
||||||
|
pos, data["rmcp_sequence"] = bin.unpack("<C", reply, pos)
|
||||||
|
|
||||||
|
pos, value = bin.unpack("C", reply, pos)
|
||||||
|
data["rmcp_mtype"] = (bit.band(value, 0x80) ~= 0)
|
||||||
|
data["rmcp_class"] = bit.band(value, 0x7F)
|
||||||
|
|
||||||
|
pos, data["session_auth_type"] = bin.unpack("C", reply, pos)
|
||||||
|
pos, data["session_sequence"] = bin.unpack("<I", reply, pos)
|
||||||
|
pos, data["session_id"] = bin.unpack("<I", reply, pos)
|
||||||
|
pos, data["message_length"] = bin.unpack("C", reply, pos)
|
||||||
|
pos, data["ipmi_tgt_address"] = bin.unpack("C", reply, pos)
|
||||||
|
pos, data["ipmi_tgt_lun"] = bin.unpack("C", reply, pos)
|
||||||
|
pos, data["ipmi_header_checksum"] = bin.unpack("C", reply, pos)
|
||||||
|
pos, data["ipmi_src_address"] = bin.unpack("C", reply, pos)
|
||||||
|
pos, data["ipmi_src_lun"] = bin.unpack("C", reply, pos)
|
||||||
|
pos, data["ipmi_command"] = bin.unpack("C", reply, pos)
|
||||||
|
pos, data["ipmi_completion_code"] = bin.unpack("C", reply, pos)
|
||||||
|
pos, data["ipmi_channel"] = bin.unpack("C", reply, pos)
|
||||||
|
|
||||||
|
pos, value = bin.unpack("C", reply, pos)
|
||||||
|
data["ipmi_compat_20"] = (bit.band(value, 0x80) ~= 0)
|
||||||
|
data["ipmi_compat_reserved1"] = (bit.band(value, 0x40) ~= 0)
|
||||||
|
data["ipmi_compat_oem_auth"] = (bit.band(value, 0x20) ~= 0)
|
||||||
|
data["ipmi_compat_password"] = (bit.band(value, 0x10) ~= 0)
|
||||||
|
data["ipmi_compat_reserved2"] = (bit.band(value, 0x08) ~= 0)
|
||||||
|
data["ipmi_compat_md5"] = (bit.band(value, 0x04) ~= 0)
|
||||||
|
data["ipmi_compat_md2"] = (bit.band(value, 0x02) ~= 0)
|
||||||
|
data["ipmi_compat_none"] = (bit.band(value, 0x01) ~= 0)
|
||||||
|
|
||||||
|
pos, value = bin.unpack("C", reply, pos)
|
||||||
|
data["ipmi_user_reserved1"] = bit.band(bit.rshift(value, 6), 0x03)
|
||||||
|
data["ipmi_user_kg"] = (bit.band(value, 0x20) ~= 0)
|
||||||
|
data["ipmi_user_disable_message_auth"] = (bit.band(value, 0x10) ~= 0)
|
||||||
|
data["ipmi_user_disable_user_auth"] = (bit.band(value, 0x08) ~= 0)
|
||||||
|
data["ipmi_user_non_null"] = (bit.band(value, 0x04) ~= 0)
|
||||||
|
data["ipmi_user_null"] = (bit.band(value, 0x02) ~= 0)
|
||||||
|
data["ipmi_user_anonymous"] = (bit.band(value, 0x01) ~= 0)
|
||||||
|
|
||||||
|
pos, value = bin.unpack("C", reply, pos)
|
||||||
|
data["ipmi_conn_reserved1"] = bit.band(bit.rshift(value, 2), 0x3F)
|
||||||
|
data["ipmi_conn_20"] = (bit.band(value, 0x02) ~= 0)
|
||||||
|
data["ipmi_conn_15"] = (bit.band(value, 0x01) ~= 0)
|
||||||
|
|
||||||
|
-- 24 bits OEMID, unpack an int and shift 1 byte to the right
|
||||||
|
pos, value = bin.unpack("<I", reply, pos)
|
||||||
|
data["ipmi_oem_id"] = bit.rshift(value, 8)
|
||||||
|
-- restore one byte position
|
||||||
|
pos = pos - 1
|
||||||
|
pos, data["ipmi_oem_data"] = bin.unpack("A", reply, pos)
|
||||||
|
|
||||||
|
return data
|
||||||
|
end
|
||||||
|
|
||||||
|
parse_open_session_reply = function(reply)
|
||||||
|
local data = {}
|
||||||
|
local pos = 0
|
||||||
|
local value
|
||||||
|
|
||||||
|
-- 4 bytes Header
|
||||||
|
pos, data["rmcp_version"] = bin.unpack("C", reply, pos)
|
||||||
|
pos, data["rmcp_padding"] = bin.unpack("C", reply, pos)
|
||||||
|
pos, data["rmcp_sequence"] = bin.unpack("C", reply, pos)
|
||||||
|
|
||||||
|
pos, value = bin.unpack("C", reply, pos)
|
||||||
|
-- bit 1
|
||||||
|
data["rmcp_mtype"] = (bit.band(value, 0x80) ~= 0)
|
||||||
|
-- bit [2:8]
|
||||||
|
data["rmcp_class"] = bit.band(value, 0x7F)
|
||||||
|
|
||||||
|
pos, data["session_auth_type"] = bin.unpack("C", reply, pos)
|
||||||
|
|
||||||
|
pos, value = bin.unpack("C", reply, pos)
|
||||||
|
-- bit 1
|
||||||
|
data["session_payload_encrypted"] = (bit.band(value, 0x80) ~= 0)
|
||||||
|
-- bit 2
|
||||||
|
data["session_payload_authenticated"] = (bit.band(value, 0x40) ~= 0)
|
||||||
|
-- bit [3:8]
|
||||||
|
data["session_payload_type"] = bit.band(value, 0x3F)
|
||||||
|
|
||||||
|
pos, data["session_id"] = bin.unpack("I", reply, pos)
|
||||||
|
pos, data["session_sequence"] = bin.unpack("I", reply, pos)
|
||||||
|
pos, data["message_length"] = bin.unpack("<S", reply, pos)
|
||||||
|
pos, data["ignored1"] = bin.unpack("C", reply, pos)
|
||||||
|
pos, data["error_code"] = bin.unpack("C", reply, pos)
|
||||||
|
pos, data["ignored2"] = bin.unpack("<S", reply, pos)
|
||||||
|
pos, data["console_session_id"] = bin.unpack("I", reply, pos)
|
||||||
|
pos, data["bmc_session_id"] = bin.unpack("I", reply, pos)
|
||||||
|
|
||||||
|
return data
|
||||||
|
end
|
||||||
|
|
||||||
|
parse_rakp_1_reply = function(reply)
|
||||||
|
local data = {}
|
||||||
|
local pos = 0
|
||||||
|
local value
|
||||||
|
|
||||||
|
-- 4 bytes Header
|
||||||
|
pos, data["rmcp_version"] = bin.unpack("C", reply, pos)
|
||||||
|
pos, data["rmcp_padding"] = bin.unpack("C", reply, pos)
|
||||||
|
pos, data["rmcp_sequence"] = bin.unpack("C", reply, pos)
|
||||||
|
|
||||||
|
pos, value = bin.unpack("C", reply, pos)
|
||||||
|
-- bit 1
|
||||||
|
data["rmcp_mtype"] = (bit.band(value, 0x80) ~= 0)
|
||||||
|
-- bit [2:8]
|
||||||
|
data["rmcp_class"] = bit.band(value, 0x7F)
|
||||||
|
|
||||||
|
pos, data["session_auth_type"] = bin.unpack("C", reply, pos)
|
||||||
|
|
||||||
|
pos, value = bin.unpack("C", reply, pos)
|
||||||
|
-- bit 1
|
||||||
|
data["session_payload_encrypted"] = (bit.band(value, 0x80) ~= 0)
|
||||||
|
-- bit 2
|
||||||
|
data["session_payload_authenticated"] = (bit.band(value, 0x40) ~= 0)
|
||||||
|
-- bit [3:8]
|
||||||
|
data["session_payload_type"] = bit.band(value, 0x3F)
|
||||||
|
|
||||||
|
pos, data["session_id"] = bin.unpack("<I", reply, pos)
|
||||||
|
pos, data["session_sequence"] = bin.unpack("<I", reply, pos)
|
||||||
|
pos, data["message_length"] = bin.unpack("<S", reply, pos)
|
||||||
|
pos, data["ignored1"] = bin.unpack("C", reply, pos)
|
||||||
|
pos, data["error_code"] = bin.unpack("C", reply, pos)
|
||||||
|
pos, data["ignored2"] = bin.unpack("<S", reply, pos)
|
||||||
|
pos, data["console_session_id"] = bin.unpack("<I", reply, pos)
|
||||||
|
pos, data["bmc_random_id"] = bin.unpack("A16", reply, pos)
|
||||||
|
pos, data["bmc_guid"] = bin.unpack("A16", reply, pos)
|
||||||
|
pos, data["hmac_sha1"] = bin.unpack("A20", reply, pos)
|
||||||
|
|
||||||
|
return data
|
||||||
|
end
|
||||||
|
|
||||||
|
return _ENV;
|
||||||
133
scripts/ipmi-brute.nse
Normal file
133
scripts/ipmi-brute.nse
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
local brute = require "brute"
|
||||||
|
local creds = require "creds"
|
||||||
|
local ipmi = require "ipmi"
|
||||||
|
local nmap = require "nmap"
|
||||||
|
local shortport = require "shortport"
|
||||||
|
local stdnse = require "stdnse"
|
||||||
|
local string = require "string"
|
||||||
|
local table = require "table"
|
||||||
|
|
||||||
|
description = [[
|
||||||
|
Performs brute force password auditing against IPMI RPC server.
|
||||||
|
]]
|
||||||
|
|
||||||
|
---
|
||||||
|
-- @usage
|
||||||
|
-- nmap -sU --script ipmi-brute -p 623 <host>
|
||||||
|
--
|
||||||
|
-- @output
|
||||||
|
-- PORT STATE SERVICE REASON
|
||||||
|
-- 623/udp open|filtered unknown
|
||||||
|
-- | ipmi-brute:
|
||||||
|
-- | Accounts
|
||||||
|
-- |_ admin:admin => Valid credentials
|
||||||
|
--
|
||||||
|
|
||||||
|
author = "Claudiu Perta"
|
||||||
|
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
|
||||||
|
categories = {"intrusive", "brute"}
|
||||||
|
|
||||||
|
portrule = shortport.port_or_service(623, "asf-rmcp", "udp", {"open", "open|filtered"})
|
||||||
|
|
||||||
|
Driver = {
|
||||||
|
|
||||||
|
new = function(self, host, port)
|
||||||
|
local o = {}
|
||||||
|
setmetatable(o, self)
|
||||||
|
self.__index = self
|
||||||
|
o.host = host
|
||||||
|
o.port = port
|
||||||
|
return o
|
||||||
|
end,
|
||||||
|
|
||||||
|
connect = function(self)
|
||||||
|
self.socket = nmap.new_socket()
|
||||||
|
self.socket:set_timeout(
|
||||||
|
((self.host.times and self.host.times.timeout) or 8) * 1000)
|
||||||
|
self.socket:connect(self.host, self.port, "udp")
|
||||||
|
|
||||||
|
return true
|
||||||
|
end,
|
||||||
|
|
||||||
|
login = function(self, username, password)
|
||||||
|
local console_session_id = stdnse.generate_random_string(4)
|
||||||
|
local console_random_id = stdnse.generate_random_string(16)
|
||||||
|
|
||||||
|
local request = ipmi.session_open_request(console_session_id)
|
||||||
|
local status, reply
|
||||||
|
|
||||||
|
self.socket:send(request)
|
||||||
|
status, reply = self.socket:receive()
|
||||||
|
|
||||||
|
if not status then
|
||||||
|
return false, brute.Error:new(
|
||||||
|
"No response to IPMI open session request")
|
||||||
|
end
|
||||||
|
|
||||||
|
local session = ipmi.parse_open_session_reply(reply)
|
||||||
|
if session["session_payload_type"] ~= ipmi.PAYLOADS["RMCPPLUSOPEN_REP"] then
|
||||||
|
return false, brute.Error:new("Unknown response to open session request")
|
||||||
|
end
|
||||||
|
|
||||||
|
if session["error_code"] ~= 0 then
|
||||||
|
return false, brute.Error:new(ipmi.RMCP_ERRORS[session.error_code] or "Unknown error")
|
||||||
|
end
|
||||||
|
local bmc_session_id = session["bmc_session_id"]
|
||||||
|
local rakp1_request = ipmi.rakp_1_request(
|
||||||
|
bmc_session_id, console_random_id, username)
|
||||||
|
|
||||||
|
self.socket:send(rakp1_request)
|
||||||
|
status, reply = self.socket:receive()
|
||||||
|
|
||||||
|
if not status then
|
||||||
|
return false, brute.Error:new("No response to RAKP1 message")
|
||||||
|
end
|
||||||
|
|
||||||
|
local rakp2_message = ipmi.parse_rakp_1_reply(reply)
|
||||||
|
if rakp2_message["session_payload_type"] ~= ipmi.PAYLOADS["RAKP2"] then
|
||||||
|
return false, brute.Error:new("Unknown response to RAPK1 request")
|
||||||
|
end
|
||||||
|
|
||||||
|
if rakp2_message["error_code"] ~= 0 then
|
||||||
|
return false, brute.Error:new(
|
||||||
|
ipmi.RMCP_ERRORS[rakp2_message["error_code"]])
|
||||||
|
end
|
||||||
|
|
||||||
|
local hmac_salt = ipmi.rakp_hmac_sha1_salt(
|
||||||
|
console_session_id,
|
||||||
|
session["bmc_session_id"],
|
||||||
|
console_random_id,
|
||||||
|
rakp2_message["bmc_random_id"],
|
||||||
|
rakp2_message["bmc_guid"],
|
||||||
|
0x14,
|
||||||
|
username
|
||||||
|
)
|
||||||
|
|
||||||
|
local found = ipmi.verify_rakp_hmac_sha1(
|
||||||
|
hmac_salt, rakp2_message["hmac_sha1"], password)
|
||||||
|
|
||||||
|
if found then
|
||||||
|
return true, brute.Account:new(username, password, creds.State.VALID)
|
||||||
|
else
|
||||||
|
return false, brute.Error:new("Incorrect password")
|
||||||
|
end
|
||||||
|
|
||||||
|
end,
|
||||||
|
|
||||||
|
disconnect = function(self)
|
||||||
|
self.socket:close()
|
||||||
|
end,
|
||||||
|
|
||||||
|
check = function(host, port)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
action = function(host, port)
|
||||||
|
local status, result
|
||||||
|
local engine = brute.Engine:new(Driver, host, port)
|
||||||
|
|
||||||
|
engine.options.script_name = SCRIPT_NAME
|
||||||
|
status, result = engine:start()
|
||||||
|
return result
|
||||||
|
end
|
||||||
104
scripts/ipmi-cipher-zero.nse
Normal file
104
scripts/ipmi-cipher-zero.nse
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
local ipmi = require "ipmi"
|
||||||
|
local nmap = require "nmap"
|
||||||
|
local shortport = require "shortport"
|
||||||
|
local stdnse = require "stdnse"
|
||||||
|
local string = require "string"
|
||||||
|
local vulns = require "vulns"
|
||||||
|
|
||||||
|
description = [[
|
||||||
|
IPMI 2.0 Cipher Zero Authentication Bypass Scanner. This module identifies IPMI 2.0
|
||||||
|
compatible systems that are vulnerable to an authentication bypass vulnerability
|
||||||
|
through the use of cipher zero.
|
||||||
|
]]
|
||||||
|
|
||||||
|
---
|
||||||
|
-- @usage
|
||||||
|
-- nmap -sU --script ipmi-cipher-zero -p 623 <host>
|
||||||
|
--
|
||||||
|
-- @output
|
||||||
|
---PORT STATE SERVICE REASON
|
||||||
|
-- 623/udp open|filtered unknown no-response
|
||||||
|
-- | ipmi-cipher-zero:
|
||||||
|
-- | VULNERABLE:
|
||||||
|
-- | IPMI 2.0 RAKP Cipher Zero Authentication Bypass
|
||||||
|
-- | State: VULNERABLE
|
||||||
|
-- | Risk factor: High
|
||||||
|
-- | Description:
|
||||||
|
-- |
|
||||||
|
-- | The issue is due to the vendor shipping their devices with the
|
||||||
|
-- | cipher suite '0' (aka 'cipher zero') enabled. This allows a
|
||||||
|
-- | remote attacker to authenticate to the IPMI interface using
|
||||||
|
-- | an arbitrary password. The only information required is a valid
|
||||||
|
-- | account, but most vendors ship with a default 'admin' account.
|
||||||
|
-- | This would allow an attacker to have full control over the IPMI
|
||||||
|
-- | functionality.
|
||||||
|
-- |
|
||||||
|
-- | References:
|
||||||
|
-- | http://fish2.com/ipmi/cipherzero.html
|
||||||
|
-- | http://osvdb.org/show/osvdb/93039
|
||||||
|
-- |_ http://osvdb.org/show/osvdb/93040
|
||||||
|
--
|
||||||
|
|
||||||
|
author = "Claudiu Perta <claudiu.perta@gmail.com>"
|
||||||
|
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
|
||||||
|
categories = {"vuln", "safe"}
|
||||||
|
|
||||||
|
portrule = shortport.port_or_service(623, "asf-rmcp", "udp", {"open", "open|filtered"})
|
||||||
|
|
||||||
|
action = function(host, port)
|
||||||
|
|
||||||
|
local vuln_table = {
|
||||||
|
title = "IPMI 2.0 RAKP Cipher Zero Authentication Bypass",
|
||||||
|
state = vulns.STATE.NOT_VULN,
|
||||||
|
risk_factor = "High",
|
||||||
|
description = [[
|
||||||
|
|
||||||
|
The issue is due to the vendor shipping their devices with the
|
||||||
|
cipher suite '0' (aka 'cipher zero') enabled. This allows a
|
||||||
|
remote attacker to authenticate to the IPMI interface using
|
||||||
|
an arbitrary password. The only information required is a valid
|
||||||
|
account, but most vendors ship with a default 'admin' account.
|
||||||
|
This would allow an attacker to have full control over the IPMI
|
||||||
|
functionality
|
||||||
|
]],
|
||||||
|
references = {
|
||||||
|
'http://fish2.com/ipmi/cipherzero.html',
|
||||||
|
'http://osvdb.org/show/osvdb/93040',
|
||||||
|
'http://osvdb.org/show/osvdb/93039',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
local report = vulns.Report:new(SCRIPT_NAME, host, port)
|
||||||
|
|
||||||
|
local request = ipmi.session_open_cipher_zero_request()
|
||||||
|
|
||||||
|
local socket = nmap.new_socket()
|
||||||
|
socket:set_timeout(
|
||||||
|
((host.times and host.times.timeout) or 8) * 1000)
|
||||||
|
socket:connect(host, port, "udp")
|
||||||
|
|
||||||
|
-- Send 3 probes
|
||||||
|
local tries = 3
|
||||||
|
repeat
|
||||||
|
socket:send(request)
|
||||||
|
tries = tries - 1
|
||||||
|
until tries == 0
|
||||||
|
|
||||||
|
local status, reply = socket:receive()
|
||||||
|
socket:close()
|
||||||
|
|
||||||
|
if not status then
|
||||||
|
stdnse.debug1(string.format("No response (%s)", reply))
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
nmap.set_port_state(host, port, "open")
|
||||||
|
|
||||||
|
local info = ipmi.parse_open_session_reply(reply)
|
||||||
|
if info["session_payload_type"] == ipmi.PAYLOADS["RMCPPLUSOPEN_REP"] then
|
||||||
|
vuln_table.state = vulns.STATE.VULN
|
||||||
|
end
|
||||||
|
|
||||||
|
return report:make_output(vuln_table)
|
||||||
|
|
||||||
|
end
|
||||||
170
scripts/ipmi-version.nse
Normal file
170
scripts/ipmi-version.nse
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
local ipmi = require "ipmi"
|
||||||
|
local nmap = require "nmap"
|
||||||
|
local shortport = require "shortport"
|
||||||
|
local stdnse = require "stdnse"
|
||||||
|
local string = require "string"
|
||||||
|
local table = require "table"
|
||||||
|
|
||||||
|
description = [[
|
||||||
|
Performs IPMI Information Discovery through Channel Auth probes.
|
||||||
|
]]
|
||||||
|
|
||||||
|
---
|
||||||
|
-- @usage
|
||||||
|
-- nmap -sU --script ipmi-version -p 623 <host>
|
||||||
|
--
|
||||||
|
-- @output
|
||||||
|
-- PORT STATE SERVICE REASON
|
||||||
|
-- 623/udp open|filtered unknown
|
||||||
|
-- | ipmi-version:
|
||||||
|
-- | Version: IPMI-2.0
|
||||||
|
-- | UserAuth: password, md5, md2
|
||||||
|
-- | PassAuth: null_user
|
||||||
|
-- |_ Level: 1.2,2.0
|
||||||
|
--
|
||||||
|
-- @xmloutput
|
||||||
|
-- <table>
|
||||||
|
-- <table key="Version">
|
||||||
|
-- <elem>IPMI-2.0</elem>
|
||||||
|
-- </table>
|
||||||
|
--
|
||||||
|
-- <table key="UserAuth">
|
||||||
|
-- <elem>password</elem>
|
||||||
|
-- <elem>md5</elem>
|
||||||
|
-- <elem>md2</elem>
|
||||||
|
-- </table>
|
||||||
|
--
|
||||||
|
-- <table key="PassAuth">
|
||||||
|
-- <elem>kg_default</elem>
|
||||||
|
-- <elem>null_user</elem>
|
||||||
|
-- </table>
|
||||||
|
--
|
||||||
|
-- <table key="Level">
|
||||||
|
-- <elem>1.2</elem>
|
||||||
|
-- <elem>2.0</elem>
|
||||||
|
-- </table>
|
||||||
|
-- </table>
|
||||||
|
--
|
||||||
|
|
||||||
|
author = "Claudiu Perta <claudiu.perta@gmail.com>"
|
||||||
|
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
|
||||||
|
categories = {"discovery", "safe"}
|
||||||
|
|
||||||
|
portrule = shortport.version_port_or_service(623, "asf-rmcp", "udp", {"open", "open|filtered"})
|
||||||
|
|
||||||
|
local comma_separated = {
|
||||||
|
__tostring = function(t) return table.concat(t, ", ") end
|
||||||
|
}
|
||||||
|
|
||||||
|
action = function(host, port)
|
||||||
|
|
||||||
|
local request = ipmi.channel_auth_request()
|
||||||
|
local socket = nmap.new_socket()
|
||||||
|
|
||||||
|
socket:set_timeout(
|
||||||
|
((host.times and host.times.timeout) or 8) * 1000)
|
||||||
|
socket:connect(host, port, "udp")
|
||||||
|
|
||||||
|
-- Send 3 probes
|
||||||
|
local tries = 3
|
||||||
|
repeat
|
||||||
|
socket:send(request)
|
||||||
|
tries = tries - 1
|
||||||
|
until tries == 0
|
||||||
|
|
||||||
|
local status, reply = socket:receive()
|
||||||
|
socket:close()
|
||||||
|
|
||||||
|
if not status then
|
||||||
|
stdnse.debug1(string.format("No response (%s)", reply))
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
nmap.set_port_state(host, port, "open")
|
||||||
|
|
||||||
|
-- Invalid reply
|
||||||
|
local info = ipmi.parse_channel_auth_reply(reply)
|
||||||
|
if info["ipmi_command"] ~= 56 then
|
||||||
|
return "IPMI - Invalid response"
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Valid reply
|
||||||
|
local Version = {}
|
||||||
|
if info["ipmi_compat_20"] then
|
||||||
|
table.insert(Version, "IPMI-2.0")
|
||||||
|
else
|
||||||
|
table.insert(Version, "IPMI-1.5")
|
||||||
|
end
|
||||||
|
|
||||||
|
local UserAuth = {}
|
||||||
|
setmetatable(UserAuth, comma_separated)
|
||||||
|
|
||||||
|
if info["ipmi_compat_oem_auth"] then
|
||||||
|
table.insert(UserAuth, "oem_auth")
|
||||||
|
end
|
||||||
|
|
||||||
|
if info["ipmi_compat_password"] then
|
||||||
|
table.insert(UserAuth, "password")
|
||||||
|
end
|
||||||
|
|
||||||
|
if info["ipmi_compat_md5"] then
|
||||||
|
table.insert(UserAuth, "md5")
|
||||||
|
end
|
||||||
|
|
||||||
|
if info["ipmi_compat_md2"] then
|
||||||
|
table.insert(UserAuth, "md2")
|
||||||
|
end
|
||||||
|
|
||||||
|
if info["ipmi_compat_none"] then
|
||||||
|
table.insert(UserAuth, "null")
|
||||||
|
end
|
||||||
|
|
||||||
|
local PassAuth = {}
|
||||||
|
setmetatable(PassAuth, comma_separated)
|
||||||
|
|
||||||
|
if info["ipmi_compat_20"] and info["ipmi_user_kg"] then
|
||||||
|
table.insert(PassAuth, "kg_default")
|
||||||
|
end
|
||||||
|
|
||||||
|
if not info["ipmi_user_disable_message_auth"] then
|
||||||
|
table.insert(PassAuth, "auth_msg")
|
||||||
|
end
|
||||||
|
|
||||||
|
if not info["ipmi_user_disable_user_auth"] then
|
||||||
|
table.insert(PassAuth, "auth_user")
|
||||||
|
end
|
||||||
|
|
||||||
|
if info["ipmi_user_non_null"] then
|
||||||
|
table.insert(PassAuth, "non_null_user")
|
||||||
|
end
|
||||||
|
|
||||||
|
if info["ipmi_user_null"] then
|
||||||
|
table.insert(PassAuth, "null_user")
|
||||||
|
end
|
||||||
|
|
||||||
|
if info["ipmi_user_anonymous"] then
|
||||||
|
table.insert(PassAuth, "anonymous_user")
|
||||||
|
end
|
||||||
|
|
||||||
|
local ConnInfo = {}
|
||||||
|
setmetatable(ConnInfo, comma_separated)
|
||||||
|
|
||||||
|
if info["ipmi_conn_15"] then
|
||||||
|
table.insert(ConnInfo, "1.5")
|
||||||
|
end
|
||||||
|
|
||||||
|
if info["ipmi_conn_20"] then
|
||||||
|
table.insert(ConnInfo, "2.0")
|
||||||
|
end
|
||||||
|
|
||||||
|
local output = stdnse.output_table()
|
||||||
|
output["Version"] = Version
|
||||||
|
output["UserAuth"] = UserAuth
|
||||||
|
output["PassAuth"] = PassAuth
|
||||||
|
output["Level"] = ConnInfo
|
||||||
|
if info["ipmi_oem_id"] ~= 0 then
|
||||||
|
output["OEMID"] = info["ipmi_oem_id"]
|
||||||
|
end
|
||||||
|
|
||||||
|
return output
|
||||||
|
end
|
||||||
@@ -278,6 +278,9 @@ Entry { filename = "ip-geolocation-ipinfodb.nse", categories = { "discovery", "e
|
|||||||
Entry { filename = "ip-geolocation-maxmind.nse", categories = { "discovery", "external", "safe", } }
|
Entry { filename = "ip-geolocation-maxmind.nse", categories = { "discovery", "external", "safe", } }
|
||||||
Entry { filename = "ip-https-discover.nse", categories = { "default", "discovery", "safe", } }
|
Entry { filename = "ip-https-discover.nse", categories = { "default", "discovery", "safe", } }
|
||||||
Entry { filename = "ipidseq.nse", categories = { "discovery", "safe", } }
|
Entry { filename = "ipidseq.nse", categories = { "discovery", "safe", } }
|
||||||
|
Entry { filename = "ipmi-brute.nse", categories = { "brute", "intrusive", } }
|
||||||
|
Entry { filename = "ipmi-cipher-zero.nse", categories = { "safe", "vuln", } }
|
||||||
|
Entry { filename = "ipmi-version.nse", categories = { "discovery", "safe", } }
|
||||||
Entry { filename = "ipv6-multicast-mld-list.nse", categories = { "broadcast", "discovery", } }
|
Entry { filename = "ipv6-multicast-mld-list.nse", categories = { "broadcast", "discovery", } }
|
||||||
Entry { filename = "ipv6-node-info.nse", categories = { "default", "discovery", "safe", } }
|
Entry { filename = "ipv6-node-info.nse", categories = { "default", "discovery", "safe", } }
|
||||||
Entry { filename = "ipv6-ra-flood.nse", categories = { "dos", "intrusive", } }
|
Entry { filename = "ipv6-ra-flood.nse", categories = { "dos", "intrusive", } }
|
||||||
|
|||||||
Reference in New Issue
Block a user