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

@@ -77,13 +77,7 @@ author = "Patrik Karlsson"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"brute", "intrusive"}
dependencies = {"ms-sql-empty-password"}
hostrule = mssql.Helper.GetHostrule_Standard()
portrule = mssql.Helper.GetPortrule_Standard()
dependencies = {"broadcast-ms-sql-discover", "ms-sql-empty-password"}
--- Returns formatted output for the given instance
local function create_instance_output_table( instance )
@@ -132,7 +126,7 @@ local function create_instance_output_table( instance )
end
end
return instanceOutput
return stdnse.format_output(true, instanceOutput)
end
@@ -199,7 +193,7 @@ local function test_credentials( instance, helper, username, password )
end
--- 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
-- in both the port-script results and the host-script results. In order to
@@ -275,10 +269,10 @@ local function process_instance( instance )
end
local do_action
do_action, portrule, hostrule = mssql.Helper.InitScript(process_instance)
action = function( host, port )
local scriptOutput = {}
local status, instanceList = mssql.Helper.GetTargetInstances( host, port )
action = function(...)
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
end
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 )
return do_action(...)
end

View File

@@ -70,12 +70,7 @@ license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"discovery", "safe"}
dependencies = {"ms-sql-brute", "ms-sql-empty-password"}
hostrule = mssql.Helper.GetHostrule_Standard()
portrule = mssql.Helper.GetPortrule_Standard()
dependencies = {"broadcast-ms-sql-discover", "ms-sql-brute", "ms-sql-empty-password"}
--- Processes a set of instances
local function process_instance( instance )
@@ -130,28 +125,8 @@ local function process_instance( instance )
helper:Disconnect()
local instanceOutput = {}
instanceOutput["name"] = string.format( "[%s]", instance:GetName() )
table.insert( instanceOutput, result )
return instanceOutput
-- TODO: structured output instead of format_output
return stdnse.format_output(true, result)
end
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
action, portrule, hostrule = mssql.Helper.InitScript(process_instance)

View File

@@ -1,8 +1,6 @@
local coroutine = require "coroutine"
local mssql = require "mssql"
local nmap = require "nmap"
local stdnse = require "stdnse"
local table = require "table"
description = [[
Queries the Microsoft SQL Browser service for the DAC (Dedicated Admin
@@ -28,75 +26,60 @@ accessible or not.
--
-- @output
-- | ms-sql-dac:
-- |_ Instance: SQLSERVER; DAC port: 1533
-- | SQLSERVER:
-- | port: 1533
-- |_ state: open
--
author = "Patrik Karlsson"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"discovery", "safe"}
hostrule = function(host)
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
dependencies = {"broadcast-ms-sql-discover"}
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()
s:set_timeout(5000)
local status = s:connect(host, port, "tcp")
local status, err = s:connect(host, port, "tcp")
s:close()
return status
return (status and "open" or "closed"), err
end
local function discoverDAC(host, name, result)
local condvar = nmap.condvar(result)
stdnse.debug2("Discovering DAC port on instance: %s", name)
local port = mssql.Helper.DiscoverDACPort( host, name )
if ( port ) then
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
local function discoverDAC(instance)
stdnse.debug2("Discovering DAC port on instance: %s", instance:GetName())
local port = mssql.Helper.DiscoverDACPort(instance)
if not port then
return nil
end
condvar "signal"
local result = stdnse.output_table()
result.port = port
local state, err = checkPort(instance.host, port)
result.state = state
result.error = err
return result
end
action = function( host )
local result, threads = {}, {}
local condvar = nmap.condvar(result)
local lib_portrule, lib_hostrule
action, lib_portrule, lib_hostrule = mssql.Helper.InitScript(discoverDAC)
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
for _, instance in ipairs(instanceList or {}) do
local name = instance:GetName():match("^[^\\]*\\(.*)$")
if ( name ) then
local co = stdnse.new_thread(discoverDAC, host, name, result)
threads[co] = true
local function rule_if_browser_open(lib_rule)
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
while(next(threads)) do
for t in pairs(threads) do
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
portrule = rule_if_browser_open(lib_portrule)
hostrule = rule_if_browser_open(lib_hostrule)

View File

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

View File

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

View File

@@ -73,11 +73,7 @@ license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"auth", "discovery","safe"}
dependencies = {"ms-sql-brute", "ms-sql-empty-password"}
hostrule = mssql.Helper.GetHostrule_Standard()
portrule = mssql.Helper.GetPortrule_Standard()
dependencies = {"broadcast-ms-sql-discover", "ms-sql-brute", "ms-sql-empty-password"}
local function process_instance( instance )
@@ -146,30 +142,9 @@ local function process_instance( instance )
end
end
local instanceOutput = {}
instanceOutput["name"] = string.format( "[%s]", instance:GetName() )
table.insert( instanceOutput, output )
return instanceOutput
-- TODO: structured output, not format_output
return stdnse.format_output(true, output)
end
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
action, portrule, hostrule = mssql.Helper.InitScript(process_instance)

View File

@@ -150,25 +150,7 @@ license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"default", "discovery", "safe"}
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
dependencies = {"broadcast-ms-sql-discover"}
--- Returns formatted output for the given version data
local function create_version_output_table( versionInfo )
@@ -247,33 +229,9 @@ local function process_instance( instance )
end
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 )
scriptOutput[instance:GetName()] = create_instance_output_table( instance )
end
end
return scriptOutput
local function do_instance (instance)
process_instance( instance )
return create_instance_output_table( instance )
end
action, portrule, hostrule = mssql.Helper.InitScript(do_instance)

View File

@@ -1,7 +1,6 @@
local os = require "os"
local datetime = require "datetime"
local mssql = require "mssql"
local shortport = require "shortport"
local stdnse = require "stdnse"
local smbauth = require "smbauth"
local string = require "string"
@@ -46,9 +45,9 @@ author = "Justin Cacak"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
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()
@@ -127,3 +126,9 @@ action = function(host, port)
return output
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"}
dependencies = {"ms-sql-brute", "ms-sql-empty-password"}
hostrule = mssql.Helper.GetHostrule_Standard()
portrule = mssql.Helper.GetPortrule_Standard()
dependencies = {"broadcast-ms-sql-discover", "ms-sql-brute", "ms-sql-empty-password"}
---
local function process_instance( instance )
@@ -92,30 +89,18 @@ local function process_instance( instance )
result = mssql.Util.FormatOutputTable( result, true )
result["name"] = string.format( "Query: %s", query )
end
local instanceOutput = {}
instanceOutput["name"] = string.format( "[%s]", instance:GetName() )
table.insert( instanceOutput, result )
return instanceOutput
return result
end
local do_action
do_action, portrule, hostrule = mssql.Helper.InitScript(process_instance)
action = function( host, port )
local scriptOutput = {}
local status, instanceList = mssql.Helper.GetTargetInstances( host, port )
action = function(...)
local scriptOutput = do_action(...)
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
table.insert(scriptOutput, 1, "(Use --script-args=ms-sql-query.query='<QUERY>' to change query.)")
end
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.)")
end
return stdnse.format_output( true, scriptOutput )

View File

@@ -98,10 +98,7 @@ license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"discovery", "safe"}
dependencies = {"ms-sql-brute", "ms-sql-empty-password"}
hostrule = mssql.Helper.GetHostrule_Standard()
portrule = mssql.Helper.GetPortrule_Standard()
dependencies = {"broadcast-ms-sql-discover", "ms-sql-brute", "ms-sql-empty-password"}
local function process_instance( instance )
@@ -248,25 +245,9 @@ local function process_instance( instance )
instanceOutput["name"] = string.format( "[%s]", instance:GetName() )
table.insert( instanceOutput, output )
return instanceOutput
return stdnse.format_ouptut(true, instanceOutput)
end
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
action, portrule, hostrule = mssql.Helper.InitScript(process_instance)

View File

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