diff --git a/CHANGELOG b/CHANGELOG index 716f1e026..c817355c1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -57,6 +57,14 @@ o [GH#2199] Updated Nmap's NPSL license to rewrite a poorly-worded Version 0.93 of the NPSL so that users and distributors may choose either version of the license. +o [NSE][GH#2208][GH#2203] SMB2 dialect handling has been redesigned. Visible + changes include: + * Notable improvement in speed of script smb-protocols and others + * Some SMB scripts are no longer using a hardcoded dialect, improving + target interoperability + * Dialect names are aligned with Microsoft, such as 3.0.2, instead of 3.02 + [nnposter] + o [NSE] Script smb2-vuln-uptime no longer reports false positives when the target does not provide its boot time. [nnposter] diff --git a/nselib/smb.lua b/nselib/smb.lua index 3931777cd..9657767ef 100644 --- a/nselib/smb.lua +++ b/nselib/smb.lua @@ -1089,7 +1089,6 @@ end -- @return Table of supported dialects or error message --- function list_dialects(host, overrides) - local smb2_dialects = {0x0202, 0x0210, 0x0300, 0x0302, 0x0311} local supported_dialects = {} local status, smb1_dialect local smbstate @@ -1107,28 +1106,44 @@ function list_dialects(host, overrides) if status then --Add SMBv1 as a dialect table.insert(supported_dialects, smb1_dialect) end - stop(smbstate) - status = false -- Finish SMBv1 and close connection + stop(smbstate) -- Finish SMBv1 and close connection - -- Check SMB2 and SMB3 dialects - for i, dialect in pairs(smb2_dialects) do - local dialect_human = stdnse.tohex(dialect, {separator = ".", group = 2}) + status, smbstate = start(host) + if(status == false) then + return false, smbstate + end + stdnse.debug2("Checking if SMB 2+ is supported in general") + overrides['Dialects'] = nil + local max_dialect + status, max_dialect = smb2.negotiate_v2(smbstate, overrides) + stop(smbstate) + if not status then -- None of SMB2 dialects accepted by the target + return true, supported_dialects + end + stdnse.debug2("SMB2: Dialect '%s' is the highest supported", smb2.dialect_name(max_dialect)) + + -- Check individual SMB2 and SMB3 dialects + for i, dialect in pairs(smb2.dialects()) do + if dialect == max_dialect then + break + end + local dialect_name = smb2.dialect_name(dialect) -- we need a clean connection for each negotiate request status, smbstate = start(host) if(status == false) then return false, smbstate end - stdnse.debug2("Checking if dialect '%s' is supported", dialect_human) + stdnse.debug2("SMB2: Checking if dialect '%s' is supported", dialect_name) overrides['Dialects'] = {dialect} - status, dialect = smb2.negotiate_v2(smbstate, overrides) - if status then - stdnse.debug2("SMB2: Dialect '%s' is supported", dialect_human) - table.insert(supported_dialects, dialect_human) - end + status = smb2.negotiate_v2(smbstate, overrides) --clean smb connection stop(smbstate) - status = false + if status then + stdnse.debug2("SMB2: Dialect '%s' is supported", dialect_name) + table.insert(supported_dialects, dialect_name) + end end + table.insert(supported_dialects, smb2.dialect_name(max_dialect)) return true, supported_dialects end diff --git a/nselib/smb2.lua b/nselib/smb2.lua index f13131caa..4b60d0430 100644 --- a/nselib/smb2.lua +++ b/nselib/smb2.lua @@ -1,8 +1,8 @@ --- -- Implements the Server Message Block (SMB) protocol version 2 and 3. -- --- The implementation extends smb.lua to support SMB dialects 2.02, 2.10, 3.0, --- 3.02 and 3.11. This is a work in progress and not all commands are +-- The implementation extends smb.lua to support SMB dialects 2.0.2, 2.1, 3.0, +-- 3.0.2 and 3.1.1. This is a work in progress and not all commands are -- implemented yet. Features/functionality will be added as the scripts -- get updated. I tried to be consistent with the current implementation of -- smb.lua but some fields may have changed name or don't exist anymore. @@ -62,6 +62,34 @@ local smb2_values = { } local smb2_values_codes = tableaux.invert(smb2_values) +local smb2_dialects = {0x0202, 0x0210, 0x0300, 0x0302, 0x0311} +local smb2_dialect_names = {} +for _, d in ipairs(smb2_dialects) do + -- convert 0x0abc to "a.b.c" + local name = stdnse.tohex(d, {separator = ".", group = 1}) + -- trim trailing ".0" at sub-minor level + smb2_dialect_names[d] = name:find(".0", 4, true) and name:sub(1, 3) or name +end + +--- +-- Returns the list of supported SMB 2 dialects +-- https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/fac3655a-7eb5-4337-b0ab-244bbcd014e8 +-- @return list of 16-bit numerical revision codes (0x202, 0x210, ...) +--- +function dialects () + return tableaux.tcopy(smb2_dialects) +end + +--- +-- Converts a supported SMB 2 dialect code to dialect name +-- https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/fac3655a-7eb5-4337-b0ab-244bbcd014e8 +-- @param dialect SMB 2 dialect revision code +-- @return string representing the dialect (or nil). Example: 0x202 -> "2.0.2" +--- +function dialect_name (dialect) + return smb2_dialect_names[dialect] +end + --- -- Creates a SMB2 SYNC header packet. -- @@ -199,42 +227,41 @@ end --- -- Sends SMB2_COM_NEGOTIATE command for a SMB2/SMB3 connection. --- This function works for dialects 2.02, 2.10, 3.0, 3.02 and 3.11. +-- All supported dialects are offered. Use table overrides['Dialects'] +-- to exclude some dialects or to force a specific dialect. +-- Use function smb2.dialects to obtain the list of supported dialects. +-- Use function smb2.dialect_name to check whether a given dialect is supported. -- --- Packet structure: https://msdn.microsoft.com/en-us/library/cc246543.aspx +-- Packet structure: +-- https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/e14db7ff-763a-4263-8b10-0c3944f52fc5 -- -- @param smb The associated SMB connection object. -- @param overrides Overrides table. -- @return (status, dialect) If status is true, the negotiated dialect is returned as the second value. -- Otherwise if status is false, the error message is returned. +-- @see dialects +-- @see dialect_name function negotiate_v2(smb, overrides) + overrides = overrides or {} local StructureSize = 36 -- Must be set to 36. - local DialectCount - if overrides['Dialects'] then - DialectCount = #overrides['Dialects'] - else - DialectCount = 1 - end + local Dialects = overrides['Dialects'] or smb2_dialects + local DialectCount = #Dialects -- The client MUST set SecurityMode bit to 0x01 if the SMB2_NEGOTIATE_SIGNING_REQUIRED bit is not set, -- and MUST NOT set this bit if the SMB2_NEGOTIATE_SIGNING_REQUIRED bit is set. -- The server MUST ignore this bit. local SecurityMode = overrides["SecurityMode"] or smb2_values['SMB2_NEGOTIATE_SIGNING_ENABLED'] local Capabilities = overrides["Capabilities"] or 0 -- SMB 3.x dialect requires capabilities to be constructed local GUID = overrides["GUID"] or "1234567890123456" - local ClientStartTime = overrides["ClientStartTime"] or 0 -- ClientStartTime only used in dialects > 3.11 + local ClientStartTime = overrides["ClientStartTime"] or 0 -- ClientStartTime only used in dialects < 3.1.1 local total_data = 0 -- Data counter local padding_data = "" -- Padding string to align contexts local context_data -- Holds Context data - local is_0311 = false -- Flag for SMB 3.11 + local is_0311 = tableaux.contains(Dialects, 0x0311) -- Flag for SMB 3.1.1 local status, err - if not( overrides['Dialects'] ) then -- Set 2.02 as default dialect if user didn't select one - overrides['Dialects'] = {0x0202} - end - local header = smb2_encode_header_sync(smb, command_codes['SMB2_COM_NEGOTIATE'], overrides) - -- We construct the first block that works for dialects 2.02 up to 3.11. + -- We construct the first block that works for dialects 2.0.2 up to 3.1.1. local data = string.pack(" -- NT LM 0.12 (SMBv1) [dangerous, but default] --- 2.02 --- 2.10 --- 3.00 --- 3.02 --- 3.11 +-- 2.0.2 +-- 2.1 +-- 3.0 +-- 3.0.2 +-- 3.1.1 -- --- diff --git a/scripts/smb2-capabilities.nse b/scripts/smb2-capabilities.nse index c1388b8e2..172882324 100644 --- a/scripts/smb2-capabilities.nse +++ b/scripts/smb2-capabilities.nse @@ -10,11 +10,11 @@ Attempts to list the supported capabilities in a SMBv2 server for each The script sends a SMB2_COM_NEGOTIATE command and parses the response using the SMB dialects: -* 2.02 -* 2.10 -* 3.00 -* 3.02 -* 3.11 +* 2.0.2 +* 2.1 +* 3.0 +* 3.0.2 +* 3.1.1 References: * https://msdn.microsoft.com/en-us/library/cc246561.aspx @@ -26,18 +26,18 @@ References: -- -- @output -- | smb2-capabilities: --- | 2.02: +-- | 2.0.2: -- | Distributed File System --- | 2.10: +-- | 2.1: -- | Distributed File System -- | Leasing -- | Multi-credit operations -- -- @xmloutput --- +--
-- Distributed File System --
--- +--
-- Distributed File System -- Leasing -- Multi-credit operations @@ -57,9 +57,20 @@ action = function(host,port) local output = stdnse.output_table() overrides = {} - local smb2_dialects = {0x0202, 0x0210, 0x0300, 0x0302, 0x0311} + -- Checking if SMB 2+ is supported in general + status, smbstate = smb.start(host) + if(status == false) then + return false, smbstate + end + local max_dialect + status, max_dialect = smb2.negotiate_v2(smbstate) + smb.stop(smbstate) + if not status then -- None of SMB2 dialects accepted by the target + return false, "SMB 2+ not supported" + end + stdnse.debug2("SMB2: Dialect '%s' is the highest supported", smb2.dialect_name(max_dialect)) - for i, dialect in pairs(smb2_dialects) do + for i, dialect in pairs(smb2.dialects()) do -- we need a clean connection for each negotiate request status, smbstate = smb.start(host) if(status == false) then @@ -99,10 +110,12 @@ action = function(host,port) if #capabilities<1 then table.insert(capabilities, "All capabilities are disabled") end - output[stdnse.tohex(dialect, {separator = ".", group = 2})] = capabilities + output[smb2.dialect_name(dialect)] = capabilities end smb.stop(smbstate) - status = false + if dialect == max_dialect then + break + end end if #output>0 then diff --git a/scripts/smb2-security-mode.nse b/scripts/smb2-security-mode.nse index 564bb2d24..36b2368d6 100644 --- a/scripts/smb2-security-mode.nse +++ b/scripts/smb2-security-mode.nse @@ -22,11 +22,11 @@ References: -- -- @output -- | smb2-security-mode: --- | 3.11: +-- | 3.1.1: -- |_ Message signing enabled but not required -- -- @xmloutput ---
+--
-- Message signing enabled but not required --
--- @@ -40,53 +40,43 @@ hostrule = function(host) end action = function(host,port) - local status, smbstate, overrides + local status, smbstate local output = stdnse.output_table() - overrides = overrides or {} - local smb2_dialects = {0x0202, 0x0210, 0x0300, 0x0302, 0x0311} - - for i, dialect in pairs(smb2_dialects) do - -- we need a clean connection for each negotiate request - status, smbstate = smb.start(host) - if(status == false) then - return false, smbstate - end - overrides['Dialects'] = {dialect} - status, dialect = smb2.negotiate_v2(smbstate, overrides) - if status then - local message_signing = {} - - -- Signing configuration. SMBv2 servers support two flags: - -- * Message signing enabled - -- * Message signing required - local signing_enabled, signing_required - if smbstate['security_mode'] & 0x01 == 0x01 then - signing_enabled = true - end - if smbstate['security_mode'] & 0x02 == 0x02 then - signing_required = true - end - - if signing_enabled and signing_required then - table.insert(message_signing, "Message signing enabled and required") - elseif signing_enabled and not(signing_required) then - table.insert(message_signing, "Message signing enabled but not required") - elseif not(signing_enabled) and not(signing_required) then - table.insert(message_signing, "Message signing is disabled and not required!") - elseif not(signing_enabled) and signing_required then - table.insert(message_signing, "Message signing is disabled!") - end - output[stdnse.tohex(dialect, {separator = ".", group = 2})] = message_signing - -- We exit after first accepted dialect, - -- SMB signing configuration appears to be global so - -- there is no point of trying other dialects. - break - end - - smb.stop(smbstate) - status = false + status, smbstate = smb.start(host) + if(status == false) then + return false, smbstate end + -- SMB signing configuration appears to be global so + -- there is no point of trying different dialects. + status, dialect = smb2.negotiate_v2(smbstate) + if status then + local message_signing = {} + -- Signing configuration. SMBv2 servers support two flags: + -- * Message signing enabled + -- * Message signing required + local signing_enabled, signing_required + if smbstate['security_mode'] & 0x01 == 0x01 then + signing_enabled = true + end + if smbstate['security_mode'] & 0x02 == 0x02 then + signing_required = true + end + if signing_enabled and signing_required then + table.insert(message_signing, "Message signing enabled and required") + elseif signing_enabled and not(signing_required) then + table.insert(message_signing, "Message signing enabled but not required") + elseif not(signing_enabled) and not(signing_required) then + table.insert(message_signing, "Message signing is disabled and not required!") + elseif not(signing_enabled) and signing_required then + table.insert(message_signing, "Message signing is disabled!") + end + output[smb2.dialect_name(dialect)] = message_signing + -- We exit after first accepted dialect, + end + + smb.stop(smbstate) + status = false if #output>0 then return output diff --git a/scripts/smb2-time.nse b/scripts/smb2-time.nse index 4bbc67ed5..4b1940027 100644 --- a/scripts/smb2-time.nse +++ b/scripts/smb2-time.nse @@ -31,11 +31,10 @@ hostrule = function(host) end action = function(host,port) - local smbstate, status, overrides + local smbstate, status local output = stdnse.output_table() - overrides = {} status, smbstate = smb.start(host) - status = smb2.negotiate_v2(smbstate, overrides) + status = smb2.negotiate_v2(smbstate) if status then datetime.record_skew(host, smbstate.time, os.time()) diff --git a/scripts/smb2-vuln-uptime.nse b/scripts/smb2-vuln-uptime.nse index 1840ef121..6e2bd617a 100644 --- a/scripts/smb2-vuln-uptime.nse +++ b/scripts/smb2-vuln-uptime.nse @@ -107,13 +107,11 @@ This security update resolves a privately reported vulnerability in Microsoft } local function check_vulns(host, port) - local smbstate, status, overrides + local smbstate, status local vulns_detected = {} - overrides = {} - overrides['Dialects'] = {0x0202} status, smbstate = smb.start(host) - status = smb2.negotiate_v2(smbstate, overrides) + status = smb2.negotiate_v2(smbstate) if not status then stdnse.debug2("Negotiation failed")