diff --git a/CHANGELOG b/CHANGELOG
index 518f762b0..e2ecc0fd7 100644
--- a/CHANGELOG
+++ b/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]
diff --git a/nselib/smb.lua b/nselib/smb.lua
index a2f3a6d2b..c87154fdd 100644
--- a/nselib/smb.lua
+++ b/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 SMB_COM_NEGOTIATE, 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 smb:
+-- * '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 smb:
--- * '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("
+-- @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("smb_encode_sync_header.
+-- @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(">II", 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(" 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(" 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("
+-- @usage nmap -p139 --script smb-protocols
+--
+-- @output
+-- | smb-protocols:
+-- | dialects:
+-- | NT LM 0.12 (SMBv1) [dangerous, but default]
+-- | 2.02
+-- | 2.10
+-- | 3.00
+-- | 3.02
+-- |_ 3.11
+--
+-- @xmloutput
+--
+-- NT LM 0.12 (SMBv1) [dangerous, but default]
+-- 2.02
+-- 2.10
+-- 3.00
+-- 3.02
+-- 3.11
+--
+---
+
+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
diff --git a/scripts/smb2-capabilities.nse b/scripts/smb2-capabilities.nse
new file mode 100644
index 000000000..d5dfe476b
--- /dev/null
+++ b/scripts/smb2-capabilities.nse
@@ -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
+-- @usage nmap -p 139 --script smb2-capabilities
+--
+-- @output
+-- | smb2-capabilities:
+-- | 2.02:
+-- | Distributed File System
+-- | 2.10:
+-- | Distributed File System
+-- | Leasing
+-- | Multi-credit operations
+--
+-- @xmloutput
+--
+-- Distributed File System
+--
+--
+-- Distributed File System
+-- Leasing
+-- Multi-credit operations
+--
+---
+
+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
diff --git a/scripts/smb2-security-mode.nse b/scripts/smb2-security-mode.nse
new file mode 100644
index 000000000..2bff35ea8
--- /dev/null
+++ b/scripts/smb2-security-mode.nse
@@ -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
+-- @usage nmap -p 139 --script smb2-security-mode
+--
+-- @output
+-- | smb2-security-mode:
+-- | 3.11:
+-- |_ Message signing enabled but not required
+--
+-- @xmloutput
+--
+-- Message signing enabled but not required
+--
+---
+
+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
diff --git a/scripts/smb2-time.nse b/scripts/smb2-time.nse
new file mode 100644
index 000000000..274f1cdf2
--- /dev/null
+++ b/scripts/smb2-time.nse
@@ -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
+--
+-- @output
+-- Host script results:
+-- | smb2-time:
+-- | date: 2017-07-28 03:06:34
+-- |_ start_date: 2017-07-20 09:29:49
+--
+-- @xmloutput
+-- 2017-07-28 03:07:57
+-- 2017-07-20 09:29:49
+---
+
+author = "Paulino Calderon "
+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
diff --git a/scripts/smb2-vuln-uptime.nse b/scripts/smb2-vuln-uptime.nse
new file mode 100644
index 000000000..2a3504458
--- /dev/null
+++ b/scripts/smb2-vuln-uptime.nse
@@ -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
+-- @usage nmap -p445 --script smb2-vuln-uptime --script-args smb2-vuln-uptime.skip-os=true
+--
+-- @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
+--
+-- MS17-010: Security update for Windows SMB Server
+-- LIKELY VULNERABLE
+--
+-- CVE:2017-0147
+-- ms:ms17-010
+--
+--
+-- This system is missing a security update that resolves vulnerabilities in
Microsoft Windows SMB Server.
+--
+--
+-- https://cve.mitre.org/cgi-bin/cvename.cgi?name=2017-0147
+-- https://technet.microsoft.com/en-us/library/security/ms17-010.aspx
+--
+--
+--
+-- @args smb2-vuln-uptime.skip-os Ignore OS detection results and show results
+---
+
+author = "Paulino Calderon "
+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