1
0
mirror of https://github.com/nmap/nmap.git synced 2025-12-09 22:21:29 +00:00

o [NSE] Merged the ms-sql branch with several improvements and changes to the

ms-sql scripts and library:
  - Improved version detection
  - Improved server discovery
  - Add support for named pipes
  - Add support for integrated authentication
  - Add support for connecting to instances by name or port
  - Improved script and library stability
  - Improved script and library documentation
 [Patrik Karlsson, Chris Woodbury]
This commit is contained in:
patrik
2011-02-26 22:41:10 +00:00
parent d6bbc6da8f
commit 58edddaedb
13 changed files with 3534 additions and 979 deletions

View File

@@ -1,5 +1,16 @@
# Nmap Changelog ($Id$); -*-text-*- # Nmap Changelog ($Id$); -*-text-*-
o [NSE] Merged the ms-sql branch with several improvements and changes to the
ms-sql scripts and library:
- Improved version detection
- Improved server discovery
- Add support for named pipes
- Add support for integrated authentication
- Add support for connecting to instances by name or port
- Improved script and library stability
- Improved script and library documentation
[Patrik Karlsson, Chris Woodbury]
o [NSE] Added probe for Apple iPhoto (DPAP) and the dpap-brute script that o [NSE] Added probe for Apple iPhoto (DPAP) and the dpap-brute script that
performs password guessing against a shared iPhoto library. [Patrik] performs password guessing against a shared iPhoto library. [Patrik]

File diff suppressed because it is too large Load Diff

View File

@@ -131,6 +131,8 @@ command_codes = {}
command_names = {} command_names = {}
status_codes = {} status_codes = {}
status_names = {} status_names = {}
filetype_codes = {}
filetype_names = {}
local TIMEOUT = 10000 local TIMEOUT = 10000
@@ -1350,9 +1352,9 @@ local function start_session_extended(smb, log_errors, overrides)
-- Check if they were logged in as a guest -- Check if they were logged in as a guest
if(log_errors == nil or log_errors == true) then if(log_errors == nil or log_errors == true) then
if(smb['is_guest'] == 1) then if(smb['is_guest'] == 1) then
stdnse.print_debug(1, string.format("SMB: Extended login as %s\\%s failed, but was given guest access (username may be wrong, or system may only allow guest)", domain, stdnse.string_or_blank(username))) stdnse.print_debug(1, string.format("SMB: Extended login to %s as %s\\%s failed, but was given guest access (username may be wrong, or system may only allow guest)", smb['ip'], domain, stdnse.string_or_blank(username)))
else else
stdnse.print_debug(2, string.format("SMB: Extended login as %s\\%s succeeded", domain, stdnse.string_or_blank(username))) stdnse.print_debug(2, string.format("SMB: Extended login to %s as %s\\%s succeeded", smb['ip'], domain, stdnse.string_or_blank(username)))
end end
end end
@@ -1378,7 +1380,7 @@ local function start_session_extended(smb, log_errors, overrides)
else else
-- Display a message to the user, and try the next account -- Display a message to the user, and try the next account
if(log_errors == nil or log_errors == true) then if(log_errors == nil or log_errors == true) then
stdnse.print_debug(1, "SMB: Extended login as %s\\%s failed (%s)", domain, stdnse.string_or_blank(username), status_name) stdnse.print_debug(1, "SMB: Extended login to %s as %s\\%s failed (%s)", smb['ip'], domain, stdnse.string_or_blank(username), status_name)
end end
-- Go to the next account -- Go to the next account
@@ -1763,7 +1765,9 @@ function read_file(smb, offset, count, overrides)
if(header1 == nil or mid == nil) then if(header1 == nil or mid == nil) then
return false, "SMB: ERROR: Server returned less data than it was supposed to (one or more fields are missing); aborting [25]" return false, "SMB: ERROR: Server returned less data than it was supposed to (one or more fields are missing); aborting [25]"
end end
if(status ~= 0) then if(status ~= 0 and
(status ~= status_codes.NT_STATUS_BUFFER_OVERFLOW and (smb['filetype'] == filetype_codes.FILE_TYPE_BYTE_MODE_PIPE or
smb['filetype'] == filetype_codes.FILE_TYPE_MESSAGE_MODE_PIPE) ) ) then
return false, get_status_name(status) return false, get_status_name(status)
end end
@@ -1775,6 +1779,7 @@ function read_file(smb, offset, count, overrides)
response['remaining'] = remaining response['remaining'] = remaining
response['data_length'] = bit.bor(data_length_low, bit.lshift(data_length_high, 16)) response['data_length'] = bit.bor(data_length_low, bit.lshift(data_length_high, 16))
response['status'] = status
-- data_start is the offset of the beginning of the data section -- we use this to calculate where the read data lives -- data_start is the offset of the beginning of the data section -- we use this to calculate where the read data lives
@@ -3703,3 +3708,174 @@ for i, v in pairs(status_codes) do
status_names[v] = i status_names[v] = i
end end
local NP_LIBRARY_NAME = "PIPE"
namedpipes =
{
get_pipe_subpath = function( pipeName, writeToDebugLog )
local status, pipeSubPath
if not pipeName then return false end
local _, _, match = pipeName:match( "^(\\+)(.-)\\pipe(\\.-)$" )
if match then
pipeSubPath = match
status = true
if writeToDebugLog then
stdnse.print_debug( 2, "%s: Converting %s to subpath %s", NP_LIBRARY_NAME, pipeName, match )
end
else
status = false
pipeSubPath = pipeName
end
return status, pipeSubPath
end,
make_pipe_name = function( hostnameOrIp, pipeSubPath )
if pipeSubPath:sub(1,1) ~= "\\" then
pipeSubPath = "\\" .. pipeSubPath
end
return string.format( "\\\\%s\\pipe%s", hostnameOrIp, pipeSubPath )
end,
named_pipe = {
_smbstate = nil,
_host = nil,
_pipeSubPath = nil,
_overrides = nil,
name = nil,
new = function(self,o)
o = o or {}
setmetatable(o, self)
self.__index = self
return o
end,
connect = function( self, host, pipeSubPath, overrides )
stdnse.print_debug( 2, "%s: connect() called with %s", NP_LIBRARY_NAME, tostring( pipeSubPath ) )
self._overrides = overrides or {}
self._host = host
self._pipeSubPath = pipeSubPath
if not host and not host.ip then return false, "host table is required" end
if not pipeSubPath then return false, "pipeSubPath is required" end
-- If we got a full pipe name, not a sub-path, fix it
if ( pipeSubPath:match( "^\\\\(.-)$" ) ) then
local status
status, self._pipeSubPath = namedpipes.get_pipe_subpath( self._pipeSubPath, true )
if ( not status ) then
stdnse.print_debug( 1, "%s: Attempt to connect to invalid pipe name: %s", NP_LIBRARY_NAME, tostring( pipeSubPath ) )
return false, "Invalid pipe name"
end
end
self.name = namedpipes.make_pipe_name( self._host.ip, self._pipeSubPath )
stdnse.print_debug( 2, "%s: Connecting to named pipe: %s", NP_LIBRARY_NAME, self.name )
local status, result, errorMessage
local negotiate_protocol, start_session, disable_extended = true, true, false
status, result = smb.start_ex( self._host, negotiate_protocol, start_session,
"IPC$", self._pipeSubPath, disable_extended, self._overrides )
if status then
self._smbstate = result
else
errorMessage = string.format( "Connection failed: %s", result )
stdnse.print_debug( 2, "%s: Connection to named pipe (%s) failed: %s",
NP_LIBRARY_NAME, self.name, errorMessage )
end
return status, errorMessage, result
end,
disconnect = function( self )
if ( self._smbstate ) then
stdnse.print_debug( 2, "%s: Disconnecting named pipe: %s", NP_LIBRARY_NAME, self.name )
return smb.stop( self._smbstate )
else
stdnse.print_debug( 2, "%s: disconnect() called, but SMB connection is already closed: %s", NP_LIBRARY_NAME, self.name )
end
end,
send = function( self, messageData )
if not self._smbstate then
stdnse.print_debug( 2, "%s: send() called on closed pipe (%s)", NP_LIBRARY_NAME, self.name )
return false, "Failed to send message on named pipe"
end
local offset = 0 -- offset is actually ignored for named pipes, but we'll define the argument for clarity
local status, result, errorMessage
status, result = smb.write_file( self._smbstate, messageData, offset, self._overrides )
-- if status is true, result is data that we don't need to pay attention to
if not status then
stdnse.print_debug( 2, "%s: Write to named pipe (%s) failed: %s",
NP_LIBRARY_NAME, self.name, result )
errorMessage = "Failed to send message on named pipe", result
end
return status, errorMessage
end,
receive = function( self )
if not self._smbstate then
stdnse.print_debug( 2, "%s: receive() called on closed pipe (%s)", NP_LIBRARY_NAME, self.name )
return false, "Failed to read from named pipe"
end
local status, result, messageData
-- Packet header values
local offset = 0 -- offset is actually ignored for named pipes, but we'll define the argument for clarity
local MAX_BYTES_PER_READ = 4096
status, result = smb.read_file( self._smbstate, offset, MAX_BYTES_PER_READ, self._overrides )
if status and result.data then
messageData = result.data
else
stdnse.print_debug( 2, "%s: Read from named pipe (%s) failed: %s",
NP_LIBRARY_NAME, self.name, result )
return false, "Failed to read from named pipe", result
end
while (result["status"] == smb.status_codes.NT_STATUS_BUFFER_OVERFLOW) do
status, result = smb.read_file( self._smbstate, offset, MAX_BYTES_PER_READ, self._overrides )
if status and result.data then
messageData = messageData .. result.data
else
stdnse.print_debug( 2, "%s: Read additional data from named pipe (%s) failed: %s",
NP_LIBRARY_NAME, self.name, result )
return false, "Failed to read from named pipe", result
end
end
return status, messageData
end,
}
}
filetype_codes =
{
FILE_TYPE_DISK = 0x00,
FILE_TYPE_BYTE_MODE_PIPE = 0x01,
FILE_TYPE_MESSAGE_MODE_PIPE = 0x02,
FILE_TYPE_PRINTER = 0x03,
FILE_TYPE_UNKNOWN = 0xFF
}
for i, v in pairs(filetype_codes) do
filetype_names[v] = i
end

View File

@@ -1,55 +1,111 @@
-- -*- mode: lua -*-
-- vim: set filetype=lua :
description = [[ description = [[
Discovers Microsoft SQL servers in the same broadcast domain. Discovers Microsoft SQL servers in the same broadcast domain.
SQL Server credentials required: No (will not benefit from
<code>mssql.username</code> & <code>mssql.password</code>).
The script attempts to discover SQL Server instances in the same broadcast
domain. Any instances found are stored in the Nmap registry for use by any
other ms-sql-* scripts that are run in the same scan.
In contrast to the <code>ms-sql-discover</code> script, the broadcast version
will use a broadcast method rather than targeting individual hosts. However, the
broadcast version will only use the SQL Server Browser service discovery method.
]] ]]
---
-- @usage
-- nmap --script broadcast-ms-sql-discover
-- nmap --script broadcast-ms-sql-discover,ms-sql-info --script-args=newtargets
-- --
-- Version 0.1 -- @output
-- | broadcast-ms-sql-discover:
-- | 192.168.100.128 (WINXP)
-- | [192.168.100.128\MSSQLSERVER]
-- | Name: MSSQLSERVER
-- | Product: Microsoft SQL Server 2000
-- | TCP port: 1433
-- | Named pipe: \\192.168.100.128\pipe\sql\query
-- | [192.168.100.128\SQL2K5]
-- | Name: SQL2K5
-- | Product: Microsoft SQL Server 2005
-- | Named pipe: \\192.168.100.128\pipe\MSSQL$SQL2K5\sql\query
-- | 192.168.100.150 (SQLSRV)
-- | [192.168.100.150\PROD]
-- | Name: PROD
-- | Product: Microsoft SQL Server 2008
-- |_ Named pipe: \\192.168.100.128\pipe\sql\query
--
-- Created 07/12/2010 - v0.1 - created by Patrik Karlsson <patrik@cqure.net> -- Created 07/12/2010 - v0.1 - created by Patrik Karlsson <patrik@cqure.net>
-- Revised 02/01/2011 - v0.2 - Added compatibility with changes in mssql.lua (Chris Woodbury)
author = "Patrik Karlsson" author = "Patrik Karlsson"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html" license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"broadcast"} categories = {"broadcast", "safe", "discovery"}
require 'mssql' require 'mssql'
require 'target' require 'target'
require 'stdnse'
prerule = function() return true end prerule = function() return true end
action = function()
local OUTPUT_TBL = { --- Adds a label and value to an output table. If the value is a boolean, it is
["Server name"] = "info.servername", -- converted to Yes/No; if the value is nil, nothing is added to the table.
["Version"] = "version.version", local function add_to_output_table( outputTable, outputLabel, outputData )
["Clustered"] = "info.clustered",
["Named pipe"] = "info.pipe", if outputData ~= nil then
["Tcp port"] = "info.port" if outputData == true then
} outputData = "Yes"
elseif outputData == false then
outputData = "No"
end
table.insert(outputTable, string.format( "%s: %s", outputLabel, outputData ) )
end
end
--- Returns formatted output for the given instance
local function create_instance_output_table( instance )
local instanceOutput = {}
instanceOutput["name"] = string.format( "[%s]", instance:GetName() )
add_to_output_table( instanceOutput, "Name", instance.instanceName )
if instance.version then add_to_output_table( instanceOutput, "Product", instance.version.productName ) end
if instance.port then add_to_output_table( instanceOutput, "TCP port", instance.port.number ) end
add_to_output_table( instanceOutput, "Named pipe", instance.pipeName )
return instanceOutput
end
action = function()
local status, result = mssql.Helper.Discover("255.255.255.255", 1434, true) local host = { ip = "255.255.255.255" }
local port = { number = 1434, protocol = "udp" }
local status, result = mssql.Helper.DiscoverBySsrp(host, port, true)
if ( not(status) ) then return end if ( not(status) ) then return end
local results = {} local scriptOutput = {}
for ip, instances in pairs(result) do for ip, instanceList in pairs(result) do
local result_part = {} local serverOutput, serverName = {}, nil
if target.ALLOW_NEW_TARGETS then target.add(ip) end target.add( ip )
for name, info in pairs(instances) do for _, instance in ipairs( instanceList ) do
local instance = {} serverName = serverName or instance.serverName
local version local instanceOutput = create_instance_output_table( instance )
status, version = mssql.Util.DecodeBrowserInfoVersion(info) table.insert(serverOutput, instanceOutput)
for topic, varname in pairs(OUTPUT_TBL) do
local func = loadstring( "return " .. varname )
setfenv(func, setmetatable({ info=info; version=version; }, {__index = _G}))
local result = func()
if ( result ) then
table.insert( instance, ("%s: %s"):format(topic, result) )
end
end
instance.name = version.product
table.insert( result_part, { name = "Instance: " .. info.name, instance } )
end end
result_part.name = ip serverOutput.name = string.format( "%s (%s)", ip, serverName )
table.insert( results, result_part ) table.insert( scriptOutput, serverOutput )
end end
return stdnse.format_output( true, results )
return stdnse.format_output( true, scriptOutput )
end end

View File

@@ -1,79 +1,301 @@
-- -*- mode: lua -*-
-- vim: set filetype=lua :
description = [[ description = [[
Performs password guessing against Microsoft SQL Server (ms-sql). Performs password guessing against Microsoft SQL Server (ms-sql). Works best in
conjuction with the <code>ms-sql-discover</code> script.
SQL Server credentials required: No (will not benefit from
<code>mssql.username</code> & <code>mssql.password</code>).
Run criteria:
* Host script: Will run if the <code>mssql.instance-all</code>, <code>mssql.instance-name</code>
or <code>mssql.instance-port</code> script arguments are used (see mssql.lua).
* Port script: Will run against any services identified as SQL Servers, but only
if the <code>mssql.instance-all</code>, <code>mssql.instance-name</code>
and <code>mssql.instance-port</code> script arguments are NOT used.
WARNING: SQL Server 2005 and later versions include support for account lockout
policies (which are enforced on a per-user basis). If an account is locked out,
the script will stop running for that instance, unless the
<code>ms-sql-brute.ignore-lockout</code> argument is used.
NOTE: Communication with instances via named pipes depends on the <code>smb</code>
library. To communicate with (and possibly to discover) instances via named pipes,
the host must have at least one SMB port (e.g. TCP 445) that was scanned and
found to be open. Additionally, named pipe connections may require Windows
authentication to connect to the Windows host (via SMB) in addition to the
authentication required to connect to the SQL Server instances itself. See the
documentation and arguments for the <code>smb</code> library for more information.
NOTE: By default, the ms-sql-* scripts may attempt to connect to and communicate
with ports that were not included in the port list for the Nmap scan. This can
be disabled using the <code>mssql.scanned-ports-only</code> script argument.
]] ]]
---
-- @usage
-- nmap -p 445 --script ms-sql-brute --script-args mssql.instance-all,userdb=customuser.txt,passdb=custompass.txt <host>
-- nmap -p 1433 --script ms-sql-brute --script-args userdb=customuser.txt,passdb=custompass.txt <host>
--
-- @output
-- | ms-sql-brute:
-- | [192.168.100.128\TEST]
-- | No credentials found
-- | Warnings:
-- | sa: AccountLockedOut
-- | [192.168.100.128\PROD]
-- | Credentials found:
-- | webshop_reader:secret => Login Success
-- | testuser:secret1234 => PasswordMustChange
-- |_ lordvader:secret1234 => Login Success
--
----
-- @args ms-sql-brute.ignore-lockout WARNING! Including this argument will cause
-- the script to continue attempting to brute-forcing passwords for users
-- even after a user has been locked out. This may result in many SQL
-- Server logins being locked out!
--
-- Created 01/17/2010 - v0.1 - created by Patrik Karlsson <patrik@cqure.net>
-- Revised 02/01/2011 - v0.2 (Chris Woodbury)
-- - Added ability to run against all instances on a host;
-- - Added recognition of account-locked out and password-expired error codes;
-- - Added storage of credentials on a per-instance basis
-- - Added compatibility with changes in mssql.lua
author = "Patrik Karlsson" author = "Patrik Karlsson"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html" license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"auth", "intrusive"} categories = {"auth", "intrusive"}
dependencies = {"ms-sql-discover", "ms-sql-empty-password"}
require 'shortport' require 'shortport'
require 'stdnse' require 'stdnse'
require 'mssql' require 'mssql'
require 'unpwdb' require 'unpwdb'
---
-- @output
-- PORT STATE SERVICE
-- 1433/tcp open ms-sql-s
-- | ms-sql-brute:
-- | webshop_reader:secret => Login Success
-- | testuser:secret1234 => Must change password at next logon
-- |_ lordvader:secret1234 => Login Success
-- Version 0.1 hostrule = mssql.Helper.GetHostrule_Standard()
-- Created 01/17/2010 - v0.1 - created by Patrik Karlsson <patrik@cqure.net> portrule = mssql.Helper.GetPortrule_Standard()
--- Returns formatted output for the given instance
local function create_instance_output_table( instance )
local instanceOutput = {}
instanceOutput["name"] = string.format( "[%s]", instance:GetName() )
if ( instance.ms_sql_brute.credentials ) then
local credsOutput = {}
credsOutput["name"] = "Credentials found:"
table.insert( instanceOutput, credsOutput )
for username, result in pairs( instance.ms_sql_brute.credentials ) do
local password = result[1]
local errorCode = result[2]
password = password:len()>0 and password or "<empty>"
if errorCode then
local errorMessage = mssql.LoginErrorMessage[ errorCode ] or "unknown error"
table.insert( credsOutput, string.format( "%s:%s => %s", username, password, errorMessage ) )
else
table.insert( credsOutput, string.format( "%s:%s => Login Success", username, password ) )
end
end
if ( table.getn( credsOutput ) == 0 ) then
table.insert( instanceOutput, "No credentials found" )
end
end
if ( instance.ms_sql_brute.warnings ) then
local warningsOutput = {}
warningsOutput["name"] = "Warnings:"
table.insert( instanceOutput, warningsOutput )
for _, warning in ipairs( instance.ms_sql_brute.warnings ) do
table.insert( warningsOutput, warning )
end
end
if ( instance.ms_sql_brute.errors ) then
local errorsOutput = {}
errorsOutput["name"] = "Errors:"
table.insert( instanceOutput, errorsOutput )
for _, error in ipairs( instance.ms_sql_brute.errors ) do
table.insert( errorsOutput, error )
end
end
return instanceOutput
end
local function test_credentials( instance, helper, username, password )
local database = "tempdb"
local stopUser, stopInstance = false, false
local status, result = helper:ConnectEx( instance )
local loginErrorCode
if( status ) then
stdnse.print_debug( 2, "%s: Attempting login to %s as %s/%s", SCRIPT_NAME, instance:GetName(), username, password )
status, result, loginErrorCode = helper:Login( username, password, database, instance.host.ip )
end
helper:Disconnect()
local passwordIsGood, canLogin
if status then
passwordIsGood = true
canLogin = true
elseif ( loginErrorCode ) then
if ( ( loginErrorCode ~= mssql.LoginErrorType.InvalidUsernameOrPassword ) and
( loginErrorCode ~= mssql.LoginErrorType.NotAssociatedWithTrustedConnection ) ) then
stopUser = true
end
if ( loginErrorCode == mssql.LoginErrorType.PasswordExpired ) then passwordIsGood = true
elseif ( loginErrorCode == mssql.LoginErrorType.PasswordMustChange ) then passwordIsGood = true
elseif ( loginErrorCode == mssql.LoginErrorType.AccountLockedOut ) then
stdnse.print_debug( 1, "%s: Account %s locked out on %s", SCRIPT_NAME, username, instance:GetName() )
table.insert( instance.ms_sql_brute.warnings, string.format( "%s: Account is locked out.", username ) )
if ( not stdnse.get_script_args( "ms-sql-brute.ignore-lockout" ) ) then
stopInstance = true
end
end
if ( mssql.LoginErrorMessage[ loginErrorCode ] == nil ) then
stdnse.print_debug( 2, "%s: Attemping login to %s as (%s/%s): Unknown login error number: %s",
SCRIPT_NAME, instance:GetName(), username, password, loginErrorCode )
table.insert( instance.ms_sql_brute.warnings, string.format( "Unknown login error number: %s", loginErrorCode ) )
end
stdnse.print_debug( 3, "%s: Attempt to login to %s as (%s/%s): %d (%s)",
SCRIPT_NAME, instance:GetName(), username, password, loginErrorCode, tostring( mssql.LoginErrorMessage[ loginErrorCode ] ) )
else
table.insert( instance.ms_sql_brute.errors, string.format("Network error. Skipping instance. Error: %s", result ) )
stopUser = true
stopInstance = true
end
if ( passwordIsGood ) then
stopUser = true
instance.ms_sql_brute.credentials[ username ] = { password, loginErrorCode }
-- Add credentials for other ms-sql scripts to use but don't
-- add accounts that need to change passwords
if ( canLogin ) then
instance.credentials[ username ] = password
-- Legacy storage method (does not distinguish between instances)
nmap.registry.mssqlusers = nmap.registry.mssqlusers or {}
nmap.registry.mssqlusers[username]=password
end
end
return stopUser, stopInstance
end
--- Processes a single instance, attempting to detect an empty password for "sa"
local function process_instance( 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
-- avoid redundant login attempts on an instance, we will just make the
-- attempt once and then re-use the results. We'll use a mutex to make sure
-- that multiple script instances (e.g. a host-script and a port-script)
-- working on the same SQL Server instance can only enter this block one at
-- a time.
local mutex = nmap.mutex( instance )
mutex( "lock" )
-- If this instance has already been tested (e.g. if we got to it by both the
-- hostrule and the portrule), don't test it again.
if ( instance.tested_brute ~= true ) then
instance.tested_brute = true
instance.credentials = instance.credentials or {}
instance.ms_sql_brute = instance.ms_sql_brute or {}
instance.ms_sql_brute.credentials = instance.ms_sql_brute.credentials or {}
instance.ms_sql_brute.warnings = instance.ms_sql_brute.warnings or {}
instance.ms_sql_brute.errors = instance.ms_sql_brute.errors or {}
local result, status
local stopUser, stopInstance
local usernames, passwords, username, password
local helper = mssql.Helper:new()
if ( not instance:HasNetworkProtocols() ) then
stdnse.print_debug( 1, "%s: %s has no network protocols enabled.", SCRIPT_NAME, instance:GetName() )
table.insert( instance.ms_sql_brute.errors, "No network protocols enabled." )
stopInstance = true
end
status, usernames = unpwdb.usernames()
if ( not(status) ) then
stdnse.print_debug( 1, "%s: Failed to load usernames list.", SCRIPT_NAME )
table.insert( instance.ms_sql_brute.errors, "Failed to load usernames list." )
stopInstance = true
end
if ( status ) then
status, passwords = unpwdb.passwords()
if ( not(status) ) then
stdnse.print_debug( 1, "%s: Failed to load passwords list.", SCRIPT_NAME )
table.insert( instance.ms_sql_brute.errors, "Failed to load passwords list." )
stopInstance = true
end
end
if ( status ) then
for username in usernames do
if stopInstance then break end
-- See if the password is the same as the username (which may not
-- be in the password list)
stopUser, stopInstance = test_credentials( instance, helper, username, username )
for password in passwords do
if stopUser then break end
stopUser, stopInstance = test_credentials( instance, helper, username, password )
end
passwords("reset")
end
end
end
-- The password testing has been finished. Unlock the mutex.
mutex( "done" )
return create_instance_output_table( instance )
end
portrule = shortport.port_or_service(1433, "ms-sql-s")
action = function( host, port ) action = function( host, port )
local scriptOutput = {}
local result, response, status = {}, nil, nil local status, instanceList = mssql.Helper.GetTargetInstances( host, port )
local valid_accounts = {}
local usernames, passwords
local username, password
local helper = mssql.Helper:new()
status, usernames = unpwdb.usernames() local domain, bruteWindows = stdnse.get_script_args("mssql.domain", "ms-sql-brute.brute-windows-accounts")
if ( not(status) ) then
return " \n\nFailed to load usernames.lst"
end
status, passwords = unpwdb.passwords()
if ( not(status) ) then
return " \n\nFailed to load usernames.lst"
end
for username in usernames do
for password in passwords do
status, result = helper:Connect(host, port) if ( domain and not(bruteWindows) ) then
if( not(status) ) then local ret = "\n " ..
return " \n\n" .. result "Windows authentication was enabled but the argument\n " ..
"ms-sql-brute.brute-windows-accounts was not given. As there is currently no\n " ..
"way of detecting accounts being locked out when Windows authentication is \n " ..
"used, make sure that the amount entries in the password list\n " ..
"(passdb argument) are at least 2 entries below the lockout threshold."
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
stdnse.print_debug( "Trying %s/%s ...", username, password )
status, result = helper:Login( username, password, "tempdb", host.ip )
helper:Disconnect()
if ( status ) or ( "Must change password at next logon" == result ) then
-- Add credentials for other mysql scripts to use
table.insert( valid_accounts, string.format("%s:%s => %s", username, password:len()>0 and password or "<empty>", result ) )
-- don't add accounts that need to change passwords to the registry
if ( result ~= "Login Success") then
break
end
if nmap.registry.mssqlusers == nil then
nmap.registry.mssqlusers = {}
end
nmap.registry.mssqlusers[username]=password
break
end
end end
passwords("reset")
end end
local output = stdnse.format_output(true, valid_accounts) return stdnse.format_output( true, scriptOutput )
return output
end end

View File

@@ -1,8 +1,65 @@
-- -*- mode: lua -*-
-- vim: set filetype=lua :
description = [[ description = [[
Queries Microsoft SQL Server (ms-sql) for a list of databases, linked Queries Microsoft SQL Server (ms-sql) instances for a list of databases, linked servers,
servers, and configuration settings. and configuration settings.
SQL Server credentials required: Yes (use <code>ms-sql-brute</code>, <code>ms-sql-empty-password</code>
and/or <code>mssql.username</code> & <code>mssql.password</code>)
Run criteria:
* Host script: Will run if the <code>mssql.instance-all</code>, <code>mssql.instance-name</code>
or <code>mssql.instance-port</code> script arguments are used (see mssql.lua).
* Port script: Will run against any services identified as SQL Servers, but only
if the <code>mssql.instance-all</code>, <code>mssql.instance-name</code>
and <code>mssql.instance-port</code> script arguments are NOT used.
NOTE: Communication with instances via named pipes depends on the <code>smb</code>
library. To communicate with (and possibly to discover) instances via named pipes,
the host must have at least one SMB port (e.g. TCP 445) that was scanned and
found to be open. Additionally, named pipe connections may require Windows
authentication to connect to the Windows host (via SMB) in addition to the
authentication required to connect to the SQL Server instances itself. See the
documentation and arguments for the <code>smb</code> library for more information.
NOTE: By default, the ms-sql-* scripts may attempt to connect to and communicate
with ports that were not included in the port list for the Nmap scan. This can
be disabled using the <code>mssql.scanned-ports-only</code> script argument.
]] ]]
---
-- @usage
-- nmap -p 1433 --script ms-sql-config --script-args mssql.username=sa,mssql.password=sa <host>
--
-- @args ms-sql-config.showall If set, shows all configuration options.
--
-- @output
-- | ms-sql-config:
-- | [192.168.100.25\MSSQLSERVER]
-- | Databases
-- | name db_size owner
-- | ==== ======= =====
-- | nmap 2.74 MB MAC-MINI\david
-- | Configuration
-- | name value inuse description
-- | ==== ===== ===== ===========
-- | SQL Mail XPs 0 0 Enable or disable SQL Mail XPs
-- | Database Mail XPs 0 0 Enable or disable Database Mail XPs
-- | SMO and DMO XPs 1 1 Enable or disable SMO and DMO XPs
-- | Ole Automation Procedures 0 0 Enable or disable Ole Automation Procedures
-- | xp_cmdshell 0 0 Enable or disable command shell
-- | Ad Hoc Distributed Queries 0 0 Enable or disable Ad Hoc Distributed Queries
-- | Replication XPs 0 0 Enable or disable Replication XPs
-- | Linked Servers
-- | srvname srvproduct providername
-- | ======= ========== ============
-- |_ MAC-MINI SQL Server SQLOLEDB
--
-- Created 04/02/2010 - v0.1 - created by Patrik Karlsson <patrik@cqure.net>
-- Revised 02/01/2011 - v0.2 - Added ability to run against all instances on a host;
-- added compatibility with changes in mssql.lua (Chris Woodbury)
author = "Patrik Karlsson" author = "Patrik Karlsson"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html" license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"discovery", "safe"} categories = {"discovery", "safe"}
@@ -11,57 +68,23 @@ require 'shortport'
require 'stdnse' require 'stdnse'
require 'mssql' require 'mssql'
dependencies = {"ms-sql-brute", "ms-sql-empty-password"} dependencies = {"ms-sql-brute", "ms-sql-empty-password", "ms-sql-discover"}
---
-- @args mssql.username specifies the username to use to connect to
-- the server. This option overrides any accounts found by
-- the mssql-brute and mssql-empty-password scripts.
--
-- @args mssql.password specifies the password to use to connect to
-- the server. This option overrides any accounts found by
-- the mssql-brute and mssql-empty-password scripts.
--
-- @args ms-sql-config.showall if set shows all configuration options.
--
-- @output
-- PORT STATE SERVICE
-- 1433/tcp open ms-sql-s
-- | ms-sql-config:
-- | Databases
-- | name db_size owner
-- | ==== ======= =====
-- | nmap 2.74 MB MAC-MINI\david
-- | Configuration
-- | name value inuse description
-- | ==== ===== ===== ===========
-- | SQL Mail XPs 0 0 Enable or disable SQL Mail XPs
-- | Database Mail XPs 0 0 Enable or disable Database Mail XPs
-- | SMO and DMO XPs 1 1 Enable or disable SMO and DMO XPs
-- | Ole Automation Procedures 0 0 Enable or disable Ole Automation Procedures
-- | xp_cmdshell 0 0 Enable or disable command shell
-- | Ad Hoc Distributed Queries 0 0 Enable or disable Ad Hoc Distributed Queries
-- | Replication XPs 0 0 Enable or disable Replication XPs
-- | Linked Servers
-- | srvname srvproduct providername
-- | ======= ========== ============
-- |_ MAC-MINI SQL Server SQLOLEDB
-- Version 0.1 hostrule = mssql.Helper.GetHostrule_Standard()
-- Created 04/02/2010 - v0.1 - created by Patrik Karlsson <patrik@cqure.net> portrule = mssql.Helper.GetPortrule_Standard()
portrule = shortport.port_or_service(1433, "ms-sql-s")
action = function( host, port ) --- Processes a set of instances
local function process_instance( instance )
local status, helper, response
local username = stdnse.get_script_args( 'mssql.username' ) local status, errorMessage
local password = stdnse.get_script_args( 'mssql.password' ) or ""
local result, result_part = {}, {} local result, result_part = {}, {}
local conf_filter = stdnse.get_script_args( {'mssql-config.showall', 'ms-sql-config.showall'} ) and "" local conf_filter = stdnse.get_script_args( {'mssql-config.showall', 'ms-sql-config.showall'} ) and ""
or " WHERE configuration_id > 16384" or " WHERE configuration_id > 16384"
local db_filter = stdnse.get_script_args( {'mssql-config.showall', 'ms-sql-config.showall'} ) and "" local db_filter = stdnse.get_script_args( {'mssql-config.showall', 'ms-sql-config.showall'} ) and ""
or " WHERE name NOT IN ('master','model','tempdb','msdb')" or " WHERE name NOT IN ('master','model','tempdb','msdb')"
local helper = mssql.Helper:new()
local queries = { local queries = {
[2]={ ["Configuration"] = [[ SELECT name, [2]={ ["Configuration"] = [[ SELECT name,
@@ -77,46 +100,25 @@ action = function( host, port )
INSERT INTO #nmap_dbs EXEC sp_helpdb INSERT INTO #nmap_dbs EXEC sp_helpdb
SELECT name, db_size, owner SELECT name, db_size, owner
FROM #nmap_dbs ]] .. db_filter .. [[ FROM #nmap_dbs ]] .. db_filter .. [[
DROP DATABASE #nmap_dbs ]] } DROP TABLE #nmap_dbs ]] }
} }
if ( not(username) and nmap.registry.mssqlusers ) then status, errorMessage = helper:ConnectEx( instance )
-- do we have a sysadmin? if ( not(status) ) then result = "ERROR: " .. errorMessage end
if ( nmap.registry.mssqlusers.sa ) then
username = "sa"
password = nmap.registry.mssqlusers.sa
else
-- ok were stuck with some non sysadmin account, just get the first one
for user, pass in pairs(nmap.registry.mssqlusers) do
username = user
password = pass
break
end
end
end
-- If we don't have a valid username, simply fail silently if status then
if ( not(username) ) then status, errorMessage = helper:LoginEx( instance )
return if ( not(status) ) then result = "ERROR: " .. errorMessage end
end
helper = mssql.Helper:new()
status, response = helper:Connect(host, port)
if ( not(status) ) then
return " \n\n" .. response
end
status, response = helper:Login( username, password, nil, host.ip )
if ( not(status) ) then
return " \n\nERROR: " .. response
end end
for _, v in ipairs( queries ) do for _, v in ipairs( queries ) do
if ( not status ) then break end
for header, query in pairs(v) do for header, query in pairs(v) do
status, result_part = helper:Query( query ) status, result_part = helper:Query( query )
if ( not(status) ) then if ( not(status) ) then
return " \n\nERROR: " .. result_part result = "ERROR: " .. result_part
break
end end
result_part = mssql.Util.FormatOutputTable( result_part, true ) result_part = mssql.Util.FormatOutputTable( result_part, true )
result_part.name = header result_part.name = header
@@ -126,6 +128,28 @@ action = function( host, port )
helper:Disconnect() helper:Disconnect()
return stdnse.format_output( true, result ) local instanceOutput = {}
instanceOutput["name"] = string.format( "[%s]", instance:GetName() )
table.insert( instanceOutput, result )
return 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 end

131
scripts/ms-sql-discover.nse Executable file
View File

@@ -0,0 +1,131 @@
-- -*- mode: lua -*-
-- vim: set filetype=lua :
description = [[
Attempts to discover Microsoft SQL Server instances.
SQL Server credentials required: No (will not benefit from
<code>mssql.username</code> & <code>mssql.password</code>).
Run criteria:
* Host script: Will always run, unless the <code>mssql.scanned-ports-only</code>
script argument was specified (see mssql.lua for more details); in that case,
the script will run if one or more of the following ports were scanned and
weren't found to be closed: 1434/udp, 1433/tcp, an SMB port (see smb.lua).
* Port script: N/A
The script attempts to discover SQL Server instances. Any instances found are
stored in the Nmap registry for use by any other ms-sql-* scripts that are run
in the same scan.
The script attempts to discover SQL Server instances by the following three
methods:
* Querying the SQL Server Brower service (UDP port 1434): If this service is
available, it will provide detailed information on each of the instances
installed on the host, including an approximate version number (use <code>ms-sql-info</code>
for more accurate and detailed version information). However, this service may
not be running, even if SQL Server instances are present, and it is also possible
for instances to "hide" themselves from the Browser service.
* Connecting to the default SQL Server listening port (TCP port 1433): The script
will attempt to fingerprint the service (if any) listening on TCP port 1433, SQL
Server's default port.
* Connecting via named pipes to the default pipe names: The script will attempt
to connect over SMB to default pipe names for SQL Server.
NOTE: Communication with instances via named pipes depends on the <code>smb</code>
library. To communicate with (and possibly to discover) instances via named pipes,
the host must have at least one SMB port (e.g. TCP 445) that was scanned and
found to be open. Additionally, named pipe connections may require Windows
authentication to connect to the Windows host (via SMB) in addition to the
authentication required to connect to the SQL Server instances itself. See the
documentation and arguments for the <code>smb</code> library for more information.
NOTE: By default, the ms-sql-* scripts may attempt to connect to and communicate
with ports that were not included in the port list for the Nmap scan. This can
be disabled using the <code>mssql.scanned-ports-only</code> script argument.
]]
---
-- @usage
-- nmap -p 445 --script ms-sql-discover <host>
--
-- @output
-- | ms-sql-discover:
-- | [192.168.100.128\MSSQLSERVER]
-- | Name: MSSQLSERVER
-- | Product: Microsoft SQL Server 2000
-- | TCP port: 1433
-- | Named pipe: \\192.168.100.128\pipe\sql\query
-- | [192.168.100.128\SQL2K5]
-- | Name: SQL2K5
-- | Product: Microsoft SQL Server 2005
-- |_ Named pipe: \\192.168.100.128\pipe\MSSQL$SQL2K5\sql\query
-- rev 1.0 (2011-02-01) - Initial version (Chris Woodbury)
author = "Chris Woodbury"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"default", "discovery", "safe"}
require("mssql")
require("smb")
hostrule = function(host)
local sqlDefaultPort = nmap.get_port_state( host, {number = 1433, protocol = "tcp"} )
local sqlBrowserPort = nmap.get_port_state( host, {number = 1434, protocol = "udp"} )
local smbPortNumber = smb.get_port( host )
return (not mssql.SCANNED_PORTS_ONLY) or
(sqlDefaultPort and sqlDefaultPort.state ~= "closed") or
(sqlBrowserPort and sqlBrowserPort.state ~= "closed") or
(smbPortNumber ~= nil)
end
--- Adds a label and value to an output table. If the value is a boolean, it is
-- converted to Yes/No; if the value is nil, nothing is added to the table.
local function add_to_output_table( outputTable, outputLabel, outputData )
if outputData ~= nil then
if outputData == true then
outputData = "Yes"
elseif outputData == false then
outputData = "No"
end
table.insert(outputTable, string.format( "%s: %s", outputLabel, outputData ) )
end
end
--- Returns formatted output for the given instance
local function create_instance_output_table( instance )
local instanceOutput = {}
instanceOutput["name"] = string.format( "[%s]", instance:GetName() )
add_to_output_table( instanceOutput, "Name", instance.instanceName )
if instance.version then add_to_output_table( instanceOutput, "Product", instance.version.productName ) end
if instance.port then add_to_output_table( instanceOutput, "TCP port", instance.port.number ) end
add_to_output_table( instanceOutput, "Named pipe", instance.pipeName )
return instanceOutput
end
action = function(host)
mssql.Helper.Discover( host )
local scriptOutput, instancesFound = {}, nil
instancesFound = mssql.Helper.GetDiscoveredInstances( host )
if ( instancesFound ) then
for _, instance in ipairs( instancesFound ) do
local instanceOutput = create_instance_output_table( instance )
table.insert(scriptOutput, instanceOutput)
end
stdnse.print_debug( 1, "%s: Found %d instances for %s.", SCRIPT_NAME, #instancesFound, host.ip )
end
return stdnse.format_output( true, scriptOutput )
end

View File

@@ -1,52 +1,181 @@
-- -*- mode: lua -*-
-- vim: set filetype=lua :
description = [[ description = [[
Attempts to authenticate using an empty password for the sysadmin (sa) account. Attempts to authenticate to Microsoft SQL Servers using an empty password for
the sysadmin (sa) account.
SQL Server credentials required: No (will not benefit from
<code>mssql.username</code> & <code>mssql.password</code>).
Run criteria:
* Host script: Will run if the <code>mssql.instance-all</code>, <code>mssql.instance-name</code>
or <code>mssql.instance-port</code> script arguments are used (see mssql.lua).
* Port script: Will run against any services identified as SQL Servers, but only
if the <code>mssql.instance-all</code>, <code>mssql.instance-name</code>
and <code>mssql.instance-port</code> script arguments are NOT used.
WARNING: SQL Server 2005 and later versions include support for account lockout
policies (which are enforced on a per-user basis).
NOTE: Communication with instances via named pipes depends on the <code>smb</code>
library. To communicate with (and possibly to discover) instances via named pipes,
the host must have at least one SMB port (e.g. TCP 445) that was scanned and
found to be open. Additionally, named pipe connections may require Windows
authentication to connect to the Windows host (via SMB) in addition to the
authentication required to connect to the SQL Server instances itself. See the
documentation and arguments for the <code>smb</code> library for more information.
NOTE: By default, the ms-sql-* scripts may attempt to connect to and communicate
with ports that were not included in the port list for the Nmap scan. This can
be disabled using the <code>mssql.scanned-ports-only</code> script argument.
]] ]]
---
-- @usage
-- nmap -p 445 --script ms-sql-empty-password --script-args mssql.instance-all <host>
-- nmap -p 1433 --script ms-sql-empty-password <host>
--
-- @output
-- | ms-sql-empty-password:
-- | [192.168.100.128\PROD]
-- |_ sa:<empty> => Login Success
--
-- Created 01/17/2010 - v0.1 - created by Patrik Karlsson <patrik@cqure.net>
-- Revised 02/01/2011 - v0.2 (Chris Woodbury)
-- - Added ability to run against all instances on a host;
-- - Added storage of credentials on a per-instance basis
-- - Added compatibility with changes in mssql.lua
author = "Patrik Karlsson" author = "Patrik Karlsson"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html" license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"auth","intrusive"} categories = {"auth","intrusive"}
dependencies = {"ms-sql-discover"}
require 'shortport' require 'shortport'
require 'stdnse' require 'stdnse'
require 'mssql' require 'mssql'
--- hostrule = mssql.Helper.GetHostrule_Standard()
-- portrule = mssql.Helper.GetPortrule_Standard()
-- @output
-- PORT STATE SERVICE
-- 1433/tcp open ms-sql-s
-- | ms-sql-empty-password:
-- |_ sa:<empty> => Login Correct
--
--
-- Version 0.1 local function test_credentials( instance, helper, username, password )
-- Created 01/17/2010 - v0.1 - created by Patrik Karlsson <patrik@cqure.net> local database = "tempdb"
local status, result = helper:ConnectEx( instance )
local loginErrorCode
if( status ) then
stdnse.print_debug( 2, "%s: Attempting login to %s", SCRIPT_NAME, instance:GetName() )
status, result, loginErrorCode = helper:Login( username, password, database, instance.host.ip )
end
helper:Disconnect()
local passwordIsGood, canLogin
if status then
passwordIsGood = true
canLogin = true
elseif ( loginErrorCode ) then
if ( loginErrorCode == mssql.LoginErrorType.PasswordExpired ) then passwordIsGood = true end
if ( loginErrorCode == mssql.LoginErrorType.PasswordMustChange ) then passwordIsGood = true end
if ( loginErrorCode == mssql.LoginErrorType.AccountLockedOut ) then
stdnse.print_debug( 1, "%s: Account %s locked out on %s", SCRIPT_NAME, username, instance:GetName() )
table.insert( instance.ms_sql_empty, string.format("'sa' account is locked out.", result ) )
end
if ( mssql.LoginErrorMessage[ loginErrorCode ] == nil ) then
stdnse.print_debug( 2, "%s: Attemping login to %s: Unknown login error number: %s",
SCRIPT_NAME, instance:GetName(), loginErrorCode )
table.insert( instance.ms_sql_empty, string.format( "Unknown login error number: %s", loginErrorCode ) )
end
else
table.insert( instance.ms_sql_empty, string.format("Network error. Error: %s", result ) )
end
if ( passwordIsGood ) then
local loginResultMessage = "Login Success"
if loginErrorCode then
loginResultMessage = mssql.LoginErrorMessage[ errorCode ] or "unknown error"
end
table.insert( instance.ms_sql_empty, string.format( "%s:%s => %s", username, password:len()>0 and password or "<empty>", loginResultMessage ) )
-- Add credentials for other ms-sql scripts to use but don't
-- add accounts that need to change passwords
if ( canLogin ) then
instance.credentials[ username ] = password
-- Legacy storage method (does not distinguish between instances)
nmap.registry.mssqlusers = nmap.registry.mssqlusers or {}
nmap.registry.mssqlusers[username]=password
end
end
end
--- Processes a single instance, attempting to detect an empty password for "sa"
local function process_instance( 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
-- avoid redundant login attempts on an instance, we will just make the
-- attempt once and then re-use the results. We'll use a mutex to make sure
-- that multiple script instances (e.g. a host-script and a port-script)
-- working on the same SQL Server instance can only enter this block one at
-- a time.
local mutex = nmap.mutex( instance )
mutex( "lock" )
local status, result
-- If this instance has already been tested (e.g. if we got to it by both the
-- hostrule and the portrule), don't test it again. This will reduce the risk
-- of locking out accounts.
if ( instance.tested_empty ~= true ) then
instance.tested_empty = true
instance.credentials = instance.credentials or {}
instance.ms_sql_empty = instance.ms_sql_empty or {}
if not instance:HasNetworkProtocols() then
stdnse.print_debug( 1, "%s: %s has no network protocols enabled.", SCRIPT_NAME, instance:GetName() )
table.insert( instance.ms_sql_empty, "No network protocols enabled." )
end
local helper = mssql.Helper:new()
test_credentials( instance, helper, "sa", "" )
end
-- The password testing has been finished. Unlock the mutex.
mutex( "done" )
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
if ( nmap.verbosity() > 1 and table.getn( instance.ms_sql_empty ) == 0 ) then
table.insert( instanceOutput, "'sa' account password is not blank." )
end
end
return instanceOutput
end
portrule = shortport.port_or_service(1433, "ms-sql-s")
action = function( host, port ) action = function( host, port )
local scriptOutput = {}
local helper, status, result local status, instanceList = mssql.Helper.GetTargetInstances( host, port )
local username, password, database, valid_accounts = "sa", "", "tempdb", {}
helper = mssql.Helper:new() if ( not status ) then
status, result = helper:Connect(host, port) return stdnse.format_output( false, instanceList )
else
if( not(status) ) then for _, instance in pairs( instanceList ) do
return " \n\n" .. result local instanceOutput = process_instance( instance )
if instanceOutput then
table.insert( scriptOutput, instanceOutput )
end
end
end end
status, result = helper:Login( username, password, database, host.ip )
helper:Disconnect()
if status then
nmap.registry.mssqlusers = nmap.registry.mssqlusers or {}
nmap.registry.mssqlusers[username]=password
table.insert( valid_accounts, string.format("%s:%s => Login Success", username, password:len()>0 and password or "<empty>" ) )
end
return stdnse.format_output(true, valid_accounts)
return stdnse.format_output( true, scriptOutput )
end end

View File

@@ -1,16 +1,68 @@
-- -*- mode: lua -*-
-- vim: set filetype=lua :
description = [[ description = [[
Queries Microsoft SQL Server (ms-sql) for a list of databases a user has Queries Microsoft SQL Server (ms-sql) instances for a list of databases a user has
access to. access to.
SQL Server credentials required: Yes (use <code>ms-sql-brute</code>, <code>ms-sql-empty-password</code>
and/or <code>mssql.username</code> & <code>mssql.password</code>)
Run criteria:
* Host script: Will run if the <code>mssql.instance-all</code>, <code>mssql.instance-name</code>
or <code>mssql.instance-port</code> script arguments are used (see mssql.lua).
* Port script: Will run against any services identified as SQL Servers, but only
if the <code>mssql.instance-all</code>, <code>mssql.instance-name</code>
and <code>mssql.instance-port</code> script arguments are NOT used.
The script needs an account with the sysadmin server role to work. The script needs an account with the sysadmin server role to work.
It needs to be fed credentials through the script arguments or from
the scripts <code>mssql-brute</code> or <code>mssql-empty-password</code>.
When run, the script iterates over the credentials and attempts to run When run, the script iterates over the credentials and attempts to run
the command until either all credentials are exhausted or until the the command for each available set of credentials.
command is executed.
NOTE: The "owner" field in the results will be truncated at 20 characters. This
is a limitation of the <code>sp_MShasdbaccess</code> stored procedure that the
script uses.
NOTE: Communication with instances via named pipes depends on the <code>smb</code>
library. To communicate with (and possibly to discover) instances via named pipes,
the host must have at least one SMB port (e.g. TCP 445) that was scanned and
found to be open. Additionally, named pipe connections may require Windows
authentication to connect to the Windows host (via SMB) in addition to the
authentication required to connect to the SQL Server instances itself. See the
documentation and arguments for the <code>smb</code> library for more information.
NOTE: By default, the ms-sql-* scripts may attempt to connect to and communicate
with ports that were not included in the port list for the Nmap scan. This can
be disabled using the <code>mssql.scanned-ports-only</code> script argument.
]] ]]
---
-- @usage
-- nmap -p 1433 --script ms-sql-hasdbaccess --script-args mssql.username=sa,mssql.password=sa <host>
--
-- @args ms-sql-hasdbaccess.limit limits the amount of databases per-user
-- that are returned (default 5). If set to zero or less all
-- databases the user has access to are returned.
--
-- @output
-- | ms-sql-hasdbaccess:
-- | [192.168.100.25\MSSQLSERVER]
-- | webshop_reader
-- | dbname owner
-- | ====== =====
-- | hr sa
-- | finance sa
-- | webshop sa
-- | lordvader
-- | dbname owner
-- | ====== =====
-- | testdb CQURE-NET\Administr
-- |_ webshop sa
-- Created 01/17/2010 - v0.1 - created by Patrik Karlsson <patrik@cqure.net>
-- Revised 02/01/2011 - v0.2 - Added ability to run against all instances on a host;
-- added compatibility with changes in mssql.lua (Chris Woodbury)
author = "Patrik Karlsson" author = "Patrik Karlsson"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html" license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"auth", "discovery","safe"} categories = {"auth", "discovery","safe"}
@@ -19,54 +71,16 @@ require 'shortport'
require 'stdnse' require 'stdnse'
require 'mssql' require 'mssql'
dependencies = {"ms-sql-brute", "ms-sql-empty-password"} dependencies = {"ms-sql-brute", "ms-sql-empty-password", "ms-sql-discover"}
---
-- @args mssql.username specifies the username to use to connect to
-- the server. This option overrides any accounts found by
-- the <code>mssql-brute</code> and <code>mssql-empty-password</code> scripts.
--
-- @args mssql.password specifies the password to use to connect to
-- the server. This option overrides any accounts found by
-- the <code>ms-sql-brute</code> and <code>ms-sql-empty-password</code> scripts.
--
-- @args ms-sql-hasdbaccess.limit limits the amount of databases per-user
-- that are returned (default 5). If set to zero or less all
-- databases the user has access to are returned.
--
-- @output
-- PORT STATE SERVICE
-- 1433/tcp open ms-sql-s
-- | ms-sql-hasdbaccess:
-- | webshop_reader
-- | dbname owner
-- | hr sa
-- | finance sa
-- | webshop sa
-- | lordvader
-- | dbname owner
-- | testdb CQURE-NET\Administr
-- |_ webshop sa
-- Version 0.1
-- Created 01/17/2010 - v0.1 - created by Patrik Karlsson <patrik@cqure.net>
portrule = shortport.port_or_service(1433, "ms-sql-s") hostrule = mssql.Helper.GetHostrule_Standard()
portrule = mssql.Helper.GetPortrule_Standard()
local function table_contains( tbl, val )
for k,v in pairs(tbl) do
if ( v == val ) then
return true
end
end
return false
end
action = function( host, port ) local function process_instance( instance )
local status, result, helper, rs local status, result, rs
local username = stdnse.get_script_args('mssql.username')
local password = stdnse.get_script_args('mssql.password') or ""
local creds
local query, limit local query, limit
local output = {} local output = {}
local exclude_dbs = { "'master'", "'tempdb'", "'model'", "'msdb'" } local exclude_dbs = { "'master'", "'tempdb'", "'model'", "'msdb'" }
@@ -90,54 +104,71 @@ action = function( host, port )
("SELECT %s dbname, owner FROM #hasaccess WHERE dbname NOT IN(%s)"):format(limit, stdnse.strjoin(",", exclude_dbs)), ("SELECT %s dbname, owner FROM #hasaccess WHERE dbname NOT IN(%s)"):format(limit, stdnse.strjoin(",", exclude_dbs)),
"DROP TABLE #hasaccess" } "DROP TABLE #hasaccess" }
if ( username ) then local creds = mssql.Helper.GetLoginCredentials_All( instance )
creds = {} if ( not creds ) then
creds[username] = password output = "ERROR: No login credentials."
elseif ( not(username) and nmap.registry.mssqlusers ) then else
-- do we have a sysadmin? for username, password in pairs( creds ) do
creds = nmap.registry.mssqlusers local helper = mssql.Helper:new()
end status, result = helper:ConnectEx( instance )
if ( not(status) ) then
-- If we don't have valid creds, simply fail silently output = "ERROR: " .. result
if ( not(creds) ) then break
return end
end
for username, password in pairs( creds ) do
helper = mssql.Helper:new()
status, result = helper:Connect(host, port)
if ( not(status) ) then
return " \n\n" .. result
end
status, result = helper:Login( username, password, nil, host.ip )
if ( not(status) ) then
stdnse.print_debug("ERROR: %s", result)
break
end
for _, q in pairs(query) do
status, result = helper:Query( q )
if ( status ) then if ( status ) then
-- Only the SELECT statement should produce output status = helper:Login( username, password, nil, instance.host.ip )
if ( #result.rows > 0 ) then end
rs = result
if ( status ) then
for _, q in pairs(query) do
status, result = helper:Query( q )
if ( status ) then
-- Only the SELECT statement should produce output
if ( #result.rows > 0 ) then
rs = result
end
end
end end
end end
end
helper:Disconnect()
helper:Disconnect()
if ( status and rs) then
result = mssql.Util.FormatOutputTable( rs, true )
result.name = username
if ( RS_LIMIT > 0 ) then
result.name = result.name .. (" (Showing %d first results)"):format(RS_LIMIT)
end
table.insert( output, result )
end
end
return stdnse.format_output( true, output ) if ( status and rs ) then
result = mssql.Util.FormatOutputTable( rs, true )
result.name = username
if ( RS_LIMIT > 0 ) then
result.name = result.name .. (" (Showing %d first results)"):format(RS_LIMIT)
end
table.insert( output, result )
end
end
end
local instanceOutput = {}
instanceOutput["name"] = string.format( "[%s]", instance:GetName() )
table.insert( instanceOutput, output )
return instanceOutput
end 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

View File

@@ -1,170 +1,251 @@
-- -*- mode: lua -*-
-- vim: set filetype=lua :
description = [[ description = [[
Attempts to extract information from Microsoft SQL Server instances. Attempts to determine configuration and version information for Microsoft SQL
Server instances.
SQL Server credentials required: No (will not benefit from
<code>mssql.username</code> & <code>mssql.password</code>).
Run criteria:
* Host script: Will always run.
* Port script: N/A
NOTE: Unlike previous versions, this script will NOT attempt to log in to SQL
Server instances. Blank passwords can be checked using the
<code>ms-sql-empty-password</code> script. E.g.:
<code>nmap -sn --script ms-sql-empty-password --script-args mssql.instance-all <host></code>
The script uses two means of getting version information for SQL Server instances:
* Querying the SQL Server Browser service, which runs by default on UDP port
1434 on servers that have SQL Server 2000 or later installed. However, this
service may be disabled without affecting the functionality of the instances.
Additionally, it provides imprecise version information.
* Sending a probe to the instance, causing the instance to respond with
information including the exact version number. This is the same method that
Nmap uses for service versioning; however, this script can also do the same for
instances accessiable via Windows named pipes, and can target all of the
instances listed by the SQL Server Browser service.
In the event that the script can connect to the SQL Server Browser service
(UDP 1434) but is unable to connect directly to the instance to obtain more
accurate version information (because ports are blocked or the <code>mssql.scanned-ports-only</code>
argument has been used), the script will rely only upon the version number
provided by the SQL Server Browser/Monitor, which has the following limitations:
* For SQL Server 2000 and SQL Server 7.0 instances, the RTM version number is
always given, regardless of any service packs or patches installed.
* For SQL Server 2005 and later, the version number will reflect the service
pack installed, but the script will not be able to tell whether patches have
been installed.
Where possible, the script will determine major version numbers, service pack
levels and whether patches have been installed. However, in cases where
particular determinations can not be made, the script will report only what can
be confirmed.
NOTE: Communication with instances via named pipes depends on the <code>smb</code>
library. To communicate with (and possibly to discover) instances via named pipes,
the host must have at least one SMB port (e.g. TCP 445) that was scanned and
found to be open. Additionally, named pipe connections may require Windows
authentication to connect to the Windows host (via SMB) in addition to the
authentication required to connect to the SQL Server instances itself. See the
documentation and arguments for the <code>smb</code> library for more information.
NOTE: By default, the ms-sql-* scripts may attempt to connect to and communicate
with ports that were not included in the port list for the Nmap scan. This can
be disabled using the <code>mssql.scanned-ports-only</code> script argument.
]] ]]
---
-- @usage
-- nmap -p 445 --script ms-sql-info <host>
-- nmap -p 1433 --script ms-sql-info --script-args mssql.instance-port=1433 <host>
--
-- @output
-- | ms-sql-info:
-- | Windows server name: WINXP
-- | [192.168.100.128\PROD]
-- | Instance name: PROD
-- | Version: Microsoft SQL Server 2000 SP3
-- | Version number: 8.00.760
-- | Product: Microsoft SQL Server 2005
-- | Service pack level: SP3
-- | Post-SP patches applied: No
-- | TCP port: 1278
-- | Named pipe: \\192.168.100.128\pipe\MSSQL$PROD\sql\query
-- | Clustered: No
-- | [192.168.100.128\SQLFIREWALLED]
-- | Instance name: SQLFIREWALLED
-- | Version: Microsoft SQL Server 2008 RTM
-- | Product: Microsoft SQL Server 2008
-- | Service pack level: RTM
-- | TCP port: 4343
-- | Clustered: No
-- | [\\192.168.100.128\pipe\sql\query]
-- | Version: Microsoft SQL Server 2005 SP3+
-- | Version number: 9.00.4053
-- | Product: Microsoft SQL Server 2005
-- | Service pack level: SP3
-- | Post-SP patches applied: Yes
-- |_ Named pipe: \\192.168.100.128\pipe\sql\query
--
-- rev 1.0 (2007-06-09) -- rev 1.0 (2007-06-09)
-- rev 1.1 (2009-12-06 - Added SQL 2008 identification T Sellers) -- rev 1.1 (2009-12-06 - Added SQL 2008 identification T Sellers)
-- rev 1.2 (2010-10-03 - Added Broadcast support <patrik@cqure.net>) -- rev 1.2 (2010-10-03 - Added Broadcast support <patrik@cqure.net>)
-- rev 1.3 (2010-10-10 - Added prerule and newtargets support <patrik@cqure.net>) -- rev 1.3 (2010-10-10 - Added prerule and newtargets support <patrik@cqure.net>)
-- rev 1.4 (2011-01-24 - Revised logic in order to get version data without logging in;
-- added functionality to interpret version in terms of SP level, etc.
-- added script arg to prevent script from connecting to ports that
-- weren't in original Nmap scan <chris3E3@gmail.com>)
-- rev 1.5 (2011-02-01 - Moved discovery functionality into ms-sql-discover.nse and
-- broadcast-ms-sql-discovery.nse <chris3E3@gmail.com>)
author = "Thomas Buchanan" author = "Chris Woodbury, Thomas Buchanan"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html" license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"default", "discovery", "intrusive"} categories = {"default", "discovery", "safe"}
--- dependencies = {"ms-sql-discover"}
-- @output
-- PORT STATE SERVICE REASON
-- 1434/udp open ms-sql-m script-set
-- | ms-sql-info: Discovered Microsoft SQL Server 2008 Express Edition
-- | Server name: MAC-MINI
-- | Server version: 10.0.2531.0 (SP1)
-- | Instance name: SQLEXPRESS
-- | TCP Port: 1433
-- |_ Could not retrieve actual version information
--
require("shortport") require("shortport")
require("target")
require("mssql") require("mssql")
prerule = function() return false end hostrule = function(host)
portrule = shortport.portnumber({1433, 1434}, "udp", {"open", "open|filtered"}) if ( mssql.Helper.WasDiscoveryPerformed( host ) ) then
return mssql.Helper.GetDiscoveredInstances( host ) ~= nil
local parse_version = function(ver_str)
local version = {}
version.full = ver_str
version.product_long = ver_str:match("(Microsoft.-)\n") or ""
version.product = ver_str:match("^(Microsoft SQL Server %w-)%s") or ""
version.edition = ver_str:match("\n.-\n.-\n%s*(.-%sEdition)%s") or ""
version.edition_long = ver_str:match("\n.-\n.-\n%s*(.-Build.-)\n") or ""
version.version = ver_str:match("^Microsoft.-%-.-([%.%d+]+)") or ""
version.level = ver_str:match("^Microsoft.-%((.+)%)%s%-") or ""
version.windows = ver_str:match(" on%s(.*)\n$") or ""
version.real = true
return true, version
end
local function retrieve_version_as_user( info, user, pass )
local helper, status
local SQL_DB = "master"
if ( info.servername and info.port ) then
local hosts
status, hosts = nmap.resolve(info.servername, nmap.address_family())
if ( status ) then
local err
for _, host in ipairs( hosts ) do
helper = mssql.Helper:new()
status, err = helper:Connect(host, info.port)
if ( status ) then break end
end
-- we failed to connect to all of the resolved hostnames,
-- fall back to sql browser ip
if ( not(status) ) then
helper = mssql.Helper:new()
status, err = helper:Connect( info.ip, info.port )
end
else
-- resolve wasn't successful, fall back to browser service ip
stdnse.print_debug(3, "ERROR: Failed to resolve the hostname %s", info.servername)
helper = mssql.Helper:new()
status, err = helper:Connect( info.ip, info.port )
end
else else
-- we're missing either the servername or the port return true
return false, "ERROR: Either servername or tcp port is missing"
end
if ( not(status) ) then return false, "ERROR: Failed to connect to server" end
status, result = helper:Login( user, pass, SQL_DB, info.servername )
if ( not(status) ) then
stdnse.print_debug(3, "%s: login failed, reason: %s", SCRIPT_NAME, result )
return status, "Could not retrieve actual version information"
end end
local query = "SELECT @@version ver"
status, result = helper:Query( query )
if ( not(status) ) then
stdnse.print_debug(3, "%s: query failed, reason: %s", SCRIPT_NAME, result )
return status, "Could not retrieve actual version information"
end
helper:Disconnect()
if ( result.rows ) then return parse_version( result.rows[1][1] ) end
end end
local function process_response( serverInfo )
local SQL_USER, SQL_PASS = "sa", ""
local TABLE_DATA = {
["Server name"] = "info.servername",
["Server version"] = "version.version",
["Server edition"] = "version.edition_long",
["Clustered"] = "info.clustered",
["Named pipe"] = "info.pipe",
["Tcp port"] = "info.port",
}
local result = {} --- Adds a label and value to an output table. If the value is a boolean, it is
-- converted to Yes/No; if the value is nil, nothing is added to the table.
local function add_to_output_table( outputTable, outputLabel, outputData )
if outputData == nil then return end
for _, info in pairs(serverInfo) do if outputData == true then
local result_part = {} outputData = "Yes"
elseif outputData == false then
outputData = "No"
end
table.insert(outputTable, string.format( "%s: %s", outputLabel, outputData ) )
end
-- The browser service could point to instances on other IP's
-- therefore the correct behavior should be to connect to the --- Returns formatted output for the given version data
-- servername returned for the instance rather than the browser IP. local function create_version_output_table( versionInfo )
-- In case this fails, due to name resolution or something else, fall local versionOutput = {}
-- back to the browser service IP.
local status, version = retrieve_version_as_user(info, SQL_USER, SQL_PASS) versionOutput["name"] = "Version: " .. versionInfo:ToString()
if ( versionInfo.source ~= "SSRP" ) then
if (status) then add_to_output_table( versionOutput, "Version number", versionInfo.versionNumber )
if ( version.edition ) then end
version.product = version.product .. " " .. version.edition add_to_output_table( versionOutput, "Product", versionInfo.productName )
end add_to_output_table( versionOutput, "Service pack level", versionInfo.servicePackLevel )
version.version = version.version .. (" (%s)"):format(version.level) add_to_output_table( versionOutput, "Post-SP patches applied", versionInfo.patched )
return versionOutput
end
--- Returns formatted output for the given instance
local function create_instance_output_table( instance )
-- if we didn't get anything useful (due to errors or the port not actually
-- being SQL Server), don't report anything
if not ( instance.instanceName or instance.version ) then return nil end
local instanceOutput = {}
instanceOutput["name"] = string.format( "[%s]", instance:GetName() )
add_to_output_table( instanceOutput, "Instance name", instance.instanceName )
if instance.version then
local versionOutput = create_version_output_table( instance.version )
table.insert( instanceOutput, versionOutput )
end
if instance.port then add_to_output_table( instanceOutput, "TCP port", instance.port.number ) end
add_to_output_table( instanceOutput, "Named pipe", instance.pipeName )
add_to_output_table( instanceOutput, "Clustered", instance.isClustered )
return instanceOutput
end
--- Processes a single instance, attempting to determine its version, etc.
local function process_instance( instance )
local foundVersion = false
local ssnetlibVersion
-- If possible and allowed (see 'mssql.scanned-ports-only' argument), we'll
-- connect to the instance to get an accurate version number
if ( instance:HasNetworkProtocols() ) then
local ssnetlibVersion
foundVersion, ssnetlibVersion = mssql.Helper.GetInstanceVersion( instance )
if ( foundVersion ) then
instance.version = ssnetlibVersion
stdnse.print_debug( 1, "%s: Retrieved SSNetLib version for %s.", SCRIPT_NAME, instance:GetName() )
else else
status, version = mssql.Util.DecodeBrowserInfoVersion(info) stdnse.print_debug( 1, "%s: Could not retrieve SSNetLib version for %s.", SCRIPT_NAME, instance:GetName() )
end end
end
-- If we didn't get a version from SSNetLib, give the user some detail as to why
if ( not foundVersion ) then
if ( not instance:HasNetworkProtocols() ) then
stdnse.print_debug( 1, "%s: %s has no network protocols enabled.", SCRIPT_NAME, instance:GetName() )
end
if ( instance.version ) then
stdnse.print_debug( 1, "%s: Using version number from SSRP response for %s.", SCRIPT_NAME, instance:GetName() )
else
stdnse.print_debug( 1, "%s: Version info could not be retrieved for %s.", SCRIPT_NAME, instance:GetName() )
end
end
-- Give some version info back to Nmap
if ( instance.port and instance.version ) then
instance.version:PopulateNmapPortVersion( instance.port )
nmap.set_port_version( instance.host, instance.port, "hardmatched" )
end
-- format output end
for topic, varname in pairs(TABLE_DATA) do
local func = loadstring( "return " .. varname )
setfenv(func, setmetatable({ info=info; version=version; }, {__index = _G})) action = function( host )
local result = func() local scriptOutput = {}
if ( result ) then
table.insert( result_part, ("%s: %s"):format(topic, result) ) local status, instanceList = mssql.Helper.GetTargetInstances( host )
-- if no instances were targeted, then display info on all
if ( not status ) then
if ( not mssql.Helper.WasDiscoveryPerformed( host ) ) then
mssql.Helper.Discover( host )
end
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
table.insert(scriptOutput, string.format( "Windows server name: %s", instance.serverName ))
break
end end
end end
result_part.name = version.product for _, instance in pairs( instanceList ) do
process_instance( instance )
if ( version.real ) then local instanceOutput = create_instance_output_table( instance )
table.insert(result_part, "WARNING: Database was accessible as SA with empty password!") if instanceOutput then
table.insert( scriptOutput, instanceOutput )
end
end end
table.insert(result, { name = "Instance: " .. info.name, result_part } )
end end
return result
return stdnse.format_output( true, scriptOutput )
end end
action = function( host, port )
local status, response = mssql.Helper.Discover( host, port )
if ( not(status) ) then return end
local result, serverInfo = process_response( response[host.ip] )
if ( not(result) ) then return end
nmap.set_port_state( host, port, "open")
return stdnse.format_output( true, result )
end

View File

@@ -1,7 +1,54 @@
-- -*- mode: lua -*-
-- vim: set filetype=lua :
description = [[ description = [[
Runs a query against Microsoft SQL Server (ms-sql). Runs a query against Microsoft SQL Server (ms-sql).
SQL Server credentials required: Yes (use <code>ms-sql-brute</code>, <code>ms-sql-empty-password</code>
and/or <code>mssql.username</code> & <code>mssql.password</code>)
Run criteria:
* Host script: Will run if the <code>mssql.instance-all</code>, <code>mssql.instance-name</code>
or <code>mssql.instance-port</code> script arguments are used (see mssql.lua).
* Port script: Will run against any services identified as SQL Servers, but only
if the <code>mssql.instance-all</code>, <code>mssql.instance-name</code>
and <code>mssql.instance-port</code> script arguments are NOT used.
NOTE: Communication with instances via named pipes depends on the <code>smb</code>
library. To communicate with (and possibly to discover) instances via named pipes,
the host must have at least one SMB port (e.g. TCP 445) that was scanned and
found to be open. Additionally, named pipe connections may require Windows
authentication to connect to the Windows host (via SMB) in addition to the
authentication required to connect to the SQL Server instances itself. See the
documentation and arguments for the <code>smb</code> library for more information.
NOTE: By default, the ms-sql-* scripts may attempt to connect to and communicate
with ports that were not included in the port list for the Nmap scan. This can
be disabled using the <code>mssql.scanned-ports-only</code> script argument.
]] ]]
---
-- @usage
-- nmap -p 1433 --script ms-sql-query --script-args mssql.username=sa,mssql.password=sa,ms-sql-query.query="SELECT * FROM master..syslogins" <host>
--
-- @args ms-sql-query.query The query to run against the server.
-- (default: SELECT @@version version)
--
-- @output
-- | ms-sql-query:
-- | [192.168.100.25\MSSQLSERVER]
-- | Query: SELECT @@version version
-- | version
-- | =======
-- | Microsoft SQL Server 2005 - 9.00.3068.00 (Intel X86)
-- | Feb 26 2008 18:15:01
-- | Copyright (c) 1988-2005 Microsoft Corporation
-- |_ Express Edition on Windows NT 5.2 (Build 3790: Service Pack 2)
--
-- Created 01/17/2010 - v0.1 - created by Patrik Karlsson <patrik@cqure.net>
-- Revised 02/01/2011 - v0.2 - Added ability to run against all instances on a host;
-- added compatibility with changes in mssql.lua (Chris Woodbury)
author = "Patrik Karlsson" author = "Patrik Karlsson"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html" license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"discovery", "safe"} categories = {"discovery", "safe"}
@@ -10,84 +57,62 @@ require 'shortport'
require 'stdnse' require 'stdnse'
require 'mssql' require 'mssql'
dependencies = {"ms-sql-brute", "ms-sql-empty-password"} dependencies = {"ms-sql-brute", "ms-sql-empty-password", "ms-sql-discover"}
--- hostrule = mssql.Helper.GetHostrule_Standard()
-- @args ms-sql-query.query specifies the query to run against the server. portrule = mssql.Helper.GetPortrule_Standard()
-- (default SELECT @@version version)
--
-- @output
-- PORT STATE SERVICE
-- 1433/tcp open ms-sql-s
-- | ms-sql-query:
-- |
-- | Microsoft SQL Server 2005 - 9.00.3068.00 (Intel X86)
-- | Feb 26 2008 18:15:01
-- | Copyright (c) 1988-2005 Microsoft Corporation
-- |_ Express Edition on Windows NT 5.2 (Build 3790: Service Pack 2)
-- Version 0.1 ---
-- Created 01/17/2010 - v0.1 - created by Patrik Karlsson <patrik@cqure.net> local function process_instance( instance )
local status, result
portrule = shortport.port_or_service(1433, "ms-sql-s")
action = function( host, port )
local status, result, helper
local username = stdnse.get_script_args( 'mssql.username' )
local password = stdnse.get_script_args( 'mssql.password' ) or ""
-- the tempdb should be a safe guess, anyway the library is set up -- the tempdb should be a safe guess, anyway the library is set up
-- to continue even if the DB is not accessible to the user -- to continue even if the DB is not accessible to the user
local database = stdnse.get_script_args( 'mssql.database' ) or "tempdb" local database = stdnse.get_script_args( 'mssql.database' ) or "tempdb"
local query = stdnse.get_script_args( {'ms-sql-query.query', 'mssql-query.query' } ) or "SELECT @@version version" local query = stdnse.get_script_args( {'ms-sql-query.query', 'mssql-query.query' } ) or "SELECT @@version version"
local helper = mssql.Helper:new()
status, result = helper:ConnectEx( instance )
if ( not(username) and nmap.registry.mssqlusers ) then if status then
-- do we have a sysadmin? status, result = helper:LoginEx( instance, database )
if ( nmap.registry.mssqlusers.sa ) then if ( not(status) ) then result = "ERROR: " .. result end
username = "sa" end
password = nmap.registry.mssqlusers.sa if status then
else status, result = helper:Query( query )
-- ok were stuck with some n00b account, just get the first one if ( not(status) ) then result = "ERROR: " .. result end
for user, pass in pairs(nmap.registry.mssqlusers) do end
username = user
password = pass helper:Disconnect()
break
if status then
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
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 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
end end
-- If we don't have a valid username, simply fail silently return stdnse.format_output( true, scriptOutput )
if ( not(username) ) then
return
end
helper = mssql.Helper:new()
status, result = helper:Connect(host, port)
if ( not(status) ) then
return " \n\n" .. result
end
status, result = helper:Login( username, password, database, host.ip )
if ( not(status) ) then
return " \n\nERROR: " .. result
end
status, result = helper:Query( query )
helper:Disconnect()
if ( not(status) ) then
return " \n\nERROR: " .. result
end
result = mssql.Util.FormatOutputTable( result, true )
if ( not(nmap.registry.args['mssql-query.query']) ) then
table.insert(result, 1, query)
result = stdnse.format_output( true, result )
result = "(Use --script-args=mssql-query.query='<QUERY>' to change query.)" .. result
else
result = stdnse.format_output( true, result )
end
return result
end end

View File

@@ -1,10 +1,19 @@
-- -*- mode: lua -*-
-- vim: set filetype=lua :
description = [[ description = [[
Queries Microsoft SQL Server (ms-sql) for a list of tables per database. Queries Microsoft SQL Server (ms-sql) for a list of tables per database.
The sysdatabase table should be accessible by more or less everyone SQL Server credentials required: Yes (use <code>ms-sql-brute</code>, <code>ms-sql-empty-password</code>
The script attempts to use the sa account over any other if it has and/or <code>mssql.username</code> & <code>mssql.password</code>)
the password in the registry. If not the first account in the Run criteria:
registry is used. * Host script: Will run if the <code>mssql.instance-all</code>, <code>mssql.instance-name</code>
or <code>mssql.instance-port</code> script arguments are used (see mssql.lua).
* Port script: Will run against any services identified as SQL Servers, but only
if the <code>mssql.instance-all</code>, <code>mssql.instance-name</code>
and <code>mssql.instance-port</code> script arguments are NOT used.
The sysdatabase table should be accessible by more or less everyone.
Once we have a list of databases we iterate over it and attempt to extract Once we have a list of databases we iterate over it and attempt to extract
table names. In order for this to succeed we need to have either table names. In order for this to succeed we need to have either
@@ -13,27 +22,24 @@ database we successfully enumerate tables from we mark as finished, then
iterate over known user accounts until either we have exhausted the users iterate over known user accounts until either we have exhausted the users
or found all tables in all the databases. or found all tables in all the databases.
Tables installed by default are excluded. System databases are excluded.
NOTE: Communication with instances via named pipes depends on the <code>smb</code>
library. To communicate with (and possibly to discover) instances via named pipes,
the host must have at least one SMB port (e.g. TCP 445) that was scanned and
found to be open. Additionally, named pipe connections may require Windows
authentication to connect to the Windows host (via SMB) in addition to the
authentication required to connect to the SQL Server instances itself. See the
documentation and arguments for the <code>smb</code> library for more information.
NOTE: By default, the ms-sql-* scripts may attempt to connect to and communicate
with ports that were not included in the port list for the Nmap scan. This can
be disabled using the <code>mssql.scanned-ports-only</code> script argument.
]] ]]
author = "Patrik Karlsson"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"discovery", "safe"}
require 'shortport'
require 'stdnse'
require 'mssql'
dependencies = {"ms-sql-brute", "ms-sql-empty-password"}
--- ---
-- @args mssql.username specifies the username to use to connect to -- @usage
-- the server. This option overrides any accounts found by -- nmap -p 1433 --script ms-sql-tables --script-args mssql.username=sa,mssql.password=sa <host>
-- the <code>ms-sql-brute</code> and <code>ms-sql-empty-password</code> scripts.
--
-- @args mssql.password specifies the password to use to connect to
-- the server. This option overrides any accounts found by
-- the <code>ms-sql-brute</code> and <code>ms-sql-empty-password</code> scripts.
-- --
-- @args ms-sql-tables.maxdb Limits the amount of databases that are -- @args ms-sql-tables.maxdb Limits the amount of databases that are
-- processed and returned (default 5). If set to zero or less -- processed and returned (default 5). If set to zero or less
@@ -46,9 +52,8 @@ dependencies = {"ms-sql-brute", "ms-sql-empty-password"}
-- the keywords -- the keywords
-- --
-- @output -- @output
-- PORT STATE SERVICE
-- 1433/tcp open ms-sql-s
-- | ms-sql-tables: -- | ms-sql-tables:
-- | [192.168.100.25\MSSQLSERVER]
-- | webshop -- | webshop
-- | table column type length -- | table column type length
-- | payments user_id int 4 -- | payments user_id int 4
@@ -72,15 +77,28 @@ dependencies = {"ms-sql-brute", "ms-sql-empty-password"}
-- | users password varchar 50 -- | users password varchar 50
-- |_ users fullname varchar 100 -- |_ users fullname varchar 100
-- Version 0.1
-- Created 01/17/2010 - v0.1 - created by Patrik Karlsson <patrik@cqure.net> -- Created 01/17/2010 - v0.1 - created by Patrik Karlsson <patrik@cqure.net>
-- Revised 04/02/2010 - v0.2 -- Revised 04/02/2010 - v0.2
-- - Added support for filters -- - Added support for filters
-- - Changed output formatting of restrictions -- - Changed output formatting of restrictions
-- - Added parameter information in output if parameters are using their -- - Added parameter information in output if parameters are using their
-- defaults. -- defaults.
-- Revised 02/01/2011 - v0.3 (Chris Woodbury)
-- - Added ability to run against all instances on a host;
-- - Added compatibility with changes in mssql.lua
portrule = shortport.port_or_service(1433, "ms-sql-s") author = "Patrik Karlsson"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"discovery", "safe"}
require 'shortport'
require 'stdnse'
require 'mssql'
dependencies = {"ms-sql-brute", "ms-sql-empty-password", "ms-sql-discover"}
hostrule = mssql.Helper.GetHostrule_Standard()
portrule = mssql.Helper.GetPortrule_Standard()
local function table_contains( tbl, val ) local function table_contains( tbl, val )
for k,v in pairs(tbl) do for k,v in pairs(tbl) do
@@ -91,17 +109,15 @@ local function table_contains( tbl, val )
return false return false
end end
action = function( host, port )
local status, result, dbs, tables, helper local function process_instance( instance )
local username = stdnse.get_script_args( 'mssql.username' )
local password = stdnse.get_script_args( 'mssql.password' ) or "" local status, result, dbs, tables
local output = {} local output = {}
local exclude_dbs = { "'master'", "'tempdb'", "'model'", "'msdb'" } local exclude_dbs = { "'master'", "'tempdb'", "'model'", "'msdb'" }
local db_query local db_query
local done_dbs = {} local done_dbs = {}
local creds = {}
local db_limit, tbl_limit local db_limit, tbl_limit
local DB_COUNT = stdnse.get_script_args( {'ms-sql-tables.maxdb', 'mssql-tables.maxdb'} ) local DB_COUNT = stdnse.get_script_args( {'ms-sql-tables.maxdb', 'mssql-tables.maxdb'} )
@@ -122,8 +138,8 @@ action = function( host, port )
end end
-- Build the keyword filter -- Build the keyword filter
if ( nmap.registry.args['mssql-tables.keywords'] ) then if ( stdnse.get_script_args( {'ms-sql-tables.keywords', 'mssql-tables.keywords' } ) ) then
local keywords = nmap.registry.args['mssql-tables.keywords'] local keywords = stdnse.get_script_args( {'ms-sql-tables.keywords', 'mssql-tables.keywords' } )
local tmp_tbl = {} local tmp_tbl = {}
if( type(keywords) == 'string' ) then if( type(keywords) == 'string' ) then
@@ -142,110 +158,123 @@ action = function( host, port )
db_query = ("SELECT %s name from master..sysdatabases WHERE name NOT IN (%s)"):format(db_limit, stdnse.strjoin(",", exclude_dbs)) db_query = ("SELECT %s name from master..sysdatabases WHERE name NOT IN (%s)"):format(db_limit, stdnse.strjoin(",", exclude_dbs))
if ( username ) then
creds[username] = password
elseif ( not(username) and nmap.registry.mssqlusers ) then
-- do we have a sysadmin?
if ( nmap.registry.mssqlusers.sa ) then
creds["sa"] = nmap.registry.mssqlusers.sa
else
creds = nmap.registry.mssqlusers
end
end
-- If we don't have valid creds, simply fail silently
if ( not(creds) ) then
return
end
for username, password in pairs( creds ) do
helper = mssql.Helper:new()
status, result = helper:Connect(host, port)
if ( not(status) ) then
return " \n\n" .. result
end
status, result = helper:Login( username, password, nil, host.ip )
if ( not(status) ) then
stdnse.print_debug("ERROR: %s", result)
break
end
status, dbs = helper:Query( db_query ) local creds = mssql.Helper.GetLoginCredentials_All( instance )
if ( not creds ) then
if ( status ) then output = "ERROR: No login credentials."
-- all done? else
if ( #done_dbs == #dbs.rows ) then for username, password in pairs( creds ) do
local helper = mssql.Helper:new()
status, result = helper:ConnectEx( instance )
if ( not(status) ) then
table.insert(output, "ERROR: " .. result)
break break
end end
for k, v in pairs(dbs.rows) do if ( status ) then
if ( not( table_contains( done_dbs, v[1] ) ) ) then status = helper:Login( username, password, nil, instance.host.ip )
query = [[ SELECT so.name 'table', sc.name 'column', st.name 'type', sc.length end
FROM %s..syscolumns sc, %s..sysobjects so, %s..systypes st
WHERE so.id = sc.id AND sc.xtype=st.xtype AND if ( status ) then
so.id IN (SELECT %s id FROM %s..sysobjects WHERE xtype='U') %s ORDER BY so.name, sc.name, st.name]] status, dbs = helper:Query( db_query )
query = query:format( v[1], v[1], v[1], tbl_limit, v[1], keywords_filter) end
status, tables = helper:Query( query )
if ( not(status) ) then if ( status ) then
stdnse.print_debug(tables) -- all done?
else if ( #done_dbs == #dbs.rows ) then
local item = {} break
item = mssql.Util.FormatOutputTable( tables, true ) end
if ( #item == 0 and keywords_filter ~= "" ) then
table.insert(item, "Filter returned no matches") for k, v in pairs(dbs.rows) do
if ( not( table_contains( done_dbs, v[1] ) ) ) then
query = [[ SELECT so.name 'table', sc.name 'column', st.name 'type', sc.length
FROM %s..syscolumns sc, %s..sysobjects so, %s..systypes st
WHERE so.id = sc.id AND sc.xtype=st.xtype AND
so.id IN (SELECT %s id FROM %s..sysobjects WHERE xtype='U') %s ORDER BY so.name, sc.name, st.name]]
query = query:format( v[1], v[1], v[1], tbl_limit, v[1], keywords_filter)
status, tables = helper:Query( query )
if ( not(status) ) then
stdnse.print_debug(tables)
else
local item = {}
item = mssql.Util.FormatOutputTable( tables, true )
if ( #item == 0 and keywords_filter ~= "" ) then
table.insert(item, "Filter returned no matches")
end
item.name = v[1]
table.insert(output, item)
table.insert(done_dbs, v[1])
end end
item.name = v[1]
table.insert(output, item)
table.insert(done_dbs, v[1])
end end
end end
end end
end helper:Disconnect()
helper:Disconnect() end
end
local pos = 1 local pos = 1
local restrict_tbl = {} local restrict_tbl = {}
if ( stdnse.get_script_args( {'ms-sql-tables.keywords', 'mssql-tables.keywords' } ) ) then
tmp = stdnse.get_script_args( {'ms-sql-tables.keywords', 'mssql-tables.keywords' } )
if ( type(tmp) == 'table' ) then
tmp = stdnse.strjoin(',', tmp)
end
table.insert(restrict_tbl, 1, ("Filter: %s"):format(tmp))
pos = pos + 1
else
table.insert(restrict_tbl, 1, "No filter (see ms-sql-tables.keywords)")
end
if ( DB_COUNT > 0 ) then
local tmp = ("Output restricted to %d databases"):format(DB_COUNT)
if ( not(stdnse.get_script_args( { 'ms-sql-tables.maxdb', 'mssql-tables.maxdb' } ) ) ) then
tmp = tmp .. " (see ms-sql-tables.maxdb)"
end
table.insert(restrict_tbl, 1, tmp)
pos = pos + 1
end
if ( TABLE_COUNT > 0 ) then
local tmp = ("Output restricted to %d tables"):format(TABLE_COUNT)
if ( not(stdnse.get_script_args( { 'ms-sql-tables.maxtables', 'mssql-tables.maxtables' } ) ) ) then
tmp = tmp .. " (see ms-sql-tables.maxtables)"
end
table.insert(restrict_tbl, 1, tmp)
pos = pos + 1
end
if ( 1 < pos and #output > 0) then
restrict_tbl.name = "Restrictions"
table.insert(output, "")
table.insert(output, restrict_tbl)
end
output = stdnse.format_output( true, output )
return output if ( stdnse.get_script_args( {'ms-sql-tables.keywords', 'mssql-tables.keywords' } ) ) then
tmp = stdnse.get_script_args( {'ms-sql-tables.keywords', 'mssql-tables.keywords' } )
if ( type(tmp) == 'table' ) then
tmp = stdnse.strjoin(',', tmp)
end
table.insert(restrict_tbl, 1, ("Filter: %s"):format(tmp))
pos = pos + 1
else
table.insert(restrict_tbl, 1, "No filter (see ms-sql-tables.keywords)")
end
if ( DB_COUNT > 0 ) then
local tmp = ("Output restricted to %d databases"):format(DB_COUNT)
if ( not(stdnse.get_script_args( { 'ms-sql-tables.maxdb', 'mssql-tables.maxdb' } ) ) ) then
tmp = tmp .. " (see ms-sql-tables.maxdb)"
end
table.insert(restrict_tbl, 1, tmp)
pos = pos + 1
end
if ( TABLE_COUNT > 0 ) then
local tmp = ("Output restricted to %d tables"):format(TABLE_COUNT)
if ( not(stdnse.get_script_args( { 'ms-sql-tables.maxtables', 'mssql-tables.maxtables' } ) ) ) then
tmp = tmp .. " (see ms-sql-tables.maxtables)"
end
table.insert(restrict_tbl, 1, tmp)
pos = pos + 1
end
if ( 1 < pos and type( output ) == "table" and #output > 0) then
restrict_tbl.name = "Restrictions"
table.insert(output, "")
table.insert(output, restrict_tbl)
end
end
local instanceOutput = {}
instanceOutput["name"] = string.format( "[%s]", instance:GetName() )
table.insert( instanceOutput, output )
return instanceOutput
end 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

View File

@@ -1,17 +1,80 @@
-- -*- mode: lua -*-
-- vim: set filetype=lua :
description = [[ description = [[
Attempts to run a command using the command shell of Microsoft SQL Attempts to run a command using the command shell of Microsoft SQL
Server (ms-sql). Server (ms-sql).
SQL Server credentials required: Yes (use <code>ms-sql-brute</code>, <code>ms-sql-empty-password</code>
and/or <code>mssql.username</code> & <code>mssql.password</code>)
Run criteria:
* Host script: Will run if the <code>mssql.instance-all</code>, <code>mssql.instance-name</code>
or <code>mssql.instance-port</code> script arguments are used (see mssql.lua).
* Port script: Will run against any services identified as SQL Servers, but only
if the <code>mssql.instance-all</code>, <code>mssql.instance-name</code>
and <code>mssql.instance-port</code> script arguments are NOT used.
The script needs an account with the sysadmin server role to work. The script needs an account with the sysadmin server role to work.
It needs to be fed credentials through the script arguments or from
the scripts <code>ms-sql-brute</code> or
<code>ms-sql-empty-password</code>.
When run, the script iterates over the credentials and attempts to run When run, the script iterates over the credentials and attempts to run
the command until either all credentials are exhausted or until the the command until either all credentials are exhausted or until the
command is executed. command is executed.
NOTE: Communication with instances via named pipes depends on the <code>smb</code>
library. To communicate with (and possibly to discover) instances via named pipes,
the host must have at least one SMB port (e.g. TCP 445) that was scanned and
found to be open. Additionally, named pipe connections may require Windows
authentication to connect to the Windows host (via SMB) in addition to the
authentication required to connect to the SQL Server instances itself. See the
documentation and arguments for the <code>smb</code> library for more information.
NOTE: By default, the ms-sql-* scripts may attempt to connect to and communicate
with ports that were not included in the port list for the Nmap scan. This can
be disabled using the <code>mssql.scanned-ports-only</code> script argument.
]] ]]
---
-- @usage
-- nmap -p 445 --script ms-sql-discover,ms-sql-empty-password,ms-sql-xp-cmdshell <host>
-- nmap -p 1433 --script ms-sql-xp-cmdshell --script-args mssql.username=sa,mssql.password=sa,ms-sql-xp-cmdshell.cmd="net user test test /add" <host>
--
-- @args ms-sql-xp-cmdshell.cmd The OS command to run (default: ipconfig /all).
--
-- @output
-- | ms-sql-xp-cmdshell:
-- | [192.168.56.3\MSSQLSERVER]
-- | Command: ipconfig /all
-- | output
-- | ======
-- |
-- | Windows IP Configuration
-- |
-- | Host Name . . . . . . . . . . . . : EDUSRV011
-- | Primary Dns Suffix . . . . . . . : cqure.net
-- | Node Type . . . . . . . . . . . . : Unknown
-- | IP Routing Enabled. . . . . . . . : No
-- | WINS Proxy Enabled. . . . . . . . : No
-- | DNS Suffix Search List. . . . . . : cqure.net
-- |
-- | Ethernet adapter Local Area Connection 3:
-- |
-- | Connection-specific DNS Suffix . :
-- | Description . . . . . . . . . . . : AMD PCNET Family PCI Ethernet Adapter #2
-- | Physical Address. . . . . . . . . : 08-00-DE-AD-C0-DE
-- | DHCP Enabled. . . . . . . . . . . : Yes
-- | Autoconfiguration Enabled . . . . : Yes
-- | IP Address. . . . . . . . . . . . : 192.168.56.3
-- | Subnet Mask . . . . . . . . . . . : 255.255.255.0
-- | Default Gateway . . . . . . . . . :
-- | DHCP Server . . . . . . . . . . . : 192.168.56.2
-- | Lease Obtained. . . . . . . . . . : den 21 mars 2010 00:12:10
-- | Lease Expires . . . . . . . . . . : den 21 mars 2010 01:12:10
-- |_
-- Created 01/17/2010 - v0.1 - created by Patrik Karlsson <patrik@cqure.net>
-- Revised 02/01/2011 - v0.2 - Added ability to run against all instances on a host;
-- added compatibility with changes in mssql.lua (Chris Woodbury)
author = "Patrik Karlsson" author = "Patrik Karlsson"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html" license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"intrusive"} categories = {"intrusive"}
@@ -20,128 +83,81 @@ require 'shortport'
require 'stdnse' require 'stdnse'
require 'mssql' require 'mssql'
dependencies = {"ms-sql-brute", "ms-sql-empty-password"} dependencies = {"ms-sql-brute", "ms-sql-empty-password", "ms-sql-discover"}
---
-- @args mssql.username specifies the username to use to connect to
-- the server. This option overrides any accounts found by
-- the <code>ms-sql-brute</code> and <code>ms-sql-empty-password</code> scripts.
--
-- @args mssql.password specifies the password to use to connect to
-- the server. This option overrides any accounts found by
-- the <code>ms-sql-brute</code> and <code>ms-sql-empty-password</code> scripts.
--
-- @args ms-sql-xp-cmdshell.cmd specifies the OS command to run.
-- (default is ipconfig /all)
--
-- @output
-- PORT STATE SERVICE
-- 1433/tcp open ms-sql-s
-- | ms-sql-xp-cmdshell:
-- | Command: ipconfig /all; User: sa
-- | output
-- |
-- | Windows IP Configuration
-- |
-- | Host Name . . . . . . . . . . . . : EDUSRV011
-- | Primary Dns Suffix . . . . . . . : cqure.net
-- | Node Type . . . . . . . . . . . . : Unknown
-- | IP Routing Enabled. . . . . . . . : No
-- | WINS Proxy Enabled. . . . . . . . : No
-- | DNS Suffix Search List. . . . . . : cqure.net
-- |
-- | Ethernet adapter Local Area Connection 3:
-- |
-- | Connection-specific DNS Suffix . :
-- | Description . . . . . . . . . . . : AMD PCNET Family PCI Ethernet Adapter #2
-- | Physical Address. . . . . . . . . : 08-00-DE-AD-C0-DE
-- | DHCP Enabled. . . . . . . . . . . : Yes
-- | Autoconfiguration Enabled . . . . : Yes
-- | IP Address. . . . . . . . . . . . : 192.168.56.3
-- | Subnet Mask . . . . . . . . . . . : 255.255.255.0
-- | Default Gateway . . . . . . . . . :
-- | DHCP Server . . . . . . . . . . . : 192.168.56.2
-- | Lease Obtained. . . . . . . . . . : den 21 mars 2010 00:12:10
-- | Lease Expires . . . . . . . . . . : den 21 mars 2010 01:12:10
-- |_
-- Version 0.1 hostrule = mssql.Helper.GetHostrule_Standard()
-- Created 01/17/2010 - v0.1 - created by Patrik Karlsson <patrik@cqure.net> portrule = mssql.Helper.GetPortrule_Standard()
portrule = shortport.port_or_service(1433, "ms-sql-s")
local function table_contains( tbl, val ) local function process_instance( instance )
for k,v in pairs(tbl) do
if ( v == val ) then
return true
end
end
return false
end
action = function( host, port ) local status, result
local status, result, helper
local username = stdnse.get_script_args( 'mssql.username' )
local password = stdnse.get_script_args( 'mssql.password' ) or ""
local creds
local query local query
local cmd = stdnse.get_script_args( {'ms-sql-xp-cmdshell.cmd', 'mssql-xp-cmdshell.cmd'} ) or 'ipconfig /all' local cmd = stdnse.get_script_args( {'ms-sql-xp-cmdshell.cmd', 'mssql-xp-cmdshell.cmd'} ) or 'ipconfig /all'
local output = {} local output = {}
query = ("EXEC master..xp_cmdshell '%s'"):format(cmd) query = ("EXEC master..xp_cmdshell '%s'"):format(cmd)
if ( username ) then local creds = mssql.Helper.GetLoginCredentials_All( instance )
creds = {} if ( not creds ) then
creds[username] = password output = "ERROR: No login credentials."
elseif ( not(username) and nmap.registry.mssqlusers ) then else
-- do we have a sysadmin? for username, password in pairs( creds ) do
creds = {} local helper = mssql.Helper:new()
if ( nmap.registry.mssqlusers.sa ) then status, result = helper:ConnectEx( instance )
creds["sa"] = nmap.registry.mssqlusers.sa if ( not(status) ) then
else output = "ERROR: " .. result
creds = nmap.registry.mssqlusers break
end end
if ( status ) then
status = helper:Login( username, password, nil, instance.host.ip )
end
if ( status ) then
status, result = helper:Query( query )
end
helper:Disconnect()
if ( status ) then
output = mssql.Util.FormatOutputTable( result, true )
output[ "name" ] = string.format( "Command: %s", cmd )
break
elseif ( result and result:gmatch("xp_configure") ) then
if( nmap.verbosity() > 1 ) then
output = "Procedure xp_cmdshell disabled. For more information see \"Surface Area Configuration\" in Books Online."
end
end
end
end end
-- If we don't have valid creds, simply fail silently local instanceOutput = {}
if ( not(creds) ) then instanceOutput["name"] = string.format( "[%s]", instance:GetName() )
return table.insert( instanceOutput, output )
end
for username, password in pairs( creds ) do return instanceOutput
helper = mssql.Helper:new()
status, result = helper:Connect(host, port)
if ( not(status) ) then
return " \n\n" .. result
end
status, result = helper:Login( username, password, nil, host.ip )
if ( not(status) ) then
stdnse.print_debug("ERROR: %s", result)
break
end
status, result = helper:Query( query )
helper:Disconnect()
if ( status ) then
output = mssql.Util.FormatOutputTable( result, true )
if ( not(stdnse.get_script_args( {'ms-sql-xp-cmdshell.cmd', 'mssql-xp-cmdshell.cmd'} ) ) ) then
table.insert(output, 1, cmd)
output = stdnse.format_output( true, output )
output = "(Use --script-args=ms-sql-xp-cmdshell.cmd='<CMD>' to change command.)" .. output
else
output = stdnse.format_output( true, output )
end
break
elseif ( result:gmatch("xp_configure") ) then
if( nmap.verbosity() > 1 ) then
return " \nProcedure xp_cmdshell disabled, for more information see \"Surface Area Configuration\" in Books Online."
end
end
end
return output
end 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
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
end
return stdnse.format_output( true, scriptOutput )
end