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:
11
CHANGELOG
11
CHANGELOG
@@ -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]
|
||||
|
||||
|
||||
2040
nselib/mssql.lua
2040
nselib/mssql.lua
File diff suppressed because it is too large
Load Diff
184
nselib/smb.lua
184
nselib/smb.lua
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
131
scripts/ms-sql-discover.nse
Executable 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user