mirror of
https://github.com/nmap/nmap.git
synced 2025-12-21 23:19:03 +00:00
Adds new NSE library for SMB2/SMB3 and scripts smb-protocols,smb2-time,smb2-capabilities,smb2-security-mode,smb2-vuln-uptime. Closes #943
This commit is contained in:
11
CHANGELOG
11
CHANGELOG
@@ -1,5 +1,16 @@
|
||||
# Nmap Changelog ($Id$); -*-text-*-
|
||||
|
||||
o [NSE][GH#943] Added new SMB2/3 library and scripts:
|
||||
+ smb-protocols discovers if a server supports dialects
|
||||
NT LM 0.12 (SMBv1), 2.02, 2.10, 3.00, 3.02 and 3.11.
|
||||
+ smb2-time determines the current date and boot date of SMB2 servers.
|
||||
+ smb2-capabilities lists the supported capabilities of SMB2/SMB3 servers.
|
||||
+ smb2-security-mode determines the message signing configuration of
|
||||
SMB2/SMB3 servers.
|
||||
+ smb2-vuln-uptime attempts to discover missing critical
|
||||
patches in Microsoft Windows systems based on the SMB2
|
||||
server uptime. [Paulino Calderon]
|
||||
|
||||
o [NSE][GH#950] Added wildcard detection to dns-brute. Only hostnames that
|
||||
resolve to unique addresses will be listed. [Aaron Heesakkers]
|
||||
|
||||
|
||||
151
nselib/smb.lua
151
nselib/smb.lua
@@ -136,6 +136,7 @@ local stdnse = require "stdnse"
|
||||
local string = require "string"
|
||||
local table = require "table"
|
||||
local unicode = require "unicode"
|
||||
local smb2 = require "smb2"
|
||||
_ENV = stdnse.module("smb", stdnse.seeall)
|
||||
|
||||
-- These arrays are filled in with constants at the bottom of this file
|
||||
@@ -942,69 +943,52 @@ function smb_read(smb, read_data)
|
||||
return true, header, parameters, data
|
||||
end
|
||||
|
||||
--- Sends out <code>SMB_COM_NEGOTIATE</code>, which is typically the first SMB packet sent out.
|
||||
--
|
||||
---
|
||||
-- Negotiates SMBv1 connections
|
||||
--
|
||||
-- Sends the following:
|
||||
-- * List of known protocols
|
||||
--
|
||||
-- Receives:
|
||||
-- * The preferred dialect
|
||||
-- * The security mode
|
||||
-- * Max number of multiplexed connections, virtual circuits, and buffer sizes
|
||||
-- * The server's system time and timezone
|
||||
-- * The "encryption key" (aka, the server challenge)
|
||||
-- * The capabilities
|
||||
-- * The server and domain names
|
||||
-- This function adds to <code>smb</code>:
|
||||
-- * 'security_mode' Whether or not to use cleartext passwords, message signatures, etc.
|
||||
-- * 'max_mpx' Maximum number of multiplexed connections
|
||||
-- * 'max_vc' Maximum number of virtual circuits
|
||||
-- * 'max_buffer' Maximum buffer size
|
||||
-- * 'max_raw_buffer' Maximum buffer size for raw connections (considered obsolete)
|
||||
-- * 'session_key' A value that's basically just echoed back
|
||||
-- * 'capabilities' The server's capabilities
|
||||
-- * 'time' The server's time (in UNIX-style seconds since 1970)
|
||||
-- * 'date' The server's date in a user-readable format
|
||||
-- * 'timezone' The server's timezone, in hours from UTC
|
||||
-- * 'timezone_str' The server's timezone, as a string
|
||||
-- * 'server_challenge' A random string used for challenge/response
|
||||
-- * 'domain' The server's primary domain or workgroup
|
||||
-- * 'server' The server's name
|
||||
--
|
||||
--@param smb The SMB object associated with the connection
|
||||
--@param overrides [optional] Overrides for various fields
|
||||
--@return (status, result) If status is false, result is an error message. Otherwise, result is
|
||||
-- nil and the following elements are added to <code>smb</code>:
|
||||
-- * 'security_mode' Whether or not to use cleartext passwords, message signatures, etc.
|
||||
-- * 'max_mpx' Maximum number of multiplexed connections
|
||||
-- * 'max_vc' Maximum number of virtual circuits
|
||||
-- * 'max_buffer' Maximum buffer size
|
||||
-- * 'max_raw_buffer' Maximum buffer size for raw connections (considered obsolete)
|
||||
-- * 'session_key' A value that's basically just echoed back
|
||||
-- * 'capabilities' The server's capabilities
|
||||
-- * 'time' The server's time (in UNIX-style seconds since 1970)
|
||||
-- * 'date' The server's date in a user-readable format
|
||||
-- * 'timezone' The server's timezone, in hours from UTC
|
||||
-- * 'timezone_str' The server's timezone, as a string
|
||||
-- * 'server_challenge' A random string used for challenge/response
|
||||
-- * 'domain' The server's primary domain or workgroup
|
||||
-- * 'server' The server's name
|
||||
function negotiate_protocol(smb, overrides)
|
||||
-- @param smb The SMB object associated with the connection.
|
||||
-- @param overrides Overrides table.
|
||||
-- @return Boolean status
|
||||
-- @return The negotiated dialect in human readable form or an error message.
|
||||
---
|
||||
function negotiate_v1(smb, overrides)
|
||||
local header, parameters, data
|
||||
local pos
|
||||
local header1, header2, header3, header4, command, status, flags, flags2, pid_high, signature, unused, pid, mid
|
||||
|
||||
header = smb_encode_header(smb, command_codes['SMB_COM_NEGOTIATE'], overrides)
|
||||
local result, err
|
||||
local pos, header1, header2, header3, header4, command, status, flags, flags2, pid_high, signature, unused, pid, uid, tid, mid
|
||||
|
||||
header = smb_encode_header(smb, command_codes['SMB_COM_NEGOTIATE'], overrides)
|
||||
-- Make sure we have overrides
|
||||
overrides = overrides or {}
|
||||
|
||||
-- Parameters are blank
|
||||
parameters = ""
|
||||
|
||||
-- Data is a list of strings, terminated by a blank one.
|
||||
if(overrides['dialects'] == nil) then
|
||||
data = bin.pack("<CzCz", 2, (overrides['dialect'] or "NT LM 0.12"), 2, "")
|
||||
else
|
||||
data = ""
|
||||
for _, v in ipairs(overrides['dialects']) do
|
||||
data = data .. bin.pack("<Cz", 2, v)
|
||||
end
|
||||
data = data .. bin.pack("Cz", 2, "")
|
||||
end
|
||||
data = bin.pack("<CzCz", 2, (overrides['dialect'] or "NT LM 0.12"), 2, "")
|
||||
|
||||
-- Send the negotiate request
|
||||
stdnse.debug2("SMB: Sending SMB_COM_NEGOTIATE")
|
||||
local result, err = smb_send(smb, header, parameters, data, overrides)
|
||||
result, err = smb_send(smb, header, parameters, data, overrides)
|
||||
if(status == false) then
|
||||
return false, err
|
||||
end
|
||||
|
||||
-- Read the result
|
||||
status, header, parameters, data = smb_read(smb)
|
||||
if(status ~= true) then
|
||||
@@ -1012,7 +996,6 @@ function negotiate_protocol(smb, overrides)
|
||||
end
|
||||
|
||||
-- Parse out the header
|
||||
local uid, tid
|
||||
pos, header1, header2, header3, header4, command, status, flags, flags2, pid_high, signature, unused, tid, pid, uid, mid = bin.unpack("<CCCCCICSSlSSSSS", header)
|
||||
|
||||
-- Get the protocol version
|
||||
@@ -1043,6 +1026,7 @@ function negotiate_protocol(smb, overrides)
|
||||
end
|
||||
-- Check if the server didn't like our requested protocol
|
||||
if(smb['dialect'] ~= 0) then
|
||||
stdnse.debug2("Server negotiated an unknown protocol (#%d) -- aborting", smb['dialect'])
|
||||
return false, string.format("Server negotiated an unknown protocol (#%d) -- aborting", smb['dialect'])
|
||||
end
|
||||
|
||||
@@ -1118,9 +1102,80 @@ function negotiate_protocol(smb, overrides)
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
stdnse.debug2("SMB_COM_NEGOTIATE got status:%s", status)
|
||||
if status == 0 then
|
||||
return true, overrides['dialect'] or "NT LM 0.12"
|
||||
end
|
||||
end
|
||||
|
||||
---
|
||||
-- Wrapper function to negotiate the protocol to use in the SMB connection.
|
||||
-- By default it attempts to negotiate with using following dialects:
|
||||
-- * NT LM 12.0 (SMBv1)
|
||||
-- @param smb The SMB object
|
||||
-- @param overrides Overrides table
|
||||
-- @return Boolean status
|
||||
---
|
||||
function negotiate_protocol(smb, overrides)
|
||||
local status, dialect
|
||||
status, dialect = negotiate_v1(smb, overrides)
|
||||
if status then
|
||||
return true
|
||||
else
|
||||
stdnse.debug1("Couldn't negotiate a SMBv1 connection:%s", dialect)
|
||||
return false, string.format("Could not negotiate a connection:%s", dialect)
|
||||
end
|
||||
end
|
||||
|
||||
---
|
||||
-- Returns list of supported dialects for SMBv1, SMBv2 and SMBv3.
|
||||
-- @param host The SMB host to connect to.
|
||||
-- @param overrides [optional] Overrides for various fields.
|
||||
-- @return Boolean status
|
||||
-- @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_dialects
|
||||
local smbstate
|
||||
|
||||
-- Check for SMBv1 first
|
||||
stdnse.debug2("Checking if SMBv1 is supported")
|
||||
status, smbstate = start(host)
|
||||
if(status == false) then
|
||||
return false, smbstate
|
||||
end
|
||||
|
||||
status, smb1_dialects = negotiate_v1(smbstate, overrides)
|
||||
if status then --Add SMBv1 as a dialect
|
||||
table.insert(supported_dialects, smb1_dialects)
|
||||
end
|
||||
stop(smbstate)
|
||||
status = false -- 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})
|
||||
-- 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)
|
||||
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
|
||||
--clean smb connection
|
||||
stop(smbstate)
|
||||
status = false
|
||||
end
|
||||
|
||||
return true, supported_dialects
|
||||
end
|
||||
|
||||
--- This is an internal function and should not be called externally. Use
|
||||
-- the start_session() function instead.
|
||||
|
||||
407
nselib/smb2.lua
Normal file
407
nselib/smb2.lua
Normal file
@@ -0,0 +1,407 @@
|
||||
---
|
||||
-- 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
|
||||
-- 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.
|
||||
--
|
||||
-- @author Paulino Calderon <paulino@calderonpale.com>
|
||||
-- @copyright Same as Nmap--See https://nmap.org/book/man-legal.html
|
||||
---
|
||||
|
||||
local string = require "string"
|
||||
local stdnse = require "stdnse"
|
||||
local netbios = require "netbios"
|
||||
local nmap = require "nmap"
|
||||
local table = require "table"
|
||||
local match = require "match"
|
||||
local math = require "math"
|
||||
local os = require "os"
|
||||
|
||||
_ENV = stdnse.module("smb2", stdnse.seeall)
|
||||
|
||||
local TIMEOUT = 10000
|
||||
local command_names = {}
|
||||
local command_codes =
|
||||
{
|
||||
SMB2_COM_NEGOTIATE = 0x0000,
|
||||
SMB2_COM_SESSION_SETUP = 0x0001,
|
||||
SMB2_COM_LOGOFF = 0x0002,
|
||||
SMB2_COM_TREE_CONNECT = 0x0003,
|
||||
SMB2_COM_TREE_DISCONNECT = 0x0004,
|
||||
SMB2_COM_CREATE = 0x0005,
|
||||
SMB2_COM_CLOSE = 0x0006,
|
||||
SMB2_COM_FLUSH = 0x0007,
|
||||
SMB2_COM_READ = 0x0008,
|
||||
SMB2_COM_WRITE = 0x0009,
|
||||
SMB2_COM_LOCK = 0x000A,
|
||||
SMB2_COM_IOCTL = 0x000B,
|
||||
SMB2_COM_CANCEL = 0x000C,
|
||||
SMB2_COM_ECHO = 0x000D,
|
||||
SMB2_COM_QUERY_DIRECTORY = 0x000E,
|
||||
SMB2_COM_CHANGE_NOTIFY = 0x000F,
|
||||
SMB2_COM_QUERY_INFO = 0x0010,
|
||||
SMB2_COM_SET_INFO = 0x0011,
|
||||
SMB2_COM_OPLOCK_BREAK = 0x0012
|
||||
}
|
||||
local smb2_values_codes = {}
|
||||
local smb2_values = {
|
||||
-- Security Mode
|
||||
SMB2_NEGOTIATE_SIGNING_ENABLED = 0x0001,
|
||||
SMB2_NEGOTIATE_SIGNING_REQUIRED = 0x0002,
|
||||
-- Capabilities
|
||||
SMB2_GLOBAL_CAP_DFS = 0x00000001,
|
||||
SMB2_GLOBAL_CAP_LEASING = 0x00000002,
|
||||
SMB2_GLOBAL_CAP_LARGE_MTU = 0x00000004,
|
||||
SMB2_GLOBAL_CAP_MULTI_CHANNEL = 0x00000008,
|
||||
SMB2_GLOBAL_CAP_PERSISTENT_HANDLES = 0x00000010,
|
||||
SMB2_GLOBAL_CAP_DIRECTORY_LEASING = 0x00000020,
|
||||
SMB2_GLOBAL_CAP_ENCRYPTION = 0x00000040,
|
||||
-- Context Types
|
||||
SMB2_ENCRYPTION_CAPABILITIES = 0x0002,
|
||||
SMB2_PREAUTH_INTEGRITY_CAPABILITIES = 0x0001
|
||||
}
|
||||
|
||||
for i, v in pairs(command_codes) do
|
||||
command_names[v] = i
|
||||
end
|
||||
for i, v in pairs(smb2_values) do
|
||||
smb2_values_codes[v] = i
|
||||
end
|
||||
|
||||
---
|
||||
-- Creates a SMB2 SYNC header packet.
|
||||
--
|
||||
-- SMB2 Packet Header - SYNC:
|
||||
-- * https://msdn.microsoft.com/en-us/library/cc246529.aspx
|
||||
--
|
||||
-- @param smb The SMB object associated with the connection.
|
||||
-- @param command The SMB2 command to execute.
|
||||
-- @param overrides Overrides table.
|
||||
-- @return header The encoded SMB2 SYNC header.
|
||||
---
|
||||
function smb2_encode_header_sync(smb, command, overrides)
|
||||
overrides = overrides or {}
|
||||
|
||||
local sig = "\xFESMB" -- SMB2 packet
|
||||
local structureSize = 64 -- SYNC header structure size
|
||||
local flags = 0 -- TODO: Set flags that will work for all dialects
|
||||
|
||||
-- Increase the message id
|
||||
if smb['MessageId'] then
|
||||
smb['MessageId'] = smb['MessageId'] + 1
|
||||
end
|
||||
|
||||
-- Header structure
|
||||
local header = string.pack("<c4 I2 I2 I4 I2 I2 I4 I4 I8 I4 I4 I8 c16",
|
||||
sig, -- 4 bytes: ProtocolId
|
||||
structureSize, -- 2 bytes: StructureSize. Must be 64.
|
||||
(overrides['CreditCharge'] or 0), -- 2 bytes: CreditCharge.
|
||||
(overrides['Status'] or 0), -- 4 bytes: (ChannelSequence/Reserved)/Status.
|
||||
command, -- 2 bytes: Command.
|
||||
(overrides['CreditR'] or 0), -- 2 bytes: CreditRequest/CreditResponse.
|
||||
(overrides['Flags'] or flags), -- 4 bytes: Flags. TODO
|
||||
(overrides['NextCommand'] or 0), -- 4 bytes: NextCommand.
|
||||
(overrides['MessageId'] or smb['MessageId'] or 0), -- 8 bytes: MessageId.
|
||||
(overrides['Reserved'] or 0), -- 4 bytes: Reserved.
|
||||
(overrides['TreeId'] or smb['TreeId'] or 0), -- 4 bytes: TreeId.
|
||||
(overrides['SessionId'] or smb['SessionId'] or 0), -- 8 bytes: SessionId.
|
||||
(overrides['Signature'] or '1234567890123456') -- 16 bytes: Signature.
|
||||
)
|
||||
|
||||
return header
|
||||
end
|
||||
|
||||
---
|
||||
-- Sends a SMB2 packet
|
||||
-- @param smb The SMB object associated with the connection
|
||||
-- @param header The header encoded with <code>smb_encode_sync_header</code>.
|
||||
-- @param data The data.
|
||||
-- @param overrides Overrides table.
|
||||
-- @return Boolean Status.
|
||||
-- @return An error message if status is false.
|
||||
---
|
||||
function smb2_send(smb, header, data, overrides)
|
||||
overrides = overrides or {}
|
||||
local body = header .. data
|
||||
local attempts = 5
|
||||
local status, err
|
||||
|
||||
local out = string.pack(">I<c" .. #body, #body, body)
|
||||
repeat
|
||||
attempts = attempts - 1
|
||||
stdnse.debug3("SMB: Sending SMB packet (len: %d, attempts remaining: %d)", #out, attempts)
|
||||
status, err = smb['socket']:send(out)
|
||||
until(status or (attempts == 0))
|
||||
|
||||
if(attempts == 0) then
|
||||
stdnse.debug1("SMB: Sending packet failed after 5 tries! Giving up.")
|
||||
end
|
||||
|
||||
return status, err
|
||||
end
|
||||
|
||||
---
|
||||
-- Reads the next SMB2 packet from the socket, and parses it into the header and data.
|
||||
-- Netbios handling based taken from smb.lua.
|
||||
--
|
||||
-- @param smb The SMB object associated with the connection
|
||||
-- @param read_data [optional] Return data section. Set to false if you only need the header. Default: true
|
||||
-- @return (status, header, data) If status is true, the header,
|
||||
-- and data are all the raw arrays of bytes.
|
||||
-- If status is false, header contains an error message and data is undefined.
|
||||
---
|
||||
function smb2_read(smb, read_data)
|
||||
local status
|
||||
local pos, netbios_data, netbios_length, length, header, parameter_length, parameters, data_length, data
|
||||
local attempts = 5
|
||||
|
||||
stdnse.debug3("SMB2: Receiving SMB2 packet")
|
||||
|
||||
-- Receive the response -- we make sure to receive at least 4 bytes, the length of the NetBIOS length
|
||||
smb['socket']:set_timeout(TIMEOUT)
|
||||
|
||||
-- perform 5 attempt to read the Netbios header
|
||||
local netbios
|
||||
repeat
|
||||
attempts = attempts - 1
|
||||
status, netbios_data = smb['socket']:receive_buf(match.numbytes(4), true);
|
||||
|
||||
if ( not(status) and netbios_data == "EOF" ) then
|
||||
return false, "SMB2: ERROR: Server disconnected the connection"
|
||||
end
|
||||
until(status or (attempts == 0))
|
||||
|
||||
-- Make sure the connection is still alive
|
||||
if(status ~= true) then
|
||||
return false, "SMB2: Failed to receive bytes after 5 attempts: " .. netbios_data
|
||||
end
|
||||
|
||||
-- The length of the packet is 4 bytes of big endian (for our purposes).
|
||||
-- The NetBIOS header is 24 bits, big endian
|
||||
netbios_length, pos = string.unpack(">I", netbios_data)
|
||||
if(netbios_length == nil) then
|
||||
return false, "SMB2: ERROR:Server returned less data than it was supposed to"
|
||||
end
|
||||
-- Make the length 24 bits
|
||||
netbios_length = netbios_length & 0x00FFFFFF
|
||||
-- The total length is the netbios_length, plus 4 (for the length itself)
|
||||
length = netbios_length + 4
|
||||
|
||||
local attempts = 5
|
||||
local smb_data
|
||||
repeat
|
||||
attempts = attempts - 1
|
||||
status, smb_data = smb['socket']:receive_buf(match.numbytes(netbios_length), true)
|
||||
until(status or (attempts == 0))
|
||||
|
||||
-- Make sure the connection is still alive
|
||||
if(status ~= true) then
|
||||
return false, "SMB2: Failed to receive bytes after 5 attempts: " .. smb_data
|
||||
end
|
||||
|
||||
local result = netbios_data .. smb_data
|
||||
if(#result ~= length) then
|
||||
stdnse.debug1("SMB2: ERROR: Received wrong number of bytes, there will likely be issues (received %d, expected %d)", #result, length)
|
||||
return false, string.format("SMB2: ERROR: Didn't receive the expected number of bytes; received %d, expected %d. This will almost certainly cause some errors.", #result, length)
|
||||
end
|
||||
|
||||
-- The header is 64 bytes.
|
||||
if (pos + 64 > #result) then
|
||||
stdnse.debug2("SMB2: SMB2 packet too small. Size needed to be at least '%d' but we got '%d' bytes", pos+64, #result)
|
||||
return false, "SMB2: ERROR: Header packet too small."
|
||||
end
|
||||
header, pos = string.unpack("<c64", result, pos)
|
||||
if(header == nil) then
|
||||
return false, "SMB2: ERROR: Server returned less data than it was supposed to (one or more fields are missing); aborting [3]"
|
||||
end
|
||||
|
||||
-- Read the data section or skip it if read_data is false.
|
||||
if(read_data == nil or read_data == true) then
|
||||
data, pos = string.unpack("<c" .. #result - pos + 1, result, pos)
|
||||
if(data == nil) then
|
||||
return false, "SMB2: ERROR: Server returned less data than it was supposed to (one or more fields are missing); aborting [7]"
|
||||
end
|
||||
else
|
||||
data = nil
|
||||
end
|
||||
|
||||
stdnse.debug3("SMB2: smb2_read() received %d bytes", #result)
|
||||
return true, header, data
|
||||
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.
|
||||
--
|
||||
-- Packet structure: https://msdn.microsoft.com/en-us/library/cc246543.aspx
|
||||
--
|
||||
-- @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.
|
||||
function negotiate_v2(smb, overrides)
|
||||
local header, parameters, data
|
||||
local StructureSize = 36 -- Must be set to 36.
|
||||
local DialectCount
|
||||
if overrides['Dialects'] then
|
||||
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,
|
||||
-- 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 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 status, err
|
||||
|
||||
if not( overrides['Dialects'] ) then -- Set 2.02 as default dialect if user didn't select one
|
||||
overrides['Dialects'] = {0x0202}
|
||||
end
|
||||
|
||||
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.
|
||||
data = string.pack("<I2 I2 I2 I2 I4 c16",
|
||||
StructureSize, -- 2 bytes: StructureSize
|
||||
DialectCount, -- 2 bytes: DialectCount
|
||||
SecurityMode, -- 2 bytes: SecurityMode
|
||||
0, -- 2 bytes: Reserved - Must be 0
|
||||
Capabilities, -- 4 bytes: Capabilities - 0 for dialects > 3.x
|
||||
GUID -- 16 bytes: ClientGuid
|
||||
)
|
||||
|
||||
-- The next block gets interpreted in different ways depending on the dialect
|
||||
if stdnse.contains(overrides['Dialects'], 0x0311) then
|
||||
is_0311 = true
|
||||
end
|
||||
|
||||
-- If we are dealing with 3.11 we need to set the following fields:
|
||||
-- NegotiateContextOffset, NegotiateContextCount, and Reserved2
|
||||
if is_0311 then
|
||||
total_data = #header + #data + (DialectCount*2)
|
||||
padding_data = string.rep("\0", (8 - total_data % 8) % 8)
|
||||
total_data = total_data + #padding_data
|
||||
data = data .. string.pack("<I4 I2 I2",
|
||||
total_data+8, -- NegotiateContextOffset (4 bytes)
|
||||
0x2, -- NegotiateContextCount (2 bytes)
|
||||
0x0 -- Reserved2 (2 bytes)
|
||||
)
|
||||
else -- If it's not 3.11, the bytes are the ClientStartTime (8 bytes)
|
||||
data = data .. string.pack("<I8", ClientStartTime) -- If it is not 3.11, we set it to 0
|
||||
end -- if is_0311
|
||||
|
||||
-- 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
|
||||
data = data .. string.pack("<I2", 0x0210)
|
||||
else -- Dialects are set in overrides table
|
||||
for _, v in ipairs(overrides['Dialects']) do
|
||||
data = data .. string.pack("<I2", v)
|
||||
end
|
||||
end
|
||||
|
||||
-- If 3.11, we now need to add some padding between the dialects and the NegotiateContextList
|
||||
-- I was only able to get this to work using both NegotiateContexts:
|
||||
-- * SMB2_PREAUTH_INTEGRITY_CAPABILITIES
|
||||
-- * SMB2_ENCRYPTION_CAPABILITIES
|
||||
if is_0311 then
|
||||
data = data .. padding_data
|
||||
local negotiate_context_list, context_data
|
||||
|
||||
-- We set SMB2_ENCRYPTION_CAPABILITIES first
|
||||
context_data = string.pack("<I2 I2 I2",
|
||||
0x2, -- CipherCount (2 bytes): 2 ciphers available
|
||||
0x0002, -- Ciphers (2 bytes each): AES-128-GCM
|
||||
0x0001 -- Ciphers (2 bytes each): AES-128-CCM
|
||||
)
|
||||
data = data .. string.pack("<I2 I2 I4",
|
||||
smb2_values['SMB2_ENCRYPTION_CAPABILITIES'],-- ContextType (2 bytes)
|
||||
#context_data, -- DataLength (2 bytes)
|
||||
0x0 -- Reserved (4 bytes)
|
||||
) .. context_data -- Data (SMB2_ENCRYPTION_CAPABILITIES)
|
||||
|
||||
-- We now add SMB2_PREAUTH_INTEGRITY_CAPABILITIES
|
||||
-- We add the padding between contexts so they are 8 byte aligned
|
||||
total_data = #header + #data
|
||||
padding_data = string.rep("\0", (8 - total_data % 8) % 8)
|
||||
data = data .. padding_data
|
||||
context_data = context_data .. string.pack("<I2 I2 I2 I16 I16",
|
||||
0x1, -- HashAlgorithmCount (2 bytes)
|
||||
0x20, -- SaltLength (2 bytes)
|
||||
0x0001, -- HashAlgorithms (2 bytes each): SHA-512
|
||||
0x0, -- Salt
|
||||
0x1 -- Salt
|
||||
)
|
||||
data = data .. string.pack("<I2 I2 I4",
|
||||
smb2_values['SMB2_PREAUTH_INTEGRITY_CAPABILITIES'], -- ContextType (2 bytes)
|
||||
#context_data, -- DataLength (2 bytes)
|
||||
0x0 -- Reserved (4 bytes)
|
||||
) .. context_data
|
||||
|
||||
end
|
||||
|
||||
status, err = smb2_send(smb, header, data)
|
||||
if not status then
|
||||
return false, err
|
||||
end
|
||||
status, header, data = smb2_read(smb)
|
||||
|
||||
local protocol_version, structure_size, credit_charge, status = string.unpack("<c4 I2 I2 I4", header)
|
||||
-- Get the protocol version
|
||||
if(protocol_version ~= ("\xFESMB") or structure_size ~= 64) then
|
||||
return false, "SMB: Server returned an invalid SMBv2 packet"
|
||||
end
|
||||
stdnse.debug2("SMB2_COM_NEGOTIATE returned status '%s'", status)
|
||||
|
||||
if status ~= 0 then
|
||||
stdnse.debug2("SMB2_COM_NEGOTIATE command failed: Dialect not supported.")
|
||||
return false, "SMB2: Dialect is not supported. Exiting."
|
||||
end
|
||||
|
||||
local data_structure_size, security_mode, negotiate_context_count
|
||||
data_structure_size, smb['security_mode'], smb['dialect'],
|
||||
negotiate_context_count, smb['server_guid'], smb['capabilities'],
|
||||
smb['max_trans'], smb['max_read'], smb['max_write'], smb['time'],
|
||||
smb['start_time'] = string.unpack("<I2 I2 I2 I2 c16 I4 I4 I4 I4 I8 I8", data)
|
||||
|
||||
if(smb['dialect'] == nil or smb['capabilities'] == nil or smb['server_guid'] == nil or smb['security_mode'] == nil) then
|
||||
return false, "SMB: ERROR: Server returned less data than it was supposed to (one or more fields are missing)"
|
||||
end
|
||||
|
||||
if(data_structure_size ~= 65) then
|
||||
return false, string.format("Server returned an unknown structure size in SMB2 NEGOTIATE response")
|
||||
end
|
||||
-- To be consistent with our current SMBv1 implementation, let's set this values if not present
|
||||
if(smb['time'] == nil) then
|
||||
smb['time'] = 0
|
||||
end
|
||||
|
||||
if(smb['timezone'] == nil) then
|
||||
smb['timezone'] = 0
|
||||
end
|
||||
|
||||
-- Convert the time and timezone to human readable values (taken from smb.lua)
|
||||
smb['time'] = (smb['time'] // 10000000) - 11644473600
|
||||
smb['date'] = os.date("%Y-%m-%d %H:%M:%S", smb['time'])
|
||||
smb['start_time'] = (smb['start_time'] // 10000000) - 11644473600
|
||||
smb['start_date'] = os.date("%Y-%m-%d %H:%M:%S", smb['start_time'])
|
||||
|
||||
local security_buffer_offset, security_buffer_length, neg_context_offset
|
||||
security_buffer_offset, security_buffer_length, neg_context_offset = string.unpack("<I2 I2 I4", data)
|
||||
if status == 0 then
|
||||
return true, overrides['Dialects']
|
||||
else
|
||||
return false, string.format("Status error code:%s",status)
|
||||
end
|
||||
end
|
||||
|
||||
return _ENV;
|
||||
@@ -463,6 +463,7 @@ Entry { filename = "smb-ls.nse", categories = { "discovery", "safe", } }
|
||||
Entry { filename = "smb-mbenum.nse", categories = { "discovery", "safe", } }
|
||||
Entry { filename = "smb-os-discovery.nse", categories = { "default", "discovery", "safe", } }
|
||||
Entry { filename = "smb-print-text.nse", categories = { "intrusive", } }
|
||||
Entry { filename = "smb-protocols.nse", categories = { "discovery", "safe", } }
|
||||
Entry { filename = "smb-psexec.nse", categories = { "intrusive", } }
|
||||
Entry { filename = "smb-security-mode.nse", categories = { "default", "discovery", "safe", } }
|
||||
Entry { filename = "smb-server-stats.nse", categories = { "discovery", "intrusive", } }
|
||||
@@ -477,6 +478,10 @@ Entry { filename = "smb-vuln-ms10-054.nse", categories = { "dos", "intrusive", "
|
||||
Entry { filename = "smb-vuln-ms10-061.nse", categories = { "intrusive", "vuln", } }
|
||||
Entry { filename = "smb-vuln-ms17-010.nse", categories = { "safe", "vuln", } }
|
||||
Entry { filename = "smb-vuln-regsvc-dos.nse", categories = { "dos", "exploit", "intrusive", "vuln", } }
|
||||
Entry { filename = "smb2-capabilities.nse", categories = { "discovery", "safe", } }
|
||||
Entry { filename = "smb2-security-mode.nse", categories = { "default", "discovery", "safe", } }
|
||||
Entry { filename = "smb2-time.nse", categories = { "default", "discovery", "safe", } }
|
||||
Entry { filename = "smb2-vuln-uptime.nse", categories = { "safe", "vuln", } }
|
||||
Entry { filename = "smbv2-enabled.nse", categories = { "default", "safe", } }
|
||||
Entry { filename = "smtp-brute.nse", categories = { "brute", "intrusive", } }
|
||||
Entry { filename = "smtp-commands.nse", categories = { "default", "discovery", "safe", } }
|
||||
|
||||
75
scripts/smb-protocols.nse
Normal file
75
scripts/smb-protocols.nse
Normal file
@@ -0,0 +1,75 @@
|
||||
local smb = require "smb"
|
||||
local stdnse = require "stdnse"
|
||||
local string = require "string"
|
||||
local nmap = require "nmap"
|
||||
|
||||
description = [[
|
||||
Attempts to list the supported protocols and dialects of a SMB server.
|
||||
|
||||
The script attempts to initiate a connection using the dialects:
|
||||
* NT LM 0.12 (SMBv1)
|
||||
* 2.02 (SMBv2)
|
||||
* 2.10 (SMBv2)
|
||||
* 3.00 (SMBv3)
|
||||
* 3.02 (SMBv3)
|
||||
* 3.11 (SMBv3)
|
||||
|
||||
Aditionally if SMBv1 is found enabled, it will mark it as insecure.
|
||||
]]
|
||||
|
||||
---
|
||||
-- @usage nmap -p445 --script smb-protocols <target>
|
||||
-- @usage nmap -p139 --script smb-protocols <target>
|
||||
--
|
||||
-- @output
|
||||
-- | smb-protocols:
|
||||
-- | dialects:
|
||||
-- | NT LM 0.12 (SMBv1) [dangerous, but default]
|
||||
-- | 2.02
|
||||
-- | 2.10
|
||||
-- | 3.00
|
||||
-- | 3.02
|
||||
-- |_ 3.11
|
||||
--
|
||||
-- @xmloutput
|
||||
-- <table key="dialects">
|
||||
-- <elem>NT LM 0.12 (SMBv1) [dangerous, but default]</elem>
|
||||
-- <elem>2.02</elem>
|
||||
-- <elem>2.10</elem>
|
||||
-- <elem>3.00</elem>
|
||||
-- <elem>3.02</elem>
|
||||
-- <elem>3.11</elem>
|
||||
-- </table>
|
||||
---
|
||||
|
||||
author = "Paulino Calderon"
|
||||
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
|
||||
categories = {"safe", "discovery"}
|
||||
|
||||
hostrule = function(host)
|
||||
return smb.get_port(host) ~= nil
|
||||
end
|
||||
|
||||
action = function(host,port)
|
||||
local status, supported_dialects, overrides
|
||||
local output = stdnse.output_table()
|
||||
overrides = {}
|
||||
status, supported_dialects = smb.list_dialects(host, overrides)
|
||||
if status then
|
||||
for i, v in pairs(supported_dialects) do -- Mark SMBv1 as insecure
|
||||
if v == "NT LM 0.12" then
|
||||
supported_dialects[i] = v .. " (SMBv1) [dangerous, but default]"
|
||||
end
|
||||
end
|
||||
output.dialects = supported_dialects
|
||||
end
|
||||
|
||||
if #output.dialects>0 then
|
||||
return output
|
||||
else
|
||||
stdnse.debug1("No dialects were accepted")
|
||||
if nmap.verbosity()>1 then
|
||||
return "No dialects accepted. Something may be blocking the responses"
|
||||
end
|
||||
end
|
||||
end
|
||||
117
scripts/smb2-capabilities.nse
Normal file
117
scripts/smb2-capabilities.nse
Normal file
@@ -0,0 +1,117 @@
|
||||
local smb = require "smb"
|
||||
local smb2 = require "smb2"
|
||||
local stdnse = require "stdnse"
|
||||
local string = require "string"
|
||||
local table = require "table"
|
||||
local nmap = require "nmap"
|
||||
|
||||
description = [[
|
||||
Attempts to list the supported capabilities in a SMBv2 server for each
|
||||
enabled dialect.
|
||||
|
||||
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
|
||||
|
||||
References:
|
||||
* https://msdn.microsoft.com/en-us/library/cc246561.aspx
|
||||
]]
|
||||
|
||||
---
|
||||
-- @usage nmap -p 445 --script smb2-capabilities <target>
|
||||
-- @usage nmap -p 139 --script smb2-capabilities <target>
|
||||
--
|
||||
-- @output
|
||||
-- | smb2-capabilities:
|
||||
-- | 2.02:
|
||||
-- | Distributed File System
|
||||
-- | 2.10:
|
||||
-- | Distributed File System
|
||||
-- | Leasing
|
||||
-- | Multi-credit operations
|
||||
--
|
||||
-- @xmloutput
|
||||
-- <table key="2.02">
|
||||
-- <elem>Distributed File System</elem>
|
||||
-- </table>
|
||||
-- <table key="2.10">
|
||||
-- <elem>Distributed File System</elem>
|
||||
-- <elem>Leasing</elem>
|
||||
-- <elem>Multi-credit operations</elem>
|
||||
-- </table>
|
||||
---
|
||||
|
||||
author = "Paulino Calderon"
|
||||
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
|
||||
categories = {"safe", "discovery"}
|
||||
|
||||
hostrule = function(host)
|
||||
return smb.get_port(host) ~= nil
|
||||
end
|
||||
|
||||
action = function(host,port)
|
||||
local status, smbstate, overrides
|
||||
local output = stdnse.output_table()
|
||||
overrides = {}
|
||||
|
||||
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
|
||||
stdnse.debug1("Could not establish a connection.")
|
||||
return nil
|
||||
end
|
||||
-- We set our overrides Dialects table with the dialect we are testing
|
||||
overrides['Dialects'] = {dialect}
|
||||
status = smb2.negotiate_v2(smbstate, overrides)
|
||||
if status then
|
||||
local capabilities = {}
|
||||
stdnse.debug2("SMB2: Server capabilities: '%s'", smbstate['capabilities'])
|
||||
|
||||
-- We check the capabilities flags. Not all of them are supported by
|
||||
-- every dialect but we dumb check anyway.
|
||||
if smbstate['capabilities'] & 0x01 == 0x01 then
|
||||
table.insert(capabilities, "Distributed File System")
|
||||
end
|
||||
if smbstate['capabilities'] & 0x02 == 0x02 then
|
||||
table.insert(capabilities, "Leasing")
|
||||
end
|
||||
if smbstate['capabilities'] & 0x04 == 0x04 then
|
||||
table.insert(capabilities, "Multi-credit operations")
|
||||
end
|
||||
if smbstate['capabilities'] & 0x08 == 0x08 then
|
||||
table.insert(capabilities, "Multiple Channel support")
|
||||
end
|
||||
if smbstate['capabilities'] & 0x10 == 0x10 then
|
||||
table.insert(capabilities, "Persistent handles")
|
||||
end
|
||||
if smbstate['capabilities'] & 0x20 == 0x20 then
|
||||
table.insert(capabilities, "Directory Leasing")
|
||||
end
|
||||
if smbstate['capabilities'] & 0x40 == 0x40 then
|
||||
table.insert(capabilities, "Encryption")
|
||||
end
|
||||
if #capabilities<1 then
|
||||
table.insert(capabilities, "All capabilities are disabled")
|
||||
end
|
||||
output[stdnse.tohex(dialect, {separator = ".", group = 2})] = capabilities
|
||||
end
|
||||
smb.stop(smbstate)
|
||||
status = false
|
||||
end
|
||||
|
||||
if #output>0 then
|
||||
return output
|
||||
else
|
||||
stdnse.debug1("No dialects were accepted.")
|
||||
if nmap.verbosity()>1 then
|
||||
return "Couldn't establish a SMBv2 connection."
|
||||
end
|
||||
end
|
||||
end
|
||||
100
scripts/smb2-security-mode.nse
Normal file
100
scripts/smb2-security-mode.nse
Normal file
@@ -0,0 +1,100 @@
|
||||
local smb = require "smb"
|
||||
local smb2 = require "smb2"
|
||||
local stdnse = require "stdnse"
|
||||
local string = require "string"
|
||||
local table = require "table"
|
||||
local nmap = require "nmap"
|
||||
|
||||
description = [[
|
||||
Determines the message signing configuration in SMBv2 servers
|
||||
for all supported dialects.
|
||||
|
||||
The script sends a SMB2_COM_NEGOTIATE request for each SMB2/SMB3 dialect
|
||||
and parses the security mode field to determine the message signing
|
||||
configuration of the SMB server.
|
||||
|
||||
References:
|
||||
* https://msdn.microsoft.com/en-us/library/cc246561.aspx
|
||||
]]
|
||||
|
||||
---
|
||||
-- @usage nmap -p 445 --script smb2-security-mode <target>
|
||||
-- @usage nmap -p 139 --script smb2-security-mode <target>
|
||||
--
|
||||
-- @output
|
||||
-- | smb2-security-mode:
|
||||
-- | 3.11:
|
||||
-- |_ Message signing enabled but not required
|
||||
--
|
||||
-- @xmloutput
|
||||
-- <table key="3.11">
|
||||
-- <elem>Message signing enabled but not required</elem>
|
||||
-- </table>
|
||||
---
|
||||
|
||||
author = "Paulino Calderon"
|
||||
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
|
||||
categories = {"safe", "discovery", "default"}
|
||||
|
||||
hostrule = function(host)
|
||||
return smb.get_port(host) ~= nil
|
||||
end
|
||||
|
||||
action = function(host,port)
|
||||
local status, smbstate, overrides
|
||||
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[1], {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
|
||||
|
||||
if #output>0 then
|
||||
return output
|
||||
else
|
||||
stdnse.debug1("No SMB2/SMB3 dialects were accepted.")
|
||||
if nmap.verbosity()>1 then
|
||||
return "Couldn't establish a SMBv2 connection."
|
||||
end
|
||||
end
|
||||
end
|
||||
49
scripts/smb2-time.nse
Normal file
49
scripts/smb2-time.nse
Normal file
@@ -0,0 +1,49 @@
|
||||
local smb = require "smb"
|
||||
local stdnse = require "stdnse"
|
||||
local smb2 = require "smb2"
|
||||
|
||||
description = [[
|
||||
Attempts to obtain the current system date and the start date of a SMB2 server.
|
||||
]]
|
||||
|
||||
---
|
||||
-- @usage nmap -p445 --script smb2-time <target>
|
||||
--
|
||||
-- @output
|
||||
-- Host script results:
|
||||
-- | smb2-time:
|
||||
-- | date: 2017-07-28 03:06:34
|
||||
-- |_ start_date: 2017-07-20 09:29:49
|
||||
--
|
||||
-- @xmloutput
|
||||
-- <elem key="date">2017-07-28 03:07:57</elem>
|
||||
-- <elem key="start_date">2017-07-20 09:29:49</elem>
|
||||
---
|
||||
|
||||
author = "Paulino Calderon <calderon()websec.mx>"
|
||||
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
|
||||
categories = {"discovery", "safe", "default"}
|
||||
|
||||
hostrule = function(host)
|
||||
return smb.get_port(host) ~= nil
|
||||
end
|
||||
|
||||
action = function(host,port)
|
||||
local smbstate, status, overrides
|
||||
local output = stdnse.output_table()
|
||||
overrides = {}
|
||||
status, smbstate = smb.start(host)
|
||||
status = smb2.negotiate_v2(smbstate, overrides)
|
||||
|
||||
if status then
|
||||
stdnse.debug2("SMB2: Date: %s (%s) Start date:%s (%s)",
|
||||
smbstate['date'], smbstate['time'],
|
||||
smbstate['start_date'], smbstate['start_time'])
|
||||
output.date = smbstate['date']
|
||||
output.start_date = smbstate['start_date']
|
||||
return output
|
||||
else
|
||||
stdnse.debug2("Negotiation failed")
|
||||
return "Protocol negotiation failed (SMB2)"
|
||||
end
|
||||
end
|
||||
152
scripts/smb2-vuln-uptime.nse
Normal file
152
scripts/smb2-vuln-uptime.nse
Normal file
@@ -0,0 +1,152 @@
|
||||
local smb = require "smb"
|
||||
local vulns = require "vulns"
|
||||
local stdnse = require "stdnse"
|
||||
local string = require "string"
|
||||
local smb2 = require "smb2"
|
||||
local table = require "table"
|
||||
|
||||
description = [[
|
||||
Attempts to detect missing patches in Windows systems by checking the
|
||||
uptime returned during the SMB2 protocol negotiation.
|
||||
|
||||
SMB2 protocol negotiation response returns the system boot time
|
||||
pre-authentication. This information can be used to determine
|
||||
if a system is missing critical patches without triggering IDS/IPS/AVs.
|
||||
|
||||
Remember that a rebooted system may still be vulnerable. This check
|
||||
only reveals unpatched systems based on the uptime, no additional probes are sent.
|
||||
|
||||
References:
|
||||
* https://twitter.com/breakersall/status/880496571581857793
|
||||
]]
|
||||
|
||||
---
|
||||
-- @usage nmap -O --script smb2-vuln-uptime <target>
|
||||
-- @usage nmap -p445 --script smb2-vuln-uptime --script-args smb2-vuln-uptime.skip-os=true <target>
|
||||
--
|
||||
-- @output
|
||||
-- | smb2-vuln-uptime:
|
||||
-- | VULNERABLE:
|
||||
-- | MS17-010: Security update for Windows SMB Server
|
||||
-- | State: LIKELY VULNERABLE
|
||||
-- | IDs: ms:ms17-010 CVE:2017-0147
|
||||
-- | This system is missing a security update that resolves vulnerabilities in
|
||||
-- | Microsoft Windows SMB Server.
|
||||
-- |
|
||||
-- | References:
|
||||
-- | https://cve.mitre.org/cgi-bin/cvename.cgi?name=2017-0147
|
||||
-- |_ https://technet.microsoft.com/en-us/library/security/ms17-010.aspx
|
||||
--
|
||||
-- @xmloutput
|
||||
-- <table key="2017-0147">
|
||||
-- <elem key="title">MS17-010: Security update for Windows SMB Server</elem>
|
||||
-- <elem key="state">LIKELY VULNERABLE</elem>
|
||||
-- <table key="ids">
|
||||
-- <elem>CVE:2017-0147</elem>
|
||||
-- <elem>ms:ms17-010</elem>
|
||||
-- </table>
|
||||
-- <table key="description">
|
||||
-- <elem>This system is missing a security update that resolves vulnerabilities in
 Microsoft Windows SMB Server.
</elem>
|
||||
-- </table>
|
||||
-- <table key="refs">
|
||||
-- <elem>https://cve.mitre.org/cgi-bin/cvename.cgi?name=2017-0147</elem>
|
||||
-- <elem>https://technet.microsoft.com/en-us/library/security/ms17-010.aspx</elem>
|
||||
-- </table>
|
||||
-- </table>
|
||||
--
|
||||
-- @args smb2-vuln-uptime.skip-os Ignore OS detection results and show results
|
||||
---
|
||||
|
||||
author = "Paulino Calderon <calderon()calderonpale.com>"
|
||||
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
|
||||
categories = {"vuln", "safe"}
|
||||
|
||||
hostrule = function(host)
|
||||
local ms = false
|
||||
local os_detection = stdnse.get_script_args(SCRIPT_NAME .. ".skip-os") or false
|
||||
if host.os then
|
||||
for k, v in pairs(host.os) do -- Loop through OS matches
|
||||
if string.match(v['name'], "Microsoft") then
|
||||
ms = true
|
||||
end
|
||||
end
|
||||
end
|
||||
return (smb.get_port(host) ~= nil and ms) or (os_detection)
|
||||
end
|
||||
|
||||
local ms_vulns = {
|
||||
{
|
||||
title = 'MS17-010: Security update for Windows SMB Server',
|
||||
ids = {ms = "ms17-010", CVE = "2017-0147"},
|
||||
desc = [[
|
||||
This system is missing a security update that resolves vulnerabilities in
|
||||
Microsoft Windows SMB Server.
|
||||
]],
|
||||
disclosure_time = 1489471200,
|
||||
disclosure_date = {year=2017, month=3, day=14},
|
||||
references = {
|
||||
'https://technet.microsoft.com/en-us/library/security/ms17-010.aspx',
|
||||
},
|
||||
},
|
||||
{
|
||||
title = 'Microsoft Kerberos Checksum Vulnerability',
|
||||
ids = {ms = "ms14-068", CVE = "2014-6324"},
|
||||
desc = [[
|
||||
This security update resolves a privately reported vulnerability in Microsoft
|
||||
Windows Kerberos KDC that could allow an attacker to elevate unprivileged
|
||||
domain user account privileges to those of the domain administrator account.
|
||||
]],
|
||||
disclosure_time = 1416290400,
|
||||
disclosure_date = {year=2014, month=11, day=18},
|
||||
references = {
|
||||
'https://technet.microsoft.com/en-us/library/security/ms14-068.aspx'
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
local function check_vulns(host, port)
|
||||
local smbstate, status, overrides
|
||||
local vulns_detected = {}
|
||||
|
||||
overrides = {}
|
||||
overrides['Dialects'] = {0x0202}
|
||||
status, smbstate = smb.start(host)
|
||||
status, _ = smb2.negotiate_v2(smbstate, overrides)
|
||||
|
||||
if status then
|
||||
stdnse.debug2("SMB2: Date: %s (%s) Start date:%s (%s)",
|
||||
smbstate['date'], smbstate['time'],
|
||||
smbstate['start_date'], smbstate['start_time'])
|
||||
|
||||
for _, vuln in pairs(ms_vulns) do
|
||||
if smbstate['start_time'] < vuln['disclosure_time'] then
|
||||
stdnse.debug2("Vulnerability detected")
|
||||
vuln.extra_info = string.format("The system hasn't been rebooted since %s", smbstate['start_date'])
|
||||
table.insert(vulns_detected, vuln)
|
||||
end
|
||||
end
|
||||
|
||||
else
|
||||
stdnse.debug2("Negotiation failed")
|
||||
return nil, "Protocol negotiation failed (SMB2)"
|
||||
end
|
||||
return true, vulns_detected
|
||||
end
|
||||
|
||||
action = function(host,port)
|
||||
local status, vulnerabilities
|
||||
local report = vulns.Report:new(SCRIPT_NAME, host, port)
|
||||
|
||||
status, vulnerabilities = check_vulns(host, port)
|
||||
if status then
|
||||
for i, v in pairs(vulnerabilities) do
|
||||
local vuln = { title = v['title'], description = v['desc'],
|
||||
references = v['references'], disclosure_date = v['disclosure_date'],
|
||||
IDS = v['ids']}
|
||||
vuln.state = vulns.STATE.LIKELY_VULN
|
||||
vuln.extra_info = v['extra_info']
|
||||
report:add_vulns(SCRIPT_NAME, vuln)
|
||||
end
|
||||
end
|
||||
return report:make_output()
|
||||
end
|
||||
Reference in New Issue
Block a user