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

Change how ms-sql NSE scripts run

MS SQL NSE scripts run on database instances, which can be TCP or named
pipes. With this change, all TCP instances on scanned ports will have
script output attached under the port as a portrule script. Named pipe
instances and TCP instances on unscanned ports will be displayed in the
hostrule script output at the end of the host's output. Utility function
mssql.Helper.InitScript makes it easy to write scripts that just work on
a per-instance basis, without bothering where to put the output.
Discovery will be done once per host, regardless of how many scripts are
run, and can be guaranteed to be done before the script's action takes
place.
This commit is contained in:
dmiller
2022-01-03 21:08:52 +00:00
parent 33405fcfb5
commit c3d54f1fac
12 changed files with 265 additions and 474 deletions

View File

@@ -74,12 +74,19 @@
-- argument is not given but <code>mssql.username</code>, a blank password -- argument is not given but <code>mssql.username</code>, a blank password
-- is used. -- is used.
-- --
-- @args mssql.instance-name The name of the instance to connect to. -- @args mssql.instance-name In addition to instances discovered via port
-- scanning and version detection, run scripts on
-- these named instances (string or list of strings)
-- --
-- @args mssql.instance-port The port of the instance to connect to. -- @args mssql.instance-port In addition to instances discovered via port
-- scanning and version detection, run scripts on
-- the instances running on these ports (number or list of numbers)
-- --
-- @args mssql.instance-all Targets all SQL server instances discovered -- @args mssql.instance-all In addition to instances discovered via port
-- through the browser service. -- scanning and version detection, run scripts on all
-- discovered instances. These include named-pipe
-- instances via SMB and those discovered via the
-- browser service.
-- --
-- @args mssql.domain The domain against which to perform integrated -- @args mssql.domain The domain against which to perform integrated
-- authentication. When set, the scripts assume integrated authentication -- authentication. When set, the scripts assume integrated authentication
@@ -107,13 +114,14 @@ local math = require "math"
local match = require "match" local match = require "match"
local nmap = require "nmap" local nmap = require "nmap"
local datetime = require "datetime" local datetime = require "datetime"
local shortport = require "shortport" local outlib = require "outlib"
local smb = require "smb" local smb = require "smb"
local smbauth = require "smbauth" local smbauth = require "smbauth"
local stdnse = require "stdnse" local stdnse = require "stdnse"
local strbuf = require "strbuf" local strbuf = require "strbuf"
local string = require "string" local string = require "string"
local table = require "table" local table = require "table"
local tableaux = require "tableaux"
local unicode = require "unicode" local unicode = require "unicode"
_ENV = stdnse.module("mssql", stdnse.seeall) _ENV = stdnse.module("mssql", stdnse.seeall)
@@ -150,11 +158,24 @@ do
end end
MSSQL_TIMEOUT = timeout MSSQL_TIMEOUT = timeout
SCANNED_PORTS_ONLY = false
if ( stdnse.get_script_args( "mssql.scanned-ports-only" ) ) then
SCANNED_PORTS_ONLY = true
end end
-- Make args either a list or nil
local function list_of (input, transform)
if not input then return nil end
if type(input) ~= "table" then
return {transform(input)}
end end
for i, v in ipairs(input) do
input[i] = transform(v)
end
return input
end
local SCANNED_PORTS_ONLY = not not stdnse.get_script_args("mssql.scanned-ports-only")
local targetInstanceNames = list_of(stdnse.get_script_args("mssql.instance-name"), string.upper)
local targetInstancePorts = list_of(stdnse.get_script_args("mssql.instance-port"), tonumber)
local targetAllInstances = not not stdnse.get_script_args("mssql.instance-all")
-- This constant is number of seconds from 1900-01-01 to 1970-01-01 -- This constant is number of seconds from 1900-01-01 to 1970-01-01
local tds_offset_seconds = -2208988800 - datetime.utc_offset() local tds_offset_seconds = -2208988800 - datetime.utc_offset()
@@ -2561,6 +2582,9 @@ Helper =
--- Gets a table containing SqlServerInstanceInfo objects discovered on --- Gets a table containing SqlServerInstanceInfo objects discovered on
-- the specified host (and port, if specified). -- the specified host (and port, if specified).
-- --
-- This table is the NSE registry table itself, not a copy, so do not alter
-- it unintentionally.
--
-- @param host A host table for the target host -- @param host A host table for the target host
-- @param port (Optional) If omitted, all of the instances for the host -- @param port (Optional) If omitted, all of the instances for the host
-- will be returned. -- will be returned.
@@ -2570,12 +2594,12 @@ Helper =
nmap.registry.mssql.instances = nmap.registry.mssql.instances or {} nmap.registry.mssql.instances = nmap.registry.mssql.instances or {}
nmap.registry.mssql.instances[ host.ip ] = nmap.registry.mssql.instances[ host.ip ] or {} nmap.registry.mssql.instances[ host.ip ] = nmap.registry.mssql.instances[ host.ip ] or {}
if ( not port ) then
local instances = nmap.registry.mssql.instances[ host.ip ] local instances = nmap.registry.mssql.instances[ host.ip ]
if ( not port ) then
if ( instances and #instances == 0 ) then instances = nil end if ( instances and #instances == 0 ) then instances = nil end
return instances return instances
else else
for _, instance in ipairs( nmap.registry.mssql.instances[ host.ip ] ) do for _, instance in ipairs(instances) do
if ( instance.port and instance.port.number == port.number and if ( instance.port and instance.port.number == port.number and
instance.port.protocol == port.protocol ) then instance.port.protocol == port.protocol ) then
return { instance } return { instance }
@@ -2660,15 +2684,20 @@ Helper =
DiscoverByTcp = function( host, port ) DiscoverByTcp = function( host, port )
local version, instance, status local version, instance, status
-- Check to see if we've already discovered an instance on this port -- Check to see if we've already discovered an instance on this port
instance = Helper.GetDiscoveredInstances( host, port ) local instance = Helper.GetDiscoveredInstances(host, port)
if ( not instance ) then if instance then
return true, {instance}
end
instance = SqlServerInstanceInfo:new() instance = SqlServerInstanceInfo:new()
instance.host = host instance.host = host
instance.port = port instance.port = port
-- -sV may have gotten a version, but for now, it doesn't extract subBuild. -- -sV may have gotten a version, but for now, it doesn't extract subBuild.
status, version = Helper.GetInstanceVersion( instance ) status, version = Helper.GetInstanceVersion( instance )
if ( status ) then if not status then
return false, version
end
Helper.AddOrMergeInstance( instance ) Helper.AddOrMergeInstance( instance )
-- The point of this wasn't to get the version, just to use the -- The point of this wasn't to get the version, just to use the
-- pre-login packet to determine whether there was a SQL Server on -- pre-login packet to determine whether there was a SQL Server on
@@ -2679,10 +2708,8 @@ Helper =
instance.version:PopulateNmapPortVersion( instance.port ) instance.version:PopulateNmapPortVersion( instance.port )
nmap.set_port_version( host, instance.port) nmap.set_port_version( host, instance.port)
end end
end
end
return (instance ~= nil), { instance } return true, { instance }
end, end,
--- Attempts to discover SQL Server instances listening on default named --- Attempts to discover SQL Server instances listening on default named
@@ -2743,48 +2770,37 @@ Helper =
end end
nmap.registry.mssql.discovery_performed[ host.ip ] = false nmap.registry.mssql.discovery_performed[ host.ip ] = false
-- Check all ports that -sV discovered -- First, do SSRP discovery. Check any open (got response) ports first:
-- First SSRP browser ports, then TCP instances local port = nmap.get_ports(host, nil, "udp", "open")
for _, c in ipairs({
{proto="udp", name="ms-sql-m", method="DiscoverBySsrp"},
{proto="tcp", name="ms-sql-s", method="DiscoverByTcp"},
}) do
-- (no need to check open|filtered because -sV marks it as open if it gets a response)
local port = nmap.get_ports(host, nil, c.proto, "open")
while port do while port do
if port.version and port.version.name == c.name then if port.version and port.version.name == "ms-sql-m" then
Helper[c.method]( host, port ) Helper.DiscoverBySsrp(host, port)
end end
port = nmap.get_ports(host, port, c.proto, "open") port = nmap.get_ports(host, port, "udp", "open")
end end
-- Then check if default SSRP port hasn't been done yet.
port = nmap.get_port_state(host, SSRP.PORT)
if not port or port.state == "open|filtered" then
-- Either it wasn't scanned or it wasn't strictly "open" so we missed it above
Helper.DiscoverBySsrp(host, port)
end
-- Next, do TCP discovery. Check any ports with an appropriate service name
port = nmap.get_ports(host, nil, "tcp", "open")
while port do
if port.version and port.version.name == "ms-sql-s" then
Helper.DiscoverByTcp(host, port)
end
port = nmap.get_ports(host, port, "tcp", "open")
end end
local sqlDefaultPort = nmap.get_port_state( host, {number = 1433, protocol = "tcp"} ) or {number = 1433, protocol = "tcp"}
local sqlBrowserPort = nmap.get_port_state( host, {number = 1434, protocol = "udp"} ) or {number = 1434, protocol = "udp"}
local smbPort
-- smb.get_port() will return nil if no SMB port was scanned OR if SMB ports were scanned but none was open -- smb.get_port() will return nil if no SMB port was scanned OR if SMB ports were scanned but none was open
local smbPortNumber = smb.get_port( host ) if smb.get_port(host) then
if ( smbPortNumber ) then Helper.DiscoverBySmb( host )
smbPort = nmap.get_port_state( host, {number = smbPortNumber, protocol = "tcp"} )
-- There's no use in manually setting an SMB port; if no SMB port was
-- scanned and found open, the SMB library won't work
end end
-- if the user has specified ports, we'll check those too
local targetInstancePorts = stdnse.get_script_args( "mssql.instance-port" )
if ( sqlBrowserPort and sqlBrowserPort.state ~= "closed" ) then -- if the user has specified ports, we'll check those too
Helper.DiscoverBySsrp( host, sqlBrowserPort )
end
if ( sqlDefaultPort and sqlDefaultPort.state ~= "closed" ) then
Helper.DiscoverByTcp( host, sqlDefaultPort )
end
if ( smbPort ) then
Helper.DiscoverBySmb( host, smbPort )
end
if ( targetInstancePorts ) then if ( targetInstancePorts ) then
if ( type( targetInstancePorts ) == "string" ) then
targetInstancePorts = { targetInstancePorts }
end
for _, portNumber in ipairs( targetInstancePorts ) do for _, portNumber in ipairs( targetInstancePorts ) do
portNumber = tonumber( portNumber ) portNumber = tonumber( portNumber )
Helper.DiscoverByTcp( host, {number = portNumber, protocol = "tcp"} ) Helper.DiscoverByTcp( host, {number = portNumber, protocol = "tcp"} )
@@ -3142,27 +3158,18 @@ Helper =
-- more SqlServerInstanceInfo objects. If status is false, this will be -- more SqlServerInstanceInfo objects. If status is false, this will be
-- an error message. -- an error message.
GetTargetInstances = function( host, port ) GetTargetInstances = function( host, port )
if ( port ) then -- Perform discovery. This won't do anything if it's already been done.
local status = true -- It's important because otherwise we might miss some ports when not using -sV
local instance = Helper.GetDiscoveredInstances( host, port ) Helper.Discover( host )
if ( not instance ) then if ( port ) then
status, instance = Helper.DiscoverByTcp( host, port ) local status, instances = Helper.GetDiscoveredInstances(host, port)
end if status then
if ( instance ) then return true, instances
return true, instance
else else
return false, "No SQL Server instance detected on this port" return false, "No SQL Server instance detected on this port"
end end
else else
local targetInstanceNames = stdnse.get_script_args( "mssql.instance-name" )
local targetInstancePorts = stdnse.get_script_args( "mssql.instance-port" )
local targetAllInstances = stdnse.get_script_args( "mssql.instance-all" )
if ( targetInstanceNames and targetInstancePorts ) then
return false, "Connections can be made either by instance name or port."
end
if ( targetAllInstances and ( targetInstanceNames or targetInstancePorts ) ) then if ( targetAllInstances and ( targetInstanceNames or targetInstancePorts ) ) then
return false, "All instances cannot be specified together with an instance name or port." return false, "All instances cannot be specified together with an instance name or port."
end end
@@ -3171,51 +3178,43 @@ Helper =
return false, "No instance(s) specified." return false, "No instance(s) specified."
end end
-- Perform discovery. This won't do anything if it's already been done.
Helper.Discover( host )
local instanceList = Helper.GetDiscoveredInstances( host ) local instanceList = Helper.GetDiscoveredInstances( host )
if ( not instanceList ) then if ( not instanceList ) then
return false, "No instances found on target host" return false, "No instances found on target host"
end end
local targetInstances = {} local targetInstances = {}
if ( targetAllInstances ) then
targetInstances = instanceList
else
-- We want an easy way to look up whether an instance's name was
-- in our target list. So, we'll make a table of { instanceName = true, ... }
local temp = {}
if ( targetInstanceNames ) then
if ( type( targetInstanceNames ) == "string" ) then
targetInstanceNames = { targetInstanceNames }
end
for _, instanceName in ipairs( targetInstanceNames ) do
temp[ string.upper( instanceName ) ] = true
end
end
targetInstanceNames = temp
-- Do the same for the target ports
temp = {}
if ( targetInstancePorts ) then
if ( type( targetInstancePorts ) == "string" ) then
targetInstancePorts = { targetInstancePorts }
end
for _, portNumber in ipairs( targetInstancePorts ) do
portNumber = tonumber( portNumber )
temp[portNumber] = true
end
end
targetInstancePorts = temp
for _, instance in ipairs( instanceList ) do for _, instance in ipairs( instanceList ) do
if ( instance.instanceName and targetInstanceNames[ string.upper( instance.instanceName ) ] ) then repeat -- just so we can use break
table.insert( targetInstances, instance ) if instance.port then
elseif ( instance.port and targetInstancePorts[ tonumber( instance.port.number ) ] ) then local scanport = nmap.get_port_state(host, instance.port)
table.insert( targetInstances, instance ) -- If scanned-ports-only and it's on a non-scanned port
if (SCANNED_PORTS_ONLY and not scanport)
-- or if a portrule script will run on it
or (scanport and scanport.state == "open") then
break -- not interested
end
-- If they want everything
if targetAllInstances or
-- or if it's in the instance-port arg
(targetInstancePorts and
tableaux.contains(targetInstancePorts, instance.port.number)) then
-- keep it and move on
targetInstances[#targetInstances+1] = instance
break
end end
end end
-- If they want everything
if targetAllInstances or
-- or if it's in the instance-name arg
(instance.instanceName and targetInstanceNames and
tableaux.contains(targetInstanceNames, string.upper(instance.instanceName))) then
--keep it and move on
targetInstances[#targetInstances+1] = instance
break
end
until false
end end
if ( #targetInstances > 0 ) then if ( #targetInstances > 0 ) then
@@ -3232,14 +3231,17 @@ Helper =
-- the database when normal connection attempts fail, for example, when -- the database when normal connection attempts fail, for example, when
-- the server is hanging, out of memory or other bad states. -- the server is hanging, out of memory or other bad states.
-- --
-- @param host Host table as received by the script action function -- @param instance the <code>SqlServerInstanceInfo</code> object to probe for a DAC port
-- @param instanceName the instance name to probe for a DAC port
-- @return number containing the DAC port on success or nil on failure -- @return number containing the DAC port on success or nil on failure
DiscoverDACPort = function(host, instanceName) DiscoverDACPort = function(instance)
local socket = nmap.new_socket() local instanceName = instance.instanceName or instance.pipeName
if not instanceName then
return nil
end
local socket = nmap.new_socket("udp")
socket:set_timeout(5000) socket:set_timeout(5000)
if ( not(socket:connect(host, 1434, "udp")) ) then if ( not(socket:connect(instance.host, 1434, "udp")) ) then
return false, "Failed to connect to sqlbrowser service" return false, "Failed to connect to sqlbrowser service"
end end
@@ -3249,11 +3251,10 @@ Helper =
end end
local status, data = socket:receive_buf(match.numbytes(6), true) local status, data = socket:receive_buf(match.numbytes(6), true)
if ( not(status) ) then
socket:close() socket:close()
if ( not(status) ) then
return nil return nil
end end
socket:close()
if ( #data < 6 ) then if ( #data < 6 ) then
return nil return nil
@@ -3261,42 +3262,47 @@ Helper =
return string.unpack("<I2", data, 5) return string.unpack("<I2", data, 5)
end, end,
--- Returns a hostrule for standard SQL Server scripts, which will return --- Returns an action, portrule, and hostrule for standard SQL Server scripts
-- true if one or more instances have been targeted with the <code>mssql.instance</code>
-- script argument.
-- --
-- However, if a previous script has failed to find any -- The action function performs discovery if necessary and dispatches the
-- SQL Server instances on the host, the hostrule function will return -- process_instance function on all discovered instances.
-- false to keep further scripts from running unnecessarily on that host.
-- --
-- @return A hostrule function (use as <code>hostrule = mssql.GetHostrule_Standard()</code>) -- The portrule returns true if the port has been identified as "ms-sql-s" or
GetHostrule_Standard = function() -- discovery has found an instance on that port.
return function( host ) --
if ( stdnse.get_script_args( {"mssql.instance-all", "mssql.instance-name", "mssql.instance-port"} ) ~= nil ) then -- The hostrule returns true if any of the <code>mssql.instance-*</code>
if ( Helper.WasDiscoveryPerformed( host ) ) then -- script-args has been set and either a matching instance exists or
return Helper.GetDiscoveredInstances( host ) ~= nil -- discovery has not yet been done.
else -- @usage action, portrule, hostrule = mssql.Helper.InitScript(do_something)
return true --
-- @param process_instance A function that takes a single parameter, a
-- <code>SqlServerInstanceInfo</code> object, and
-- returns output suitable for an action function to
-- return.
--
-- @return An action function
-- @return A portrule function
-- @return A hostrule function
InitScript = function(process_instance)
local action = function(host, port)
local status, instances = Helper.GetTargetInstances(host, port)
if not status then
stdnse.debug1("GetTargetInstances: %s", instances)
return nil
end end
else local output = {}
return false for _, instance in ipairs(instances) do
output[instance:GetName()] = process_instance(instance)
end end
if #output > 0 then
return outlib.sorted_by_key(output)
end
return nil
end end
end,
-- GetTargetInstances does the right thing depending on whether port is
--- Returns a portrule for standard SQL Server scripts -- provided, which corresponds to portrule vs hostrule.
-- return action, Helper.GetTargetInstances, Helper.GetTargetInstances
-- The portrule return true if BOTH of the following conditions are met:
-- * The port has been identified as "ms-sql-s"
-- * The <code>mssql.instance</code> script argument has NOT been used
--
-- @return A portrule function (use as <code>portrule = mssql.GetPortrule_Standard()</code>)
GetPortrule_Standard = function()
return function( host, port )
return ( shortport.service( "ms-sql-s" )(host, port) and
stdnse.get_script_args( {"mssql.instance-all", "mssql.instance-name", "mssql.instance-port"} ) == nil)
end
end, end,
} }
@@ -3371,11 +3377,11 @@ Auth = {
Util = Util =
{ {
--- Takes a table as returned by Query and does some fancy formatting --- Takes a table as returned by Query and does some fancy formatting
-- better suitable for <code>stdnse.output_result</code> -- better suitable for <code>stdnse.format_output</code>
-- --
-- @param tbl as received by <code>Helper.Query</code> -- @param tbl as received by <code>Helper.Query</code>
-- @param with_headers boolean true if output should contain column headers -- @param with_headers boolean true if output should contain column headers
-- @return table suitable for <code>stdnse.output_result</code> -- @return table suitable for <code>stdnse.format_output</code>
FormatOutputTable = function ( tbl, with_headers ) FormatOutputTable = function ( tbl, with_headers )
local new_tbl = {} local new_tbl = {}
local col_names = {} local col_names = {}

View File

@@ -77,13 +77,7 @@ author = "Patrik Karlsson"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html" license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"brute", "intrusive"} categories = {"brute", "intrusive"}
dependencies = {"ms-sql-empty-password"} dependencies = {"broadcast-ms-sql-discover", "ms-sql-empty-password"}
hostrule = mssql.Helper.GetHostrule_Standard()
portrule = mssql.Helper.GetPortrule_Standard()
--- Returns formatted output for the given instance --- Returns formatted output for the given instance
local function create_instance_output_table( instance ) local function create_instance_output_table( instance )
@@ -132,7 +126,7 @@ local function create_instance_output_table( instance )
end end
end end
return instanceOutput return stdnse.format_output(true, instanceOutput)
end end
@@ -199,7 +193,7 @@ local function test_credentials( instance, helper, username, password )
end end
--- Processes a single instance, attempting to detect an empty password for "sa" --- Processes a single instance, attempting to detect an empty password for "sa"
local function process_instance( instance ) process_instance = function ( instance )
-- One of this script's features is that it will report an instance's -- One of this script's features is that it will report an instance's
-- in both the port-script results and the host-script results. In order to -- in both the port-script results and the host-script results. In order to
@@ -275,10 +269,10 @@ local function process_instance( instance )
end end
local do_action
do_action, portrule, hostrule = mssql.Helper.InitScript(process_instance)
action = function( host, port ) action = function(...)
local scriptOutput = {}
local status, instanceList = mssql.Helper.GetTargetInstances( host, port )
local domain, bruteWindows = stdnse.get_script_args("mssql.domain", "ms-sql-brute.brute-windows-accounts") local domain, bruteWindows = stdnse.get_script_args("mssql.domain", "ms-sql-brute.brute-windows-accounts")
@@ -292,16 +286,5 @@ action = function( host, port )
return ret return ret
end end
if ( not status ) then return do_action(...)
return stdnse.format_output( false, instanceList )
else
for _, instance in pairs( instanceList ) do
local instanceOutput = process_instance( instance )
if instanceOutput then
table.insert( scriptOutput, instanceOutput )
end
end
end
return stdnse.format_output( true, scriptOutput )
end end

View File

@@ -70,12 +70,7 @@ license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"discovery", "safe"} categories = {"discovery", "safe"}
dependencies = {"ms-sql-brute", "ms-sql-empty-password"} dependencies = {"broadcast-ms-sql-discover", "ms-sql-brute", "ms-sql-empty-password"}
hostrule = mssql.Helper.GetHostrule_Standard()
portrule = mssql.Helper.GetPortrule_Standard()
--- Processes a set of instances --- Processes a set of instances
local function process_instance( instance ) local function process_instance( instance )
@@ -130,28 +125,8 @@ local function process_instance( instance )
helper:Disconnect() helper:Disconnect()
local instanceOutput = {} -- TODO: structured output instead of format_output
instanceOutput["name"] = string.format( "[%s]", instance:GetName() ) return stdnse.format_output(true, result)
table.insert( instanceOutput, result )
return instanceOutput
end end
action, portrule, hostrule = mssql.Helper.InitScript(process_instance)
action = function( host, port )
local scriptOutput = {}
local status, instanceList = mssql.Helper.GetTargetInstances( host, port )
if ( not status ) then
return stdnse.format_output( false, instanceList )
else
for _, instance in pairs( instanceList ) do
local instanceOutput = process_instance( instance )
if instanceOutput then
table.insert( scriptOutput, instanceOutput )
end
end
end
return stdnse.format_output( true, scriptOutput )
end

View File

@@ -1,8 +1,6 @@
local coroutine = require "coroutine"
local mssql = require "mssql" local mssql = require "mssql"
local nmap = require "nmap" local nmap = require "nmap"
local stdnse = require "stdnse" local stdnse = require "stdnse"
local table = require "table"
description = [[ description = [[
Queries the Microsoft SQL Browser service for the DAC (Dedicated Admin Queries the Microsoft SQL Browser service for the DAC (Dedicated Admin
@@ -28,75 +26,60 @@ accessible or not.
-- --
-- @output -- @output
-- | ms-sql-dac: -- | ms-sql-dac:
-- |_ Instance: SQLSERVER; DAC port: 1533 -- | SQLSERVER:
-- | port: 1533
-- |_ state: open
-- --
author = "Patrik Karlsson" author = "Patrik Karlsson"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html" license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"discovery", "safe"} categories = {"discovery", "safe"}
hostrule = function(host) dependencies = {"broadcast-ms-sql-discover"}
if ( mssql.Helper.WasDiscoveryPerformed( host ) ) then
return mssql.Helper.GetDiscoveredInstances( host ) ~= nil
else
local sqlBrowserPort = nmap.get_port_state( host, {number = 1434, protocol = "udp"} )
if ( (stdnse.get_script_args( {"mssql.instance-all", "mssql.instance-name", "mssql.instance-port"} ) ~= nil) or
(sqlBrowserPort and (sqlBrowserPort.state == "open" or sqlBrowserPort.state == "open|filtered")) ) then
return true
end
end
end
local function checkPort(host, port) local function checkPort(host, port)
local scanport = nmap.get_port_state(host, {number=port, protocol="tcp"})
if scanport then
return scanport.state
end
local s = nmap.new_socket() local s = nmap.new_socket()
s:set_timeout(5000) s:set_timeout(5000)
local status = s:connect(host, port, "tcp") local status, err = s:connect(host, port, "tcp")
s:close() s:close()
return status return (status and "open" or "closed"), err
end end
local function discoverDAC(host, name, result) local function discoverDAC(instance)
local condvar = nmap.condvar(result) stdnse.debug2("Discovering DAC port on instance: %s", instance:GetName())
stdnse.debug2("Discovering DAC port on instance: %s", name) local port = mssql.Helper.DiscoverDACPort(instance)
local port = mssql.Helper.DiscoverDACPort( host, name ) if not port then
if ( port ) then return nil
if ( checkPort(host, port) ) then
table.insert(result, ("Instance: %s; DAC port: %s"):format(name, port))
else
table.insert(result, ("Instance: %s; DAC port: %s (connection failed)"):format(name, port))
end
end
condvar "signal"
end end
action = function( host ) local result = stdnse.output_table()
local result, threads = {}, {} result.port = port
local condvar = nmap.condvar(result) local state, err = checkPort(instance.host, port)
result.state = state
local status, instanceList = mssql.Helper.GetTargetInstances( host ) result.error = err
-- if no instances were targeted, then display info on all return result
if ( not status ) then
mssql.Helper.Discover( host )
instanceList = mssql.Helper.GetDiscoveredInstances( host )
end end
for _, instance in ipairs(instanceList or {}) do local lib_portrule, lib_hostrule
local name = instance:GetName():match("^[^\\]*\\(.*)$") action, lib_portrule, lib_hostrule = mssql.Helper.InitScript(discoverDAC)
if ( name ) then
local co = stdnse.new_thread(discoverDAC, host, name, result) local function rule_if_browser_open(lib_rule)
threads[co] = true return function (host, ...)
if not lib_rule(host, ...) then
return false
end
local bport = nmap.get_port_state(host, {number=1434, protocol="udp"})
-- If port is nil, we don't know the state
return bport == nil or (
-- we know the state, so it has to be a good one
bport.state == "open" or bport.state == "open|filtered"
)
end end
end end
while(next(threads)) do portrule = rule_if_browser_open(lib_portrule)
for t in pairs(threads) do hostrule = rule_if_browser_open(lib_hostrule)
threads[t] = ( coroutine.status(t) ~= "dead" ) and true or nil
end
if ( next(threads) ) then
condvar "wait"
end
end
return stdnse.format_output( true, result )
end

View File

@@ -40,29 +40,19 @@ author = "Patrik Karlsson"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html" license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"auth", "discovery", "safe"} categories = {"auth", "discovery", "safe"}
dependencies = {"broadcast-ms-sql-discover", "ms-sql-brute", "ms-sql-empty-password"}
dependencies = {"ms-sql-brute", "ms-sql-empty-password"}
hostrule = mssql.Helper.GetHostrule_Standard()
portrule = mssql.Helper.GetPortrule_Standard()
local function process_instance(instance) local function process_instance(instance)
local helper = mssql.Helper:new() local helper = mssql.Helper:new()
local status, errorMessage = helper:ConnectEx( instance ) local status, errorMessage = helper:ConnectEx( instance )
if ( not(status) ) then if ( not(status) ) then
return false, { return "ERROR: " .. errorMessage
['name'] = string.format( "[%s]", instance:GetName() ),
"ERROR: " .. errorMessage
}
end end
status, errorMessage = helper:LoginEx( instance ) status, errorMessage = helper:LoginEx( instance )
if ( not(status) ) then if ( not(status) ) then
return false, { return "ERROR: " .. errorMessage
['name'] = string.format( "[%s]", instance:GetName() ),
"ERROR: " .. errorMessage
}
end end
local result local result
@@ -83,12 +73,7 @@ local function process_instance(instance)
end end
helper:Disconnect() helper:Disconnect()
local instanceOutput = {} return output
instanceOutput["name"] = string.format( "[%s]", instance:GetName() )
table.insert( instanceOutput, output )
return true, instanceOutput
end end
-- Saves the hashes to file -- Saves the hashes to file
@@ -110,31 +95,19 @@ local function saveToFile(filename, response)
return true return true
end end
action = function( host, port )
local dir = stdnse.get_script_args("ms-sql-dump-hashes.dir") local dir = stdnse.get_script_args("ms-sql-dump-hashes.dir")
local scriptOutput = {}
local status, instanceList = mssql.Helper.GetTargetInstances( host, port )
if ( not status ) then local function process_and_save (instance)
return stdnse.format_output( false, instanceList ) local instanceOutput = process_instance( instance )
else if type(instanceOutput) == "table" then
for _, instance in pairs( instanceList ) do local inst = instance:GetName():gsub(".*\\", "")
local status, instanceOutput = process_instance( instance ) local filename = dir .. "/" .. stringaux.filename_escape(
if ( status ) then ("%s_%s_ms-sql_hashes.txt"):format(instance.host.ip, inst))
local filename saveToFile(filename, instanceOutput)
if ( dir ) then
local instance = instance:GetName():match("%\\+(.+)$") or instance:GetName()
filename = dir .. "/" .. stringaux.filename_escape(("%s_%s_ms-sql_hashes.txt"):format(host.ip, instance))
saveToFile(filename, instanceOutput[1])
end
end
table.insert( scriptOutput, instanceOutput )
end end
return instanceOutput
end end
if ( #scriptOutput == 0 ) then return end local do_instance = dir and process_and_save or process_instance
local output = ( #scriptOutput > 1 and scriptOutput or scriptOutput[1] ) action, portrule, hostrule = mssql.Helper.InitScript(do_instance)
return stdnse.format_output( true, output )
end

View File

@@ -59,9 +59,7 @@ author = "Patrik Karlsson"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html" license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"auth","intrusive"} categories = {"auth","intrusive"}
dependencies = {"broadcast-ms-sql-discover"}
hostrule = mssql.Helper.GetHostrule_Standard()
portrule = mssql.Helper.GetPortrule_Standard()
local function test_credentials( instance, helper, username, password ) local function test_credentials( instance, helper, username, password )
local database = "tempdb" local database = "tempdb"
@@ -150,7 +148,6 @@ local function process_instance( instance )
local instanceOutput local instanceOutput
if ( instance.ms_sql_empty ) then if ( instance.ms_sql_empty ) then
instanceOutput = {} instanceOutput = {}
instanceOutput["name"] = string.format( "[%s]", instance:GetName() )
for _, message in ipairs( instance.ms_sql_empty ) do for _, message in ipairs( instance.ms_sql_empty ) do
table.insert( instanceOutput, message ) table.insert( instanceOutput, message )
end end
@@ -163,21 +160,4 @@ local function process_instance( instance )
end end
action, portrule, hostrule = mssql.Helper.InitScript(process_instance)
action = function( host, port )
local scriptOutput = {}
local status, instanceList = mssql.Helper.GetTargetInstances( host, port )
if ( not status ) then
return stdnse.format_output( false, instanceList )
else
for _, instance in pairs( instanceList ) do
local instanceOutput = process_instance( instance )
if instanceOutput then
table.insert( scriptOutput, instanceOutput )
end
end
end
return stdnse.format_output( true, scriptOutput )
end

View File

@@ -73,11 +73,7 @@ license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"auth", "discovery","safe"} categories = {"auth", "discovery","safe"}
dependencies = {"ms-sql-brute", "ms-sql-empty-password"} dependencies = {"broadcast-ms-sql-discover", "ms-sql-brute", "ms-sql-empty-password"}
hostrule = mssql.Helper.GetHostrule_Standard()
portrule = mssql.Helper.GetPortrule_Standard()
local function process_instance( instance ) local function process_instance( instance )
@@ -146,30 +142,9 @@ local function process_instance( instance )
end end
end end
-- TODO: structured output, not format_output
local instanceOutput = {} return stdnse.format_output(true, output)
instanceOutput["name"] = string.format( "[%s]", instance:GetName() )
table.insert( instanceOutput, output )
return instanceOutput
end end
action = function( host, port ) action, portrule, hostrule = mssql.Helper.InitScript(process_instance)
local scriptOutput = {}
local status, instanceList = mssql.Helper.GetTargetInstances( host, port )
if ( not status ) then
return stdnse.format_output( false, instanceList )
else
for _, instance in pairs( instanceList ) do
local instanceOutput = process_instance( instance )
if instanceOutput then
table.insert( scriptOutput, instanceOutput )
end
end
end
return stdnse.format_output( true, scriptOutput )
end

View File

@@ -150,25 +150,7 @@ license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"default", "discovery", "safe"} categories = {"default", "discovery", "safe"}
dependencies = {"broadcast-ms-sql-discover"}
hostrule = function(host)
if ( mssql.Helper.WasDiscoveryPerformed( host ) ) then
return mssql.Helper.GetDiscoveredInstances( host ) ~= nil
else
local sqlDefaultPort = nmap.get_port_state( host, {number = 1433, protocol = "tcp"} )
local sqlBrowserPort = nmap.get_port_state( host, {number = 1434, protocol = "udp"} )
-- smb.get_port() will return nil if no SMB port was scanned OR if SMB ports were scanned but none was open
local smbPortNumber = smb.get_port( host )
if ( (stdnse.get_script_args( {"mssql.instance-all", "mssql.instance-name", "mssql.instance-port"} ) ~= nil) or
(sqlBrowserPort and (sqlBrowserPort.state == "open" or sqlBrowserPort.state == "open|filtered")) or
(sqlDefaultPort and (sqlDefaultPort.state == "open" or sqlDefaultPort.state == "open|filtered")) or
(smbPortNumber ~= nil) ) then
return true
end
end
end
--- Returns formatted output for the given version data --- Returns formatted output for the given version data
local function create_version_output_table( versionInfo ) local function create_version_output_table( versionInfo )
@@ -247,33 +229,9 @@ local function process_instance( instance )
end end
local function do_instance (instance)
action = function( host )
local scriptOutput = stdnse.output_table()
local status, instanceList = mssql.Helper.GetTargetInstances( host )
-- if no instances were targeted, then display info on all
if ( not status ) then
mssql.Helper.Discover( host )
instanceList = mssql.Helper.GetDiscoveredInstances( host )
end
if ( not instanceList ) then
return stdnse.format_output( false, instanceList or "" )
else
for _, instance in ipairs( instanceList ) do
if instance.serverName then
scriptOutput["Windows server name"] = instance.serverName
break
end
end
for _, instance in pairs( instanceList ) do
process_instance( instance ) process_instance( instance )
scriptOutput[instance:GetName()] = create_instance_output_table( instance ) return create_instance_output_table( instance )
end
end
return scriptOutput
end end
action, portrule, hostrule = mssql.Helper.InitScript(do_instance)

View File

@@ -1,7 +1,6 @@
local os = require "os" local os = require "os"
local datetime = require "datetime" local datetime = require "datetime"
local mssql = require "mssql" local mssql = require "mssql"
local shortport = require "shortport"
local stdnse = require "stdnse" local stdnse = require "stdnse"
local smbauth = require "smbauth" local smbauth = require "smbauth"
local string = require "string" local string = require "string"
@@ -46,9 +45,9 @@ author = "Justin Cacak"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html" license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"default", "discovery", "safe"} categories = {"default", "discovery", "safe"}
portrule = shortport.port_or_service(1433, "ms-sql-s") dependencies = {"broadcast-ms-sql-discover"}
action = function(host, port) local do_action = function(host, port)
local output = stdnse.output_table() local output = stdnse.output_table()
@@ -127,3 +126,9 @@ action = function(host, port)
return output return output
end end
local function process_instance(instance)
return do_action(instance.host, instance.port)
end
action, portrule = mssql.Helper.InitScript(process_instance)

View File

@@ -60,10 +60,7 @@ license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"discovery", "safe"} categories = {"discovery", "safe"}
dependencies = {"ms-sql-brute", "ms-sql-empty-password"} dependencies = {"broadcast-ms-sql-discover", "ms-sql-brute", "ms-sql-empty-password"}
hostrule = mssql.Helper.GetHostrule_Standard()
portrule = mssql.Helper.GetPortrule_Standard()
--- ---
local function process_instance( instance ) local function process_instance( instance )
@@ -92,31 +89,19 @@ local function process_instance( instance )
result = mssql.Util.FormatOutputTable( result, true ) result = mssql.Util.FormatOutputTable( result, true )
result["name"] = string.format( "Query: %s", query ) result["name"] = string.format( "Query: %s", query )
end end
local instanceOutput = {}
instanceOutput["name"] = string.format( "[%s]", instance:GetName() )
table.insert( instanceOutput, result )
return instanceOutput return result
end end
local do_action
do_action, portrule, hostrule = mssql.Helper.InitScript(process_instance)
action = function( host, port ) action = function(...)
local scriptOutput = {} local scriptOutput = do_action(...)
local status, instanceList = mssql.Helper.GetTargetInstances( host, port )
if ( not status ) then
return stdnse.format_output( false, instanceList )
else
for _, instance in pairs( instanceList ) do
local instanceOutput = process_instance( instance )
if instanceOutput then
table.insert( scriptOutput, instanceOutput )
end
end
if ( not( stdnse.get_script_args( {'ms-sql-query.query', 'mssql-query.query' } ) ) ) then if ( not( stdnse.get_script_args( {'ms-sql-query.query', 'mssql-query.query' } ) ) ) then
table.insert(scriptOutput, 1, "(Use --script-args=ms-sql-query.query='<QUERY>' to change query.)") table.insert(scriptOutput, 1, "(Use --script-args=ms-sql-query.query='<QUERY>' to change query.)")
end end
end
return stdnse.format_output( true, scriptOutput ) return stdnse.format_output( true, scriptOutput )
end end

View File

@@ -98,10 +98,7 @@ license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"discovery", "safe"} categories = {"discovery", "safe"}
dependencies = {"ms-sql-brute", "ms-sql-empty-password"} dependencies = {"broadcast-ms-sql-discover", "ms-sql-brute", "ms-sql-empty-password"}
hostrule = mssql.Helper.GetHostrule_Standard()
portrule = mssql.Helper.GetPortrule_Standard()
local function process_instance( instance ) local function process_instance( instance )
@@ -248,25 +245,9 @@ local function process_instance( instance )
instanceOutput["name"] = string.format( "[%s]", instance:GetName() ) instanceOutput["name"] = string.format( "[%s]", instance:GetName() )
table.insert( instanceOutput, output ) table.insert( instanceOutput, output )
return instanceOutput return stdnse.format_ouptut(true, instanceOutput)
end end
action = function( host, port ) action, portrule, hostrule = mssql.Helper.InitScript(process_instance)
local scriptOutput = {}
local status, instanceList = mssql.Helper.GetTargetInstances( host, port )
if ( not status ) then
return stdnse.format_output( false, instanceList )
else
for _, instance in pairs( instanceList ) do
local instanceOutput = process_instance( instance )
if instanceOutput then
table.insert( scriptOutput, instanceOutput )
end
end
end
return stdnse.format_output( true, scriptOutput )
end

View File

@@ -86,10 +86,7 @@ license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"intrusive"} categories = {"intrusive"}
dependencies = {"ms-sql-brute", "ms-sql-empty-password"} dependencies = {"broadcast-ms-sql-discover", "ms-sql-brute", "ms-sql-empty-password"}
hostrule = mssql.Helper.GetHostrule_Standard()
portrule = mssql.Helper.GetPortrule_Standard()
local function process_instance( instance ) local function process_instance( instance )
@@ -143,24 +140,14 @@ local function process_instance( instance )
end end
action = function( host, port ) local do_action
local scriptOutput = {} do_action, portrule, hostrule = mssql.Helper.InitScript(process_instance)
local status, instanceList = mssql.Helper.GetTargetInstances( host, port )
if ( not status ) then
return stdnse.format_output( false, instanceList )
else
for _, instance in pairs( instanceList ) do
local instanceOutput = process_instance( instance )
if instanceOutput then
table.insert( scriptOutput, instanceOutput )
end
end
action = function(...)
local scriptOutput = do_action(...)
if ( not(stdnse.get_script_args( {'ms-sql-xp-cmdshell.cmd', 'mssql-xp-cmdshell.cmd'} ) ) ) then if ( not(stdnse.get_script_args( {'ms-sql-xp-cmdshell.cmd', 'mssql-xp-cmdshell.cmd'} ) ) ) then
table.insert(scriptOutput, 1, "(Use --script-args=ms-sql-xp-cmdshell.cmd='<CMD>' to change command.)") table.insert(scriptOutput, 1, "(Use --script-args=ms-sql-xp-cmdshell.cmd='<CMD>' to change command.)")
end end
end
return stdnse.format_output( true, scriptOutput ) return stdnse.format_output( true, scriptOutput )
end end