1
0
mirror of https://github.com/nmap/nmap.git synced 2025-12-09 14:11: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-*-
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
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 = {}
status_codes = {}
status_names = {}
filetype_codes = {}
filetype_names = {}
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
if(log_errors == nil or log_errors == true) 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
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
@@ -1378,7 +1380,7 @@ local function start_session_extended(smb, log_errors, overrides)
else
-- Display a message to the user, and try the next account
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
-- Go to the next account
@@ -1763,7 +1765,9 @@ function read_file(smb, offset, count, overrides)
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]"
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)
end
@@ -1775,6 +1779,7 @@ function read_file(smb, offset, count, overrides)
response['remaining'] = remaining
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
@@ -3703,3 +3708,174 @@ for i, v in pairs(status_codes) do
status_names[v] = i
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 = [[
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>
-- Revised 02/01/2011 - v0.2 - Added compatibility with changes in mssql.lua (Chris Woodbury)
author = "Patrik Karlsson"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"broadcast"}
categories = {"broadcast", "safe", "discovery"}
require 'mssql'
require 'target'
require 'stdnse'
prerule = function() return true 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()
local OUTPUT_TBL = {
["Server name"] = "info.servername",
["Version"] = "version.version",
["Clustered"] = "info.clustered",
["Named pipe"] = "info.pipe",
["Tcp port"] = "info.port"
}
local host = { ip = "255.255.255.255" }
local port = { number = 1434, protocol = "udp" }
local status, result = mssql.Helper.Discover("255.255.255.255", 1434, true)
local status, result = mssql.Helper.DiscoverBySsrp(host, port, true)
if ( not(status) ) then return end
local results = {}
for ip, instances in pairs(result) do
local result_part = {}
if target.ALLOW_NEW_TARGETS then target.add(ip) end
for name, info in pairs(instances) do
local instance = {}
local version
status, version = mssql.Util.DecodeBrowserInfoVersion(info)
local scriptOutput = {}
for ip, instanceList in pairs(result) do
local serverOutput, serverName = {}, nil
target.add( ip )
for _, instance in ipairs( instanceList ) do
serverName = serverName or instance.serverName
local instanceOutput = create_instance_output_table( instance )
table.insert(serverOutput, instanceOutput)
end
serverOutput.name = string.format( "%s (%s)", ip, serverName )
table.insert( scriptOutput, serverOutput )
end
return stdnse.format_output( true, scriptOutput )
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
result_part.name = ip
table.insert( results, result_part )
end
return stdnse.format_output( true, results )
end

View File

@@ -1,79 +1,301 @@
-- -*- mode: lua -*-
-- vim: set filetype=lua :
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"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"auth", "intrusive"}
dependencies = {"ms-sql-discover", "ms-sql-empty-password"}
require 'shortport'
require 'stdnse'
require 'mssql'
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
-- Created 01/17/2010 - v0.1 - created by Patrik Karlsson <patrik@cqure.net>
hostrule = mssql.Helper.GetHostrule_Standard()
portrule = mssql.Helper.GetPortrule_Standard()
portrule = shortport.port_or_service(1433, "ms-sql-s")
action = function( host, port )
--- Returns formatted output for the given instance
local function create_instance_output_table( instance )
local result, response, status = {}, nil, nil
local valid_accounts = {}
local usernames, passwords
local username, password
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
return " \n\nFailed to load usernames.lst"
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
return " \n\nFailed to load usernames.lst"
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
status, result = helper:Connect(host, port)
if( not(status) ) then
return " \n\n" .. result
stopUser, stopInstance = test_credentials( instance, helper, username, password )
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
passwords("reset")
end
local output = stdnse.format_output(true, valid_accounts)
return output
end
end
-- The password testing has been finished. Unlock the mutex.
mutex( "done" )
return create_instance_output_table( instance )
end
action = function( host, port )
local scriptOutput = {}
local status, instanceList = mssql.Helper.GetTargetInstances( host, port )
local domain, bruteWindows = stdnse.get_script_args("mssql.domain", "ms-sql-brute.brute-windows-accounts")
if ( domain and not(bruteWindows) ) then
local ret = "\n " ..
"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
end
return stdnse.format_output( true, scriptOutput )
end

View File

@@ -1,33 +1,41 @@
-- -*- mode: lua -*-
-- vim: set filetype=lua :
description = [[
Queries Microsoft SQL Server (ms-sql) for a list of databases, linked
servers, and configuration settings.
Queries Microsoft SQL Server (ms-sql) instances for a list of databases, linked servers,
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.
]]
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
-- the server. This option overrides any accounts found by
-- the mssql-brute and mssql-empty-password scripts.
-- @usage
-- nmap -p 1433 --script ms-sql-config --script-args mssql.username=sa,mssql.password=sa <host>
--
-- @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.
-- @args ms-sql-config.showall If set, shows all configuration options.
--
-- @output
-- PORT STATE SERVICE
-- 1433/tcp open ms-sql-s
-- | ms-sql-config:
-- | [192.168.100.25\MSSQLSERVER]
-- | Databases
-- | name db_size owner
-- | ==== ======= =====
@@ -46,22 +54,37 @@ dependencies = {"ms-sql-brute", "ms-sql-empty-password"}
-- | srvname srvproduct providername
-- | ======= ========== ============
-- |_ MAC-MINI SQL Server SQLOLEDB
--
-- Version 0.1
-- 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)
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"}
action = function( host, port )
require 'shortport'
require 'stdnse'
require 'mssql'
local status, helper, response
local username = stdnse.get_script_args( 'mssql.username' )
local password = stdnse.get_script_args( 'mssql.password' ) or ""
dependencies = {"ms-sql-brute", "ms-sql-empty-password", "ms-sql-discover"}
hostrule = mssql.Helper.GetHostrule_Standard()
portrule = mssql.Helper.GetPortrule_Standard()
--- Processes a set of instances
local function process_instance( instance )
local status, errorMessage
local result, result_part = {}, {}
local conf_filter = stdnse.get_script_args( {'mssql-config.showall', 'ms-sql-config.showall'} ) and ""
or " WHERE configuration_id > 16384"
local db_filter = stdnse.get_script_args( {'mssql-config.showall', 'ms-sql-config.showall'} ) and ""
or " WHERE name NOT IN ('master','model','tempdb','msdb')"
local helper = mssql.Helper:new()
local queries = {
[2]={ ["Configuration"] = [[ SELECT name,
@@ -77,46 +100,25 @@ action = function( host, port )
INSERT INTO #nmap_dbs EXEC sp_helpdb
SELECT name, db_size, owner
FROM #nmap_dbs ]] .. db_filter .. [[
DROP DATABASE #nmap_dbs ]] }
DROP TABLE #nmap_dbs ]] }
}
if ( not(username) and nmap.registry.mssqlusers ) then
-- do we have a sysadmin?
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
status, errorMessage = helper:ConnectEx( instance )
if ( not(status) ) then result = "ERROR: " .. errorMessage end
-- If we don't have a valid username, simply fail silently
if ( not(username) ) then
return
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
if status then
status, errorMessage = helper:LoginEx( instance )
if ( not(status) ) then result = "ERROR: " .. errorMessage end
end
for _, v in ipairs( queries ) do
if ( not status ) then break end
for header, query in pairs(v) do
status, result_part = helper:Query( query )
if ( not(status) ) then
return " \n\nERROR: " .. result_part
result = "ERROR: " .. result_part
break
end
result_part = mssql.Util.FormatOutputTable( result_part, true )
result_part.name = header
@@ -126,6 +128,28 @@ action = function( host, port )
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

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 = [[
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"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"auth","intrusive"}
dependencies = {"ms-sql-discover"}
require 'shortport'
require 'stdnse'
require 'mssql'
---
--
-- @output
-- PORT STATE SERVICE
-- 1433/tcp open ms-sql-s
-- | ms-sql-empty-password:
-- |_ sa:<empty> => Login Correct
--
--
hostrule = mssql.Helper.GetHostrule_Standard()
portrule = mssql.Helper.GetPortrule_Standard()
-- Version 0.1
-- Created 01/17/2010 - v0.1 - created by Patrik Karlsson <patrik@cqure.net>
local function test_credentials( instance, helper, username, password )
local database = "tempdb"
portrule = shortport.port_or_service(1433, "ms-sql-s")
action = function( host, port )
local helper, status, result
local username, password, database, valid_accounts = "sa", "", "tempdb", {}
helper = mssql.Helper:new()
status, result = helper:Connect(host, port)
if( not(status) ) then
return " \n\n" .. result
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
status, result = helper:Login( username, password, database, host.ip )
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
table.insert( valid_accounts, string.format("%s:%s => Login Success", username, password:len()>0 and password or "<empty>" ) )
end
end
end
return stdnse.format_output(true, valid_accounts)
--- 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
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,16 +1,68 @@
-- -*- mode: lua -*-
-- vim: set filetype=lua :
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.
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.
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
the command until either all credentials are exhausted or until the
command is executed.
the command for each available set of credentials.
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"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"auth", "discovery","safe"}
@@ -19,54 +71,16 @@ 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
-- 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
dependencies = {"ms-sql-brute", "ms-sql-empty-password", "ms-sql-discover"}
-- 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 username = stdnse.get_script_args('mssql.username')
local password = stdnse.get_script_args('mssql.password') or ""
local creds
local status, result, rs
local query, limit
local output = {}
local exclude_dbs = { "'master'", "'tempdb'", "'model'", "'msdb'" }
@@ -90,32 +104,23 @@ action = function( host, port )
("SELECT %s dbname, owner FROM #hasaccess WHERE dbname NOT IN(%s)"):format(limit, stdnse.strjoin(",", exclude_dbs)),
"DROP TABLE #hasaccess" }
if ( username ) then
creds = {}
creds[username] = password
elseif ( not(username) and nmap.registry.mssqlusers ) then
-- do we have a sysadmin?
creds = nmap.registry.mssqlusers
end
-- If we don't have valid creds, simply fail silently
if ( not(creds) ) then
return
end
local creds = mssql.Helper.GetLoginCredentials_All( instance )
if ( not creds ) then
output = "ERROR: No login credentials."
else
for username, password in pairs( creds ) do
helper = mssql.Helper:new()
status, result = helper:Connect(host, port)
local helper = mssql.Helper:new()
status, result = helper:ConnectEx( instance )
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)
output = "ERROR: " .. result
break
end
if ( status ) then
status = helper:Login( username, password, nil, instance.host.ip )
end
if ( status ) then
for _, q in pairs(query) do
status, result = helper:Query( q )
if ( status ) then
@@ -125,6 +130,7 @@ action = function( host, port )
end
end
end
end
helper:Disconnect()
@@ -137,7 +143,32 @@ action = function( host, port )
table.insert( output, result )
end
end
end
return stdnse.format_output( true, output )
local instanceOutput = {}
instanceOutput["name"] = string.format( "[%s]", instance:GetName() )
table.insert( instanceOutput, output )
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

View File

@@ -1,170 +1,251 @@
-- -*- mode: lua -*-
-- vim: set filetype=lua :
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.1 (2009-12-06 - Added SQL 2008 identification T Sellers)
-- 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.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"
categories = {"default", "discovery", "intrusive"}
categories = {"default", "discovery", "safe"}
---
-- @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
--
dependencies = {"ms-sql-discover"}
require("shortport")
require("target")
require("mssql")
prerule = function() return false end
portrule = shortport.portnumber({1433, 1434}, "udp", {"open", "open|filtered"})
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
hostrule = function(host)
if ( mssql.Helper.WasDiscoveryPerformed( host ) ) then
return mssql.Helper.GetDiscoveredInstances( host ) ~= nil
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 )
return true
end
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 return end
if outputData == true then
outputData = "Yes"
elseif outputData == false then
outputData = "No"
end
table.insert(outputTable, string.format( "%s: %s", outputLabel, outputData ) )
end
--- Returns formatted output for the given version data
local function create_version_output_table( versionInfo )
local versionOutput = {}
versionOutput["name"] = "Version: " .. versionInfo:ToString()
if ( versionInfo.source ~= "SSRP" ) then
add_to_output_table( versionOutput, "Version number", versionInfo.versionNumber )
end
add_to_output_table( versionOutput, "Product", versionInfo.productName )
add_to_output_table( versionOutput, "Service pack level", versionInfo.servicePackLevel )
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
-- we're missing either the servername or the port
return false, "ERROR: Either servername or tcp port is missing"
stdnse.print_debug( 1, "%s: Could not retrieve SSNetLib version for %s.", SCRIPT_NAME, instance:GetName() )
end
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"
-- 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
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
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 = {}
for _, info in pairs(serverInfo) do
local result_part = {}
-- The browser service could point to instances on other IP's
-- therefore the correct behavior should be to connect to the
-- servername returned for the instance rather than the browser IP.
-- In case this fails, due to name resolution or something else, fall
-- back to the browser service IP.
local status, version = retrieve_version_as_user(info, SQL_USER, SQL_PASS)
if (status) then
if ( version.edition ) then
version.product = version.product .. " " .. version.edition
end
version.version = version.version .. (" (%s)"):format(version.level)
if ( instance.version ) then
stdnse.print_debug( 1, "%s: Using version number from SSRP response for %s.", SCRIPT_NAME, instance:GetName() )
else
status, version = mssql.Util.DecodeBrowserInfoVersion(info)
stdnse.print_debug( 1, "%s: Version info could not be retrieved for %s.", SCRIPT_NAME, instance:GetName() )
end
end
-- format output
for topic, varname in pairs(TABLE_DATA) do
local func = loadstring( "return " .. varname )
setfenv(func, setmetatable({ info=info; version=version; }, {__index = _G}))
local result = func()
if ( result ) then
table.insert( result_part, ("%s: %s"):format(topic, result) )
end
end
result_part.name = version.product
if ( version.real ) then
table.insert(result_part, "WARNING: Database was accessible as SA with empty password!")
-- 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
table.insert(result, { name = "Instance: " .. info.name, result_part } )
end
return result
end
action = function( host, port )
action = function( host )
local scriptOutput = {}
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 )
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
for _, instance in pairs( instanceList ) do
process_instance( instance )
local instanceOutput = create_instance_output_table( instance )
if instanceOutput then
table.insert( scriptOutput, instanceOutput )
end
end
end
return stdnse.format_output( true, scriptOutput )
end

View File

@@ -1,7 +1,54 @@
-- -*- mode: lua -*-
-- vim: set filetype=lua :
description = [[
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"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"discovery", "safe"}
@@ -10,84 +57,62 @@ require 'shortport'
require 'stdnse'
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()
portrule = mssql.Helper.GetPortrule_Standard()
---
-- @args ms-sql-query.query specifies the query to run against the server.
-- (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>
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 ""
local function process_instance( instance )
local status, result
-- 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
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 helper = mssql.Helper:new()
if ( not(username) and nmap.registry.mssqlusers ) then
-- do we have a sysadmin?
if ( nmap.registry.mssqlusers.sa ) then
username = "sa"
password = nmap.registry.mssqlusers.sa
else
-- ok were stuck with some n00b account, just get the first one
for user, pass in pairs(nmap.registry.mssqlusers) do
username = user
password = pass
break
end
end
end
status, result = helper:ConnectEx( instance )
-- If we don't have a valid username, simply fail silently
if ( not(username) ) then
return
if status then
status, result = helper:LoginEx( instance, database )
if ( not(status) ) then result = "ERROR: " .. result end
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
if status then
status, result = helper:Query( query )
if ( not(status) ) then result = "ERROR: " .. result end
end
helper:Disconnect()
if ( not(status) ) then
return " \n\nERROR: " .. result
end
if status then
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
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
result = stdnse.format_output( true, result )
for _, instance in pairs( instanceList ) do
local instanceOutput = process_instance( instance )
if instanceOutput then
table.insert( scriptOutput, instanceOutput )
end
end
if ( not( stdnse.get_script_args( {'ms-sql-query.query', 'mssql-query.query' } ) ) ) then
table.insert(scriptOutput, 1, "(Use --script-args=ms-sql-query.query='<QUERY>' to change query.)")
end
end
return result
return stdnse.format_output( true, scriptOutput )
end

View File

@@ -1,10 +1,19 @@
-- -*- mode: lua -*-
-- vim: set filetype=lua :
description = [[
Queries Microsoft SQL Server (ms-sql) for a list of tables per database.
The sysdatabase table should be accessible by more or less everyone
The script attempts to use the sa account over any other if it has
the password in the registry. If not the first account in the
registry is used.
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 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
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
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
-- 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.
-- @usage
-- nmap -p 1433 --script ms-sql-tables --script-args mssql.username=sa,mssql.password=sa <host>
--
-- @args ms-sql-tables.maxdb Limits the amount of databases that are
-- 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
--
-- @output
-- PORT STATE SERVICE
-- 1433/tcp open ms-sql-s
-- | ms-sql-tables:
-- | [192.168.100.25\MSSQLSERVER]
-- | webshop
-- | table column type length
-- | payments user_id int 4
@@ -72,15 +77,28 @@ dependencies = {"ms-sql-brute", "ms-sql-empty-password"}
-- | users password varchar 50
-- |_ users fullname varchar 100
-- Version 0.1
-- Created 01/17/2010 - v0.1 - created by Patrik Karlsson <patrik@cqure.net>
-- Revised 04/02/2010 - v0.2
-- - Added support for filters
-- - Changed output formatting of restrictions
-- - Added parameter information in output if parameters are using their
-- 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 )
for k,v in pairs(tbl) do
@@ -91,17 +109,15 @@ local function table_contains( tbl, val )
return false
end
action = function( host, port )
local status, result, dbs, tables, helper
local username = stdnse.get_script_args( 'mssql.username' )
local password = stdnse.get_script_args( 'mssql.password' ) or ""
local function process_instance( instance )
local status, result, dbs, tables
local output = {}
local exclude_dbs = { "'master'", "'tempdb'", "'model'", "'msdb'" }
local db_query
local done_dbs = {}
local creds = {}
local db_limit, tbl_limit
local DB_COUNT = stdnse.get_script_args( {'ms-sql-tables.maxdb', 'mssql-tables.maxdb'} )
@@ -122,8 +138,8 @@ action = function( host, port )
end
-- Build the keyword filter
if ( nmap.registry.args['mssql-tables.keywords'] ) then
local keywords = nmap.registry.args['mssql-tables.keywords']
if ( stdnse.get_script_args( {'ms-sql-tables.keywords', 'mssql-tables.keywords' } ) ) then
local keywords = stdnse.get_script_args( {'ms-sql-tables.keywords', 'mssql-tables.keywords' } )
local tmp_tbl = {}
if( type(keywords) == 'string' ) then
@@ -142,36 +158,26 @@ action = function( host, port )
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
local creds = mssql.Helper.GetLoginCredentials_All( instance )
if ( not creds ) then
output = "ERROR: No login credentials."
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)
local helper = mssql.Helper:new()
status, result = helper:ConnectEx( instance )
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)
table.insert(output, "ERROR: " .. result)
break
end
if ( status ) then
status = helper:Login( username, password, nil, instance.host.ip )
end
if ( status ) then
status, dbs = helper:Query( db_query )
end
if ( status ) then
-- all done?
@@ -238,14 +244,37 @@ action = function( host, port )
pos = pos + 1
end
if ( 1 < pos and #output > 0) then
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
output = stdnse.format_output( true, output )
return output
local instanceOutput = {}
instanceOutput["name"] = string.format( "[%s]", instance:GetName() )
table.insert( instanceOutput, output )
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

View File

@@ -1,44 +1,51 @@
-- -*- mode: lua -*-
-- vim: set filetype=lua :
description = [[
Attempts to run a command using the command shell of 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.
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
the command until either all credentials are exhausted or until the
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.
]]
author = "Patrik Karlsson"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"intrusive"}
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
-- the server. This option overrides any accounts found by
-- the <code>ms-sql-brute</code> and <code>ms-sql-empty-password</code> scripts.
-- @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 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)
-- @args ms-sql-xp-cmdshell.cmd The OS command to run (default: ipconfig /all).
--
-- @output
-- PORT STATE SERVICE
-- 1433/tcp open ms-sql-s
-- | ms-sql-xp-cmdshell:
-- | Command: ipconfig /all; User: sa
-- | [192.168.56.3\MSSQLSERVER]
-- | Command: ipconfig /all
-- | output
-- | ======
-- |
-- | Windows IP Configuration
-- |
@@ -64,84 +71,93 @@ dependencies = {"ms-sql-brute", "ms-sql-empty-password"}
-- | Lease Expires . . . . . . . . . . : den 21 mars 2010 01:12:10
-- |_
-- Version 0.1
-- 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)
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 = {"intrusive"}
local function table_contains( tbl, val )
for k,v in pairs(tbl) do
if ( v == val ) then
return true
end
end
return false
end
require 'shortport'
require 'stdnse'
require 'mssql'
action = function( host, port )
dependencies = {"ms-sql-brute", "ms-sql-empty-password", "ms-sql-discover"}
local status, result, helper
local username = stdnse.get_script_args( 'mssql.username' )
local password = stdnse.get_script_args( 'mssql.password' ) or ""
local creds
hostrule = mssql.Helper.GetHostrule_Standard()
portrule = mssql.Helper.GetPortrule_Standard()
local function process_instance( instance )
local status, result
local query
local cmd = stdnse.get_script_args( {'ms-sql-xp-cmdshell.cmd', 'mssql-xp-cmdshell.cmd'} ) or 'ipconfig /all'
local output = {}
query = ("EXEC master..xp_cmdshell '%s'"):format(cmd)
if ( username ) then
creds = {}
creds[username] = password
elseif ( not(username) and nmap.registry.mssqlusers ) then
-- do we have a sysadmin?
creds = {}
if ( nmap.registry.mssqlusers.sa ) then
creds["sa"] = nmap.registry.mssqlusers.sa
local creds = mssql.Helper.GetLoginCredentials_All( instance )
if ( not creds ) then
output = "ERROR: No login credentials."
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)
local helper = mssql.Helper:new()
status, result = helper:ConnectEx( instance )
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)
output = "ERROR: " .. result
break
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 )
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
output[ "name" ] = string.format( "Command: %s", cmd )
break
elseif ( result:gmatch("xp_configure") ) then
elseif ( result and 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."
output = "Procedure xp_cmdshell disabled. For more information see \"Surface Area Configuration\" in Books Online."
end
end
end
end
return output
local instanceOutput = {}
instanceOutput["name"] = string.format( "[%s]", instance:GetName() )
table.insert( instanceOutput, output )
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
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