1
0
mirror of https://github.com/nmap/nmap.git synced 2025-12-06 04:31:29 +00:00

SMB2 dialect refactoring. Fixes #2203, closes #2208

This commit is contained in:
nnposter
2021-01-18 21:21:43 +00:00
parent 4564749ccd
commit 58617a79f7
8 changed files with 166 additions and 124 deletions

View File

@@ -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 Version 0.93 of the NPSL so that users and distributors may choose
either version of the license. 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 o [NSE] Script smb2-vuln-uptime no longer reports false positives when
the target does not provide its boot time. [nnposter] the target does not provide its boot time. [nnposter]

View File

@@ -1089,7 +1089,6 @@ end
-- @return Table of supported dialects or error message -- @return Table of supported dialects or error message
--- ---
function list_dialects(host, overrides) function list_dialects(host, overrides)
local smb2_dialects = {0x0202, 0x0210, 0x0300, 0x0302, 0x0311}
local supported_dialects = {} local supported_dialects = {}
local status, smb1_dialect local status, smb1_dialect
local smbstate local smbstate
@@ -1107,28 +1106,44 @@ function list_dialects(host, overrides)
if status then --Add SMBv1 as a dialect if status then --Add SMBv1 as a dialect
table.insert(supported_dialects, smb1_dialect) table.insert(supported_dialects, smb1_dialect)
end end
stop(smbstate) stop(smbstate) -- Finish SMBv1 and close connection
status = false -- Finish SMBv1 and close connection
-- Check SMB2 and SMB3 dialects status, smbstate = start(host)
for i, dialect in pairs(smb2_dialects) do if(status == false) then
local dialect_human = stdnse.tohex(dialect, {separator = ".", group = 2}) 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 -- we need a clean connection for each negotiate request
status, smbstate = start(host) status, smbstate = start(host)
if(status == false) then if(status == false) then
return false, smbstate return false, smbstate
end 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} overrides['Dialects'] = {dialect}
status, dialect = smb2.negotiate_v2(smbstate, overrides) status = smb2.negotiate_v2(smbstate, overrides)
if status then
stdnse.debug2("SMB2: Dialect '%s' is supported", dialect_human)
table.insert(supported_dialects, dialect_human)
end
--clean smb connection --clean smb connection
stop(smbstate) stop(smbstate)
status = false if status then
stdnse.debug2("SMB2: Dialect '%s' is supported", dialect_name)
table.insert(supported_dialects, dialect_name)
end
end end
table.insert(supported_dialects, smb2.dialect_name(max_dialect))
return true, supported_dialects return true, supported_dialects
end end

View File

@@ -1,8 +1,8 @@
--- ---
-- Implements the Server Message Block (SMB) protocol version 2 and 3. -- 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, -- The implementation extends smb.lua to support SMB dialects 2.0.2, 2.1, 3.0,
-- 3.02 and 3.11. This is a work in progress and not all commands are -- 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 -- implemented yet. Features/functionality will be added as the scripts
-- get updated. I tried to be consistent with the current implementation of -- 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. -- 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_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. -- Creates a SMB2 SYNC header packet.
-- --
@@ -199,42 +227,41 @@ end
--- ---
-- Sends SMB2_COM_NEGOTIATE command for a SMB2/SMB3 connection. -- 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 smb The associated SMB connection object.
-- @param overrides Overrides table. -- @param overrides Overrides table.
-- @return (status, dialect) If status is true, the negotiated dialect is returned as the second value. -- @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. -- Otherwise if status is false, the error message is returned.
-- @see dialects
-- @see dialect_name
function negotiate_v2(smb, overrides) function negotiate_v2(smb, overrides)
overrides = overrides or {}
local StructureSize = 36 -- Must be set to 36. local StructureSize = 36 -- Must be set to 36.
local DialectCount local Dialects = overrides['Dialects'] or smb2_dialects
if overrides['Dialects'] then local DialectCount = #Dialects
DialectCount = #overrides['Dialects']
else
DialectCount = 1
end
-- The client MUST set SecurityMode bit to 0x01 if the SMB2_NEGOTIATE_SIGNING_REQUIRED bit is not set, -- 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. -- and MUST NOT set this bit if the SMB2_NEGOTIATE_SIGNING_REQUIRED bit is set.
-- The server MUST ignore this bit. -- The server MUST ignore this bit.
local SecurityMode = overrides["SecurityMode"] or smb2_values['SMB2_NEGOTIATE_SIGNING_ENABLED'] 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 Capabilities = overrides["Capabilities"] or 0 -- SMB 3.x dialect requires capabilities to be constructed
local GUID = overrides["GUID"] or "1234567890123456" 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 total_data = 0 -- Data counter
local padding_data = "" -- Padding string to align contexts local padding_data = "" -- Padding string to align contexts
local context_data -- Holds Context data 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 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) 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("<I2 I2 I2 I2 I4 c16", local data = string.pack("<I2 I2 I2 I2 I4 c16",
StructureSize, -- 2 bytes: StructureSize StructureSize, -- 2 bytes: StructureSize
DialectCount, -- 2 bytes: DialectCount DialectCount, -- 2 bytes: DialectCount
@@ -245,11 +272,7 @@ function negotiate_v2(smb, overrides)
) )
-- The next block gets interpreted in different ways depending on the dialect -- The next block gets interpreted in different ways depending on the dialect
if tableaux.contains(overrides['Dialects'], 0x0311) then -- If we are dealing with 3.1.1 we need to set the following fields:
is_0311 = true
end
-- If we are dealing with 3.11 we need to set the following fields:
-- NegotiateContextOffset, NegotiateContextCount, and Reserved2 -- NegotiateContextOffset, NegotiateContextCount, and Reserved2
if is_0311 then if is_0311 then
total_data = #header + #data + (DialectCount*2) total_data = #header + #data + (DialectCount*2)
@@ -260,17 +283,13 @@ function negotiate_v2(smb, overrides)
0x2, -- NegotiateContextCount (2 bytes) 0x2, -- NegotiateContextCount (2 bytes)
0x0 -- Reserved2 (2 bytes) 0x0 -- Reserved2 (2 bytes)
) )
else -- If it's not 3.11, the bytes are the ClientStartTime (8 bytes) else -- If it's not 3.1.1, the bytes are the ClientStartTime (8 bytes)
data = data .. string.pack("<I8", ClientStartTime) -- If it is not 3.11, we set it to 0 data = data .. string.pack("<I8", ClientStartTime)
end -- if is_0311 end -- if is_0311
-- Now we build the Dialect list, 16 bit integers -- Now we build the Dialect list, 16 bit integers
if(overrides['Dialects'] == nil) then -- If no custom dialect is defined, used the default 2.10 for _, v in ipairs(Dialects) do
data = data .. string.pack("<I2", 0x0210) data = data .. string.pack("<I2", v)
else -- Dialects are set in overrides table
for _, v in ipairs(overrides['Dialects']) do
data = data .. string.pack("<I2", v)
end
end end
-- If 3.11, we now need to add some padding between the dialects and the NegotiateContextList -- If 3.11, we now need to add some padding between the dialects and the NegotiateContextList

View File

@@ -7,11 +7,11 @@ Attempts to list the supported protocols and dialects of a SMB server.
The script attempts to initiate a connection using the dialects: The script attempts to initiate a connection using the dialects:
* NT LM 0.12 (SMBv1) * NT LM 0.12 (SMBv1)
* 2.02 (SMBv2) * 2.0.2 (SMBv2)
* 2.10 (SMBv2) * 2.1 (SMBv2)
* 3.00 (SMBv3) * 3.0 (SMBv3)
* 3.02 (SMBv3) * 3.0.2 (SMBv3)
* 3.11 (SMBv3) * 3.1.1 (SMBv3)
Additionally if SMBv1 is found enabled, it will mark it as insecure. This Additionally if SMBv1 is found enabled, it will mark it as insecure. This
script is the successor to the (removed) smbv2-enabled script. script is the successor to the (removed) smbv2-enabled script.
@@ -25,20 +25,20 @@ script is the successor to the (removed) smbv2-enabled script.
-- | smb-protocols: -- | smb-protocols:
-- | dialects: -- | dialects:
-- | NT LM 0.12 (SMBv1) [dangerous, but default] -- | NT LM 0.12 (SMBv1) [dangerous, but default]
-- | 2.02 -- | 2.0.2
-- | 2.10 -- | 2.1
-- | 3.00 -- | 3.0
-- | 3.02 -- | 3.0.2
-- |_ 3.11 -- |_ 3.1.1
-- --
-- @xmloutput -- @xmloutput
-- <table key="dialects"> -- <table key="dialects">
-- <elem>NT LM 0.12 (SMBv1) [dangerous, but default]</elem> -- <elem>NT LM 0.12 (SMBv1) [dangerous, but default]</elem>
-- <elem>2.02</elem> -- <elem>2.0.2</elem>
-- <elem>2.10</elem> -- <elem>2.1</elem>
-- <elem>3.00</elem> -- <elem>3.0</elem>
-- <elem>3.02</elem> -- <elem>3.0.2</elem>
-- <elem>3.11</elem> -- <elem>3.1.1</elem>
-- </table> -- </table>
--- ---

View File

@@ -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 The script sends a SMB2_COM_NEGOTIATE command and parses the response
using the SMB dialects: using the SMB dialects:
* 2.02 * 2.0.2
* 2.10 * 2.1
* 3.00 * 3.0
* 3.02 * 3.0.2
* 3.11 * 3.1.1
References: References:
* https://msdn.microsoft.com/en-us/library/cc246561.aspx * https://msdn.microsoft.com/en-us/library/cc246561.aspx
@@ -26,18 +26,18 @@ References:
-- --
-- @output -- @output
-- | smb2-capabilities: -- | smb2-capabilities:
-- | 2.02: -- | 2.0.2:
-- | Distributed File System -- | Distributed File System
-- | 2.10: -- | 2.1:
-- | Distributed File System -- | Distributed File System
-- | Leasing -- | Leasing
-- | Multi-credit operations -- | Multi-credit operations
-- --
-- @xmloutput -- @xmloutput
-- <table key="2.02"> -- <table key="2.0.2">
-- <elem>Distributed File System</elem> -- <elem>Distributed File System</elem>
-- </table> -- </table>
-- <table key="2.10"> -- <table key="2.1">
-- <elem>Distributed File System</elem> -- <elem>Distributed File System</elem>
-- <elem>Leasing</elem> -- <elem>Leasing</elem>
-- <elem>Multi-credit operations</elem> -- <elem>Multi-credit operations</elem>
@@ -57,9 +57,20 @@ action = function(host,port)
local output = stdnse.output_table() local output = stdnse.output_table()
overrides = {} 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 -- we need a clean connection for each negotiate request
status, smbstate = smb.start(host) status, smbstate = smb.start(host)
if(status == false) then if(status == false) then
@@ -99,10 +110,12 @@ action = function(host,port)
if #capabilities<1 then if #capabilities<1 then
table.insert(capabilities, "All capabilities are disabled") table.insert(capabilities, "All capabilities are disabled")
end end
output[stdnse.tohex(dialect, {separator = ".", group = 2})] = capabilities output[smb2.dialect_name(dialect)] = capabilities
end end
smb.stop(smbstate) smb.stop(smbstate)
status = false if dialect == max_dialect then
break
end
end end
if #output>0 then if #output>0 then

View File

@@ -22,11 +22,11 @@ References:
-- --
-- @output -- @output
-- | smb2-security-mode: -- | smb2-security-mode:
-- | 3.11: -- | 3.1.1:
-- |_ Message signing enabled but not required -- |_ Message signing enabled but not required
-- --
-- @xmloutput -- @xmloutput
-- <table key="3.11"> -- <table key="3.1.1">
-- <elem>Message signing enabled but not required</elem> -- <elem>Message signing enabled but not required</elem>
-- </table> -- </table>
--- ---
@@ -40,53 +40,43 @@ hostrule = function(host)
end end
action = function(host,port) action = function(host,port)
local status, smbstate, overrides local status, smbstate
local output = stdnse.output_table() local output = stdnse.output_table()
overrides = overrides or {}
local smb2_dialects = {0x0202, 0x0210, 0x0300, 0x0302, 0x0311} status, smbstate = smb.start(host)
if(status == false) then
for i, dialect in pairs(smb2_dialects) do return false, smbstate
-- 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
end 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 if #output>0 then
return output return output

View File

@@ -31,11 +31,10 @@ hostrule = function(host)
end end
action = function(host,port) action = function(host,port)
local smbstate, status, overrides local smbstate, status
local output = stdnse.output_table() local output = stdnse.output_table()
overrides = {}
status, smbstate = smb.start(host) status, smbstate = smb.start(host)
status = smb2.negotiate_v2(smbstate, overrides) status = smb2.negotiate_v2(smbstate)
if status then if status then
datetime.record_skew(host, smbstate.time, os.time()) datetime.record_skew(host, smbstate.time, os.time())

View File

@@ -107,13 +107,11 @@ This security update resolves a privately reported vulnerability in Microsoft
} }
local function check_vulns(host, port) local function check_vulns(host, port)
local smbstate, status, overrides local smbstate, status
local vulns_detected = {} local vulns_detected = {}
overrides = {}
overrides['Dialects'] = {0x0202}
status, smbstate = smb.start(host) status, smbstate = smb.start(host)
status = smb2.negotiate_v2(smbstate, overrides) status = smb2.negotiate_v2(smbstate)
if not status then if not status then
stdnse.debug2("Negotiation failed") stdnse.debug2("Negotiation failed")