/nselib/mssql.lua
https://github.com/prakashgamit/nmap · Lua · 3173 lines · 1968 code · 518 blank · 687 comment · 486 complexity · 4f8391b5a235ef7c0a2568f454d42a87 MD5 · raw file
Large files are truncated click here to view the full file
- ---
- -- MSSQL Library supporting a very limited subset of operations.
- --
- -- The library was designed and tested against Microsoft SQL Server 2005.
- -- However, it should work with versions 7.0, 2000, 2005, 2008 and 2012.
- -- Only a minimal amount of parsers have been added for tokens, column types
- -- and column data in order to support the first scripts.
- --
- -- The code has been implemented based on traffic analysis and the following
- -- documentation:
- -- * SSRP Protocol Specification: http://msdn.microsoft.com/en-us/library/cc219703.aspx
- -- * TDS Protocol Specification: http://msdn.microsoft.com/en-us/library/dd304523.aspx
- -- * TDS Protocol Documentation: http://www.freetds.org/tds.html.
- -- * The JTDS source code: http://jtds.sourceforge.net/index.html.
- --
- -- * SSRP: Class that handles communication over the SQL Server Resolution Protocol, used for identifying instances on a host.
- -- * ColumnInfo: Class containing parsers for column types which are present before the row data in all query response packets. The column information contains information relevant to the data type used to hold the data eg. precision, character sets, size etc.
- -- * ColumnData: Class containing parsers for the actual column information.
- -- * Token: Class containing parsers for tokens returned in all TDS responses. A server response may hold one or more tokens with information from the server. Each token has a type which has a number of type specific fields.
- -- * QueryPacket: Class used to hold a query and convert it to a string suitable for transmission over a socket.
- -- * LoginPacket: Class used to hold login specific data which can easily be converted to a string suitable for transmission over a socket.
- -- * PreLoginPacket: Class used to (partially) implement the TDS PreLogin packet
- -- * TDSStream: Class that handles communication over the Tabular Data Stream protocol used by SQL serve. It is used to transmit the the Query- and Login-packets to the server.
- -- * Helper: Class which facilitates the use of the library by through action oriented functions with descriptive names.
- -- * Util: A "static" class containing mostly character and type conversion functions.
- --
- -- The following sample code illustrates how scripts can use the Helper class
- -- to interface the library:
- --
- -- <code>
- -- local helper = mssql.Helper:new()
- -- status, result = helper:Connect( host, port )
- -- status, result = helper:Login( username, password, "temdpb", host.ip )
- -- status, result = helper:Query( "SELECT name FROM master..syslogins" )
- -- helper:Disconnect()
- -- </code>
- --
- -- The following sample code illustrates how scripts can use the Helper class
- -- with pre-discovered instances (e.g. by <code>ms-sql-discover</code> or <code>broadcast-ms-sql-discover</code>):
- --
- -- <code>
- -- local instance = mssql.Helper.GetDiscoveredInstances( host, port )
- -- if ( instance ) then
- -- local helper = mssql.Helper:new()
- -- status, result = helper:ConnectEx( instance )
- -- status, result = helper:LoginEx( instance )
- -- status, result = helper:Query( "SELECT name FROM master..syslogins" )
- -- helper:Disconnect()
- -- end
- -- </code>
- --
- -- Known limitations:
- -- * The library does not support SSL. The foremost reason being the akward choice of implementation where the SSL handshake is performed within the TDS data block. By default, servers support connections over non SSL connections though.
- -- * Version 7 and ONLY version 7 of the protocol is supported. This should cover Microsoft SQL Server 7.0 and later.
- -- * TDS Responses contain one or more response tokens which are parsed based on their type. The supported tokens are listed in the <code>TokenType</code> table and their respective parsers can be found in the <code>Token</code> class. Note that some token parsers are not fully implemented and simply move the offset the right number of bytes to continue processing of the response.
- -- * The library only supports a limited subsets of datatypes and will abort execution and return an error if it detects an unsupported type. The supported data types are listed in the <code>DataTypes</code> table. In order to add additional data types a parser function has to be added to both the <code>ColumnInfo</code> and <code>ColumnData</code> class.
- -- * No functionality for languages, localization or characted codepages has been considered or implemented.
- -- * The library does database authentication only. No OS authentication or use of the integrated security model is supported.
- -- * Queries using SELECT, INSERT, DELETE and EXEC of procedures have been tested while developing scripts.
- --
- -- @copyright Same as Nmap--See http://nmap.org/book/man-legal.html
- --
- -- @author "Patrik Karlsson <patrik@cqure.net>, Chris Woodbury"
- --
- -- @args mssql.username The username to use to connect to SQL Server instances.
- -- This username is used by scripts taking actions that require
- -- authentication (e.g. <code>ms-sql-query</code>) This username (and its
- -- associated password) takes precedence over any credentials discovered
- -- by the <code>ms-sql-brute</code> and <code>ms-sql-empty-password</code>
- -- scripts.
- --
- -- @args mssql.password The password for <code>mssql.username</code>. If this
- -- argument is not given but <code>mssql.username</code>, a blank password
- -- is used.
- --
- -- @args mssql.instance-name The name of the instance to connect to.
- --
- -- @args mssql.instance-port The port of the instance to connect to.
- --
- -- @args mssql.instance-all Targets all SQL server instances discovered
- -- throught the browser service.
- --
- -- @args mssql.domain The domain against which to perform integrated
- -- authentication. When set, the scripts assume integrated authentication
- -- should be performed, rather than the default sql login.
- --
- -- @args mssql.protocol The protocol to use to connect to the instance. The
- -- protocol may be either <code>NP</code>,<code>Named Pipes</code> or
- -- <code>TCP</code>.
- --
- -- @args mssql.timeout How long to wait for SQL responses. This is a number
- -- followed by <code>ms</code> for milliseconds, <code>s</code> for
- -- seconds, <code>m</code> for minutes, or <code>h</code> for hours.
- -- Default: <code>30s</code>.
- --
- -- @args mssql.scanned-ports-only If set, the script will only connect
- -- to ports that were included in the Nmap scan. This may result in
- -- instances not being discovered, particularly if UDP port 1434 is not
- -- included. Additionally, instances that are found to be running on
- -- ports that were not scanned (e.g. if 1434/udp is in the scan and the
- -- SQL Server Browser service on that port reports an instance
- -- listening on 43210/tcp, which was not scanned) will be reported but
- -- will not be stored for use by other ms-sql-* scripts.
- local bin = require "bin"
- local bit = require "bit"
- local math = require "math"
- local match = require "match"
- local nmap = require "nmap"
- local os = require "os"
- local shortport = require "shortport"
- local smb = require "smb"
- local smbauth = require "smbauth"
- local stdnse = require "stdnse"
- local strbuf = require "strbuf"
- local string = require "string"
- local table = require "table"
- _ENV = stdnse.module("mssql", stdnse.seeall)
- -- Created 01/17/2010 - v0.1 - created by Patrik Karlsson <patrik@cqure.net>
- -- Revised 03/28/2010 - v0.2 - fixed incorrect token types. added 30 seconds timeout
- -- Revised 01/23/2011 - v0.3 - fixed parsing error in discovery code with patch
- -- from Chris Woodbury
- -- Revised 02/01/2011 - v0.4 - numerous changes and additions to support new
- -- functionality in ms-sql- scripts and to be more
- -- robust in parsing and handling data. (Chris Woodbury)
- -- Revised 02/19/2011 - v0.5 - numerous changes in script, library behaviour
- -- * huge improvements in version detection
- -- * added support for named pipes
- -- * added support for integrated NTLMv1 authentication
- --
- -- (Patrik Karlsson, Chris Woodbury)
- -- Revised 08/19/2012 - v0.6 - added multiple data types
- -- * added detection and handling of null values when processing query responses from the server
- -- * added DoneProc response token support
- --
- -- (Tom Sellers)
- -- Updated 10/01/2012 - v0.7 - added support for 2012 and later service packs for 2005, 2008 and 2008 R2 (Rob Nicholls)
- local HAVE_SSL, openssl = pcall(require, "openssl")
- do
- namedpipes = smb.namedpipes
- local arg = stdnse.get_script_args( "mssql.timeout" ) or "30s"
-
- local timeout, err = stdnse.parse_timespec(arg)
- if not timeout then
- error(err)
- end
- MSSQL_TIMEOUT = timeout
-
- SCANNED_PORTS_ONLY = false
- if ( stdnse.get_script_args( "mssql.scanned-ports-only" ) ) then
- SCANNED_PORTS_ONLY = true
- end
- end
- -- *************************************
- -- Informational Classes
- -- *************************************
- --- SqlServerInstanceInfo class
- SqlServerInstanceInfo =
- {
- instanceName = nil,
- version = nil,
- serverName = nil,
- isClustered = nil,
- host = nil,
- port = nil,
- pipeName = nil,
- new = function(self,o)
- o = o or {}
- setmetatable(o, self)
- self.__index = self
- return o
- end,
-
- -- Compares two SqlServerInstanceInfo objects and determines whether they
- -- refer to the same SQL Server instance, judging by a combination of host,
- -- port, named pipe information and instance name.
- __eq = function( self, other )
- local areEqual
- if ( not (self.host and other.host) ) then
- -- if they don't both have host information, we certainly can't say
- -- whether they're the same
- areEqual = false
- else
- areEqual = (self.host.ip == other.host.ip)
- end
-
- if (self.port and other.port) then
- areEqual = areEqual and ( other.port.number == self.port.number and
- other.port.protocol == self.port.protocol )
- elseif (self.pipeName and other.pipeName) then
- areEqual = areEqual and (self.pipeName == other.pipeName)
- elseif (self.instanceName and other.instanceName) then
- areEqual = areEqual and (self.instanceName == other.instanceName)
- else
- -- if we have neither port nor named pipe info nor instance names,
- -- we can't say whether they're the same
- areEqual = false
- end
-
- return areEqual
- end,
-
- --- Merges the data from one SqlServerInstanceInfo object into another. Each
- -- field in the first object is populated with the data from that field in
- -- second object if the first object's field is nil OR if <code>overwrite</code>
- -- is set to true. A special case is made for the <code>version</code> field,
- -- which is only overwritten in the second object has more reliable version
- -- information. The second object is not modified.
- Merge = function( self, other, overwrite )
- local mergeFields = { "host", "port", "instanceName", "version", "isClustered", "pipeName" }
- for _, fieldname in ipairs( mergeFields ) do
- -- Add values from other only if self doesn't have a value, or if overwrite is true
- if ( other[ fieldname ] ~= nil and (overwrite or self[ fieldname ] == nil) ) then
- self[ fieldname ] = other[ fieldname ]
- end
- end
- if (self.version and self.version.source == "SSRP" and
- other.version and other.version.Source == "SSNetLib") then
- self.version = other.version
- end
- end,
-
- --- Returns a name for the instance, based on the available information. This
- -- may take one of the following forms:
- -- * HOST\INSTANCENAME
- -- * PIPENAME
- -- * HOST:PORT
- GetName = function( self )
- if (self.instanceName) then
- return string.format( "%s\\%s", self.host.ip or self.serverName or "[nil]", self.instanceName or "[nil]" )
- elseif (self.pipeName) then
- return string.format( "%s", self.pipeName )
- else
- return string.format( "%s:%s", self.host.ip or self.serverName or "[nil]", (self.port and self.port.number) or "[nil]" )
- end
- end,
-
- --- Sets whether the instance is in a cluster
- --
- -- @param self
- -- @param isClustered Boolean true or the string "Yes" are interpreted as true;
- -- all other values are interpreted as false.
- SetIsClustered = function( self, isClustered )
- self.isClustered = (isClustered == true) or (isClustered == "Yes")
- end,
-
- --- Indicates whether this instance has networking protocols enabled, such
- -- that scripts could attempt to connect to it.
- HasNetworkProtocols = function( self )
- return (self.pipeName ~= nil) or (self.port and self.port.number)
- end,
- }
- --- SqlServerVersionInfo class
- SqlServerVersionInfo =
- {
- versionNumber = "", -- The full version string (e.g. "9.00.2047.00")
- major = nil, -- The major version (e.g. 9)
- minor = nil, -- The minor version (e.g. 0)
- build = nil, -- The build number (e.g. 2047)
- subBuild = nil, -- The sub-build number (e.g. 0)
- productName = nil, -- The prodcut name (e.g. "SQL Server 2005")
- brandedVersion = nil, -- The branded version of the product (e.g. "2005")
- servicePackLevel = nil, -- The service pack leve (e.g. "SP1")
- patched = nil, -- Whether patches have been applied since SP installation (true/false/nil)
- source = nil, -- The source of the version info (e.g. "SSRP", "SSNetLib")
- new = function(self,o)
- o = o or {}
- setmetatable(o, self)
- self.__index = self
- return o
- end,
-
- --- Sets the version using a version number string.
- --
- -- @param versionNumber a version number string (e.g. "9.00.1399.00")
- -- @param source a string indicating the source of the version info (e.g. "SSRP", "SSNetLib")
- SetVersionNumber = function(self, versionNumber, source)
- local major, minor, revision, subBuild
- if versionNumber:match( "^%d+%.%d+%.%d+.%d+" ) then
- major, minor, revision, subBuild = versionNumber:match( "^(%d+)%.(%d+)%.(%d+)" )
- elseif versionNumber:match( "^%d+%.%d+%.%d+" ) then
- major, minor, revision = versionNumber:match( "^(%d+)%.(%d+)%.(%d+)" )
- else
- stdnse.print_debug( 1, "%s: SetVersionNumber: versionNumber is not in correct format: %s", "MSSQL", versionNumber or "nil" )
- end
- self:SetVersion( major, minor, revision, subBuild, source )
- end,
-
- --- Sets the version using the individual numeric components of the version
- -- number.
- --
- -- @param source a string indicating the source of the version info (e.g. "SSRP", "SSNetLib")
- SetVersion = function(self, major, minor, build, subBuild, source)
- self.source = source
- -- make sure our version numbers all end up as valid numbers
- self.major, self.minor, self.build, self.subBuild =
- tonumber( major or 0 ), tonumber( minor or 0 ), tonumber( build or 0 ), tonumber( subBuild or 0 )
-
- self.versionNumber = string.format( "%u.%02u.%u.%02u", self.major, self.minor, self.build, self.subBuild )
-
- self:_ParseVersionInfo()
- end,
-
- --- Using the version number, determines the product version
- _InferProductVersion = function(self)
-
- local VERSION_LOOKUP_TABLE = {
- ["^6%.0"] = "6.0", ["^6%.5"] = "6.5", ["^7%.0"] = "7.0",
- ["^8%.0"] = "2000", ["^9%.0"] = "2005", ["^10%.0"] = "2008",
- ["^10%.50"] = "2008 R2", ["^11%.0"] = "2012",
- }
-
- local product = ""
-
- for m, v in pairs(VERSION_LOOKUP_TABLE) do
- if ( self.versionNumber:match(m) ) then
- product = v
- self.brandedVersion = product
- break
- end
- end
-
- self.productName = ("Microsoft SQL Server %s"):format(product)
-
- end,
-
-
- --- Returns a lookup table that maps revision numbers to service pack levels for
- -- the applicable SQL Server version (e.g. { {1600, "RTM"}, {2531, "SP1"} }).
- _GetSpLookupTable = function(self)
-
- -- Service pack lookup tables:
- -- For instances where a revised service pack was released (e.g. 2000 SP3a), we will include the
- -- build number for the original SP and the build number for the revision. However, leaving it
- -- like this would make it appear that subsequent builds were a patched version of the revision
- -- (e.g. a patch applied to 2000 SP3 that increased the build number to 780 would get displayed
- -- as "SP3a+", when it was actually SP3+). To avoid this, we will include an additional fake build
- -- number that combines the two.
- local SP_LOOKUP_TABLE_6_5 = { {201, "RTM"}, {213, "SP1"}, {240, "SP2"}, {258, "SP3"}, {281, "SP4"},
- {415, "SP5"}, {416, "SP5a"}, {417, "SP5/SP5a"}, }
-
- local SP_LOOKUP_TABLE_7 = { {623, "RTM"}, {699, "SP1"}, {842, "SP2"}, {961, "SP3"}, {1063, "SP4"}, }
-
- local SP_LOOKUP_TABLE_2000 = { {194, "RTM"}, {384, "SP1"}, {532, "SP2"}, {534, "SP2"}, {760, "SP3"},
- {766, "SP3a"}, {767, "SP3/SP3a"}, {2039, "SP4"}, }
-
- local SP_LOOKUP_TABLE_2005 = { {1399, "RTM"}, {2047, "SP1"}, {3042, "SP2"}, {4035, "SP3"}, {5000, "SP4"}, }
-
- local SP_LOOKUP_TABLE_2008 = { {1600, "RTM"}, {2531, "SP1"}, {4000, "SP2"}, {5500, "SP3"}, }
-
- local SP_LOOKUP_TABLE_2008R2 = { {1600, "RTM"}, {2500, "SP1"}, {4000, "SP2"}, }
- local SP_LOOKUP_TABLE_2012 = { {2100, "RTM"}, }
-
-
- if ( not self.brandedVersion ) then
- self:_InferProductVersion()
- end
-
- local spLookupTable
- if self.brandedVersion == "6.5" then spLookupTable = SP_LOOKUP_TABLE_6_5
- elseif self.brandedVersion == "7.0" then spLookupTable = SP_LOOKUP_TABLE_7
- elseif self.brandedVersion == "2000" then spLookupTable = SP_LOOKUP_TABLE_2000
- elseif self.brandedVersion == "2005" then spLookupTable = SP_LOOKUP_TABLE_2005
- elseif self.brandedVersion == "2008" then spLookupTable = SP_LOOKUP_TABLE_2008
- elseif self.brandedVersion == "2008 R2" then spLookupTable = SP_LOOKUP_TABLE_2008R2
- elseif self.brandedVersion == "2012" then spLookupTable = SP_LOOKUP_TABLE_2012
- end
-
- return spLookupTable
-
- end,
-
-
- --- Processes version data to determine (if possible) the product version,
- -- service pack level and patch status.
- _ParseVersionInfo = function(self)
-
- local spLookupTable = self:_GetSpLookupTable()
-
- if spLookupTable then
-
- local spLookupItr = 0
- -- Loop through the service pack levels until we find one whose revision
- -- number is the same as or lower than our revision number.
- while spLookupItr < #spLookupTable do
- spLookupItr = spLookupItr + 1
-
- if (spLookupTable[ spLookupItr ][1] == self.build ) then
- spLookupItr = spLookupItr
- break
- elseif (spLookupTable[ spLookupItr ][1] > self.build ) then
- -- The target revision number is lower than the first release
- if spLookupItr == 1 then
- self.servicePackLevel = "Pre-RTM"
- else
- -- we went too far - it's the previous SP, but with patches applied
- spLookupItr = spLookupItr - 1
- end
- break
- end
- end
-
- -- Now that we've identified the proper service pack level:
- if self.servicePackLevel ~= "Pre-RTM" then
- self.servicePackLevel = spLookupTable[ spLookupItr ][2]
-
- if ( spLookupTable[ spLookupItr ][1] == self.build ) then
- self.patched = false
- else
- self.patched = true
- end
- end
-
- -- Clean up some of our inferences. If the source of our revision number
- -- was the SSRP (SQL Server Browser) response, we need to recognize its
- -- limitations:
- -- * Versions of SQL Server prior to 2005 are reported with the RTM build
- -- number, regardless of the actual version (e.g. SQL Server 2000 is
- -- always 8.00.194).
- -- * Versions of SQL Server starting with 2005 (and going through at least
- -- 2008) do better but are still only reported with the build number as
- -- of the last service pack (e.g. SQL Server 2005 SP3 with patches is
- -- still reported as 9.00.4035.00).
- if ( self.source == "SSRP" ) then
- self.patched = nil
-
- if ( self.major <= 8 ) then
- self.servicePackLevel = nil
- end
- end
- end
-
- return true
- end,
-
- ---
- ToString = function(self)
- local friendlyVersion = strbuf.new()
- if self.productName then
- friendlyVersion:concatbuf( self.productName )
- if self.servicePackLevel then
- friendlyVersion:concatbuf( " " )
- friendlyVersion:concatbuf( self.servicePackLevel )
- end
- if self.patched then
- friendlyVersion:concatbuf( "+" )
- end
- end
-
- return friendlyVersion:dump()
- end,
-
- --- Uses the information in this SqlServerVersionInformation object to
- -- populate the version information in an Nmap port table for a SQL Server
- -- TCP listener.
- --
- -- @param self A SqlServerVersionInformation object
- -- @param port An Nmap port table corresponding to the instance
- PopulateNmapPortVersion = function(self, port)
-
- port.service = "ms-sql-s"
- port.version = port.version or {}
- port.version.name = "ms-sql-s"
- port.version.product = self.productName
-
- local versionString = strbuf.new()
- if self.source ~= "SSRP" then
- versionString:concatbuf( self.versionNumber )
- if self.servicePackLevel then
- versionString:concatbuf( "; " )
- versionString:concatbuf( self.servicePackLevel )
- end
- if self.patched then
- versionString:concatbuf( "+" )
- end
- port.version.version = versionString:dump()
- end
-
- return port
- end,
- }
- -- *************************************
- -- SSRP (SQL Server Resolution Protocol)
- -- *************************************
- SSRP =
- {
- PORT = { number = 1434, protocol = "udp" },
- DEBUG_ID = "MSSQL-SSRP",
-
- MESSAGE_TYPE =
- {
- ClientBroadcast = 0x02,
- ClientUnicast = 0x03,
- ClientUnicastInstance = 0x04,
- ClientUnicastDAC = 0x0F,
- ServerResponse = 0x05,
- },
-
- --- Parses an SSRP string and returns a table containing one or more
- -- SqlServerInstanceInfo objects created from the parsed string.
- _ParseSsrpString = function( host, ssrpString )
- -- It would seem easier to just capture (.-;;) repeateadly, since
- -- each instance ends with ";;", but ";;" can also occur within the
- -- data, signifying an empty field (e.g. "...bv;;@COMPNAME;;tcp;1433;;...").
- -- So, instead, we'll split up the string ahead of time.
- -- See the SSRP specification for more details.
-
- local instanceStrings = {}
- local firstInstanceEnd, instanceString
- repeat
- firstInstanceEnd = ssrpString:find( ";ServerName;(.-);InstanceName;(.-);IsClustered;(.-);" )
- if firstInstanceEnd then
- instanceString = ssrpString:sub( 1, firstInstanceEnd )
- ssrpString = ssrpString:sub( firstInstanceEnd + 1 )
- else
- instanceString = ssrpString
- end
-
- table.insert( instanceStrings, instanceString )
- until (not firstInstanceEnd)
- stdnse.print_debug( 2, "%s: SSRP Substrings:\n %s", SSRP.DEBUG_ID, stdnse.strjoin( "\n ", instanceStrings ) )
-
- local instances = {}
- for _, instanceString in ipairs( instanceStrings ) do
- local instance = SqlServerInstanceInfo:new()
- local version = SqlServerVersionInfo:new()
- instance.version = version
-
- instance.host = host
- instance.serverName = instanceString:match( "ServerName;(.-);")
- instance.instanceName = instanceString:match( "InstanceName;(.-);")
- instance:SetIsClustered( instanceString:match( "IsClustered;(.-);") )
- version:SetVersionNumber( instanceString:match( "Version;(.-);"), "SSRP" )
-
- local tcpPort = tonumber( instanceString:match( ";tcp;(.-);") )
- if tcpPort then instance.port = {number = tcpPort, protocol = "tcp"} end
-
- local pipeName = instanceString:match( ";np;(.-);")
- local status, pipeSubPath = namedpipes.get_pipe_subpath( pipeName )
- if status then
- pipeName = namedpipes.make_pipe_name( host.ip, pipeSubPath )
- elseif pipeName ~= nil then
- stdnse.print_debug( 1, "%s: Invalid pipe name:\n%s", SSRP.DEBUG_ID, pipeName )
- end
- instance.pipeName = pipeName
-
- table.insert( instances, instance )
- end
-
- return instances
- end,
-
- ---
- _ProcessResponse = function( host, responseData )
- local instances
- local pos, messageType, dataLength = 1, nil, nil
- pos, messageType, dataLength = bin.unpack("<CS", responseData, 1)
- -- extract the response data (i.e. everything after the 3-byte header)
- responseData = responseData:sub(4)
- stdnse.print_debug( 2, "%s: SSRP Data: %s", SSRP.DEBUG_ID, responseData )
- if ( messageType ~= SSRP.MESSAGE_TYPE.ServerResponse or
- dataLength ~= responseData:len() ) then
-
- stdnse.print_debug( 2, "%s: Invalid SSRP response. Type: 0x%02x, Length: %d, Actual length: %d",
- SSRP.DEBUG_ID, messageType, dataLength, responseData:len() )
- else
- instances = SSRP._ParseSsrpString( host, responseData )
- end
-
- return instances
- end,
-
- --- Attempts to retrieve information about SQL Server instances by querying
- -- the SQL Server Browser service on a host.
- --
- -- @param host A host table for the target host
- -- @param port (Optional) A port table for the target SQL Server Browser service
- -- @return (status, result) If status is true, result is a table of
- -- SqlServerInstanceInfo objects. If status is false, result is an
- -- error message.
- DiscoverInstances = function( host, port )
- port = port or SSRP.PORT
-
- if ( SCANNED_PORTS_ONLY and nmap.get_port_state( host, port ) == nil ) then
- stdnse.print_debug( 2, "%s: Discovery disallowed: scanned-ports-only is set and port %d was not scanned", SSRP.DEBUG_ID, port.number )
- return false, "Discovery disallowed: scanned-ports-only"
- end
-
- local socket = nmap.new_socket("udp")
- socket:set_timeout(5000)
-
- if ( port.number ~= SSRP.PORT.number ) then
- stdnse.print_debug( 1, "%s: DiscoverInstances() called with non-standard port (%d)", SSRP.DEBUG_ID, port.number )
- end
-
- local status, err = socket:connect( host, port )
- if ( not(status) ) then return false, err end
- status, err = socket:send( bin.pack( "C", SSRP.MESSAGE_TYPE.ClientUnicast ) )
- if ( not(status) ) then return false, err end
-
- local responseData, instances_host
- status, responseData = socket:receive()
- if ( not(status) ) then return false, responseData
- else
- instances_host = SSRP._ProcessResponse( host, responseData )
- end
- socket:close()
-
- return status, instances_host
- end,
-
-
- --- Attempts to retrieve information about SQL Server instances by querying
- -- the SQL Server Browser service on a broadcast domain.
- --
- -- @param host A host table for the broadcast specification
- -- @param port (Optional) A port table for the target SQL Server Browser service
- -- @return (status, result) If status is true, result is a table of
- -- tables containing SqlServerInstanceInfo objects. The top-level table
- -- is indexed by IP address. If status is false, result is an
- -- error message.
- DiscoverInstances_Broadcast = function( host, port )
- port = port or SSRP.PORT
-
- local socket = nmap.new_socket("udp")
- socket:set_timeout(5000)
- local instances_all = {}
-
- if ( port.number ~= SSRP.PORT.number ) then
- stdnse.print_debug( 1, "%S: DiscoverInstances_Broadcast() called with non-standard port (%d)", SSRP.DEBUG_ID, port.number )
- end
-
- local status, err = socket:sendto(host, port, bin.pack( "C", SSRP.MESSAGE_TYPE.ClientBroadcast ))
- if ( not(status) ) then return false, err end
-
- while ( status ) do
- local responseData
- status, responseData = socket:receive()
- if ( status ) then
- local remoteIp, _
- status, _, _, remoteIp, _ = socket:get_info()
- local instances_host = SSRP._ProcessResponse( {ip = remoteIp, name = ""}, responseData )
- instances_all[ remoteIp ] = instances_host
- end
- end
- socket:close()
-
- return true, instances_all
- end,
- }
- -- *************************
- -- TDS (Tabular Data Stream)
- -- *************************
- -- TDS packet types
- PacketType =
- {
- Query = 0x01,
- Response = 0x04,
- Login = 0x10,
- NTAuthentication = 0x11,
- PreLogin = 0x12,
- }
- -- TDS response token types
- TokenType =
- {
- ReturnStatus = 0x79,
- TDS7Results = 0x81,
- ErrorMessage = 0xAA,
- InformationMessage = 0xAB,
- LoginAcknowledgement = 0xAD,
- Row = 0xD1,
- OrderBy = 0xA9,
- EnvironmentChange = 0xE3,
- NTLMSSP_CHALLENGE = 0xed,
- Done = 0xFD,
- DoneProc = 0xFE,
- DoneInProc = 0xFF,
- }
- -- SQL Server/Sybase data types
- DataTypes =
- {
- SQLTEXT = 0x23,
- GUIDTYPE = 0x24,
- SYBINTN = 0x26,
- SYBINT2 = 0x34,
- SYBINT4 = 0x38,
- SYBDATETIME = 0x3D,
- NTEXTTYPE = 0x63,
- BITNTYPE = 0x68,
- DECIMALNTYPE = 0x6A,
- NUMERICNTYPE = 0x6C,
- FLTNTYPE = 0x6D,
- MONEYNTYPE = 0x6E,
- SYBDATETIMN = 0x6F,
- XSYBVARBINARY = 0xA5,
- XSYBVARCHAR = 0xA7,
- BIGBINARYTYPE = 0xAD,
- BIGCHARTYPE = 0xAF,
- XSYBNVARCHAR = 0xE7,
- SQLNCHAR = 0xEF,
- }
- -- SQL Server login error codes
- -- See http://msdn.microsoft.com/en-us/library/ms131024.aspx
- LoginErrorType =
- {
- AccountLockedOut = 15113,
- NotAssociatedWithTrustedConnection = 18452, -- This probably means that the server is set for Windows authentication only
- InvalidUsernameOrPassword = 18456,
- PasswordChangeFailed_PasswordNotAllowed = 18463,
- PasswordChangeFailed_PasswordTooShort = 18464,
- PasswordChangeFailed_PasswordTooLong = 18465,
- PasswordChangeFailed_PasswordNotComplex = 18466,
- PasswordChangeFailed_PasswordFilter = 18467,
- PasswordChangeFailed_UnexpectedError = 18468,
- PasswordExpired = 18487,
- PasswordMustChange = 18488,
- }
- LoginErrorMessage = {}
- for i, v in pairs(LoginErrorType) do
- LoginErrorMessage[v] = i
- end
- -- "static" ColumInfo parser class
- ColumnInfo =
- {
- Parse =
- {
- [DataTypes.SQLTEXT] = function( data, pos )
- local colinfo = {}
- local tmp
-
- pos, colinfo.unknown, colinfo.codepage, colinfo.flags, colinfo.charset = bin.unpack("<ISSC", data, pos )
- pos, colinfo.tablenamelen = bin.unpack("s", data, pos )
- pos, colinfo.tablename = bin.unpack("A" .. (colinfo.tablenamelen * 2), data, pos)
- pos, colinfo.msglen = bin.unpack("<C", data, pos )
- pos, tmp = bin.unpack("A" .. (colinfo.msglen * 2), data, pos)
- colinfo.text = Util.FromWideChar(tmp)
- return pos, colinfo
- end,
- [DataTypes.GUIDTYPE] = function( data, pos )
- return ColumnInfo.Parse[DataTypes.SYBINTN](data, pos)
- end,
- [DataTypes.SYBINTN] = function( data, pos )
- local colinfo = {}
- local tmp
- pos, colinfo.unknown, colinfo.msglen = bin.unpack("<CC", data, pos)
- pos, tmp = bin.unpack("A" .. (colinfo.msglen * 2), data, pos )
- colinfo.text = Util.FromWideChar(tmp)
- return pos, colinfo
- end,
- [DataTypes.SYBINT2] = function( data, pos )
- return ColumnInfo.Parse[DataTypes.SYBDATETIME](data, pos)
- end,
- [DataTypes.SYBINT4] = function( data, pos )
- return ColumnInfo.Parse[DataTypes.SYBDATETIME](data, pos)
- end,
- [DataTypes.SYBDATETIME] = function( data, pos )
- local colinfo = {}
- local tmp
-
- pos, colinfo.msglen = bin.unpack("C", data, pos)
- pos, tmp = bin.unpack("A" .. (colinfo.msglen * 2), data, pos )
- colinfo.text = Util.FromWideChar(tmp)
-
- return pos, colinfo
- end,
- [DataTypes.NTEXTTYPE] = function( data, pos )
- return ColumnInfo.Parse[DataTypes.SQLTEXT](data, pos)
- end,
- [DataTypes.BITNTYPE] = function( data, pos )
- return ColumnInfo.Parse[DataTypes.SYBINTN](data, pos)
- end,
- [DataTypes.DECIMALNTYPE] = function( data, pos )
- local colinfo = {}
- local tmp
- pos, colinfo.unknown, colinfo.precision, colinfo.scale = bin.unpack("<CCC", data, pos)
- pos, colinfo.msglen = bin.unpack("<C",data,pos)
- pos, tmp = bin.unpack("A" .. (colinfo.msglen * 2), data, pos )
- colinfo.text = Util.FromWideChar(tmp)
- return pos, colinfo
- end,
- [DataTypes.NUMERICNTYPE] = function( data, pos )
- return ColumnInfo.Parse[DataTypes.DECIMALNTYPE](data, pos)
- end,
- [DataTypes.FLTNTYPE] = function( data, pos )
- return ColumnInfo.Parse[DataTypes.SYBINTN](data, pos)
- end,
- [DataTypes.MONEYNTYPE] = function( data, pos )
- return ColumnInfo.Parse[DataTypes.SYBINTN](data, pos)
- end,
- [DataTypes.SYBDATETIMN] = function( data, pos )
- return ColumnInfo.Parse[DataTypes.SYBINTN](data, pos)
- end,
- [DataTypes.XSYBVARBINARY] = function( data, pos )
- local colinfo = {}
- local tmp
- pos, colinfo.lts, colinfo.msglen = bin.unpack("<SC", data, pos)
- pos, tmp = bin.unpack("A" .. (colinfo.msglen * 2), data, pos )
- colinfo.text = Util.FromWideChar(tmp)
-
- return pos, colinfo
- end,
- [DataTypes.XSYBVARCHAR] = function( data, pos )
- return ColumnInfo.Parse[DataTypes.XSYBNVARCHAR](data, pos)
- end,
- [DataTypes.BIGBINARYTYPE] = function( data, pos )
- return ColumnInfo.Parse[DataTypes.XSYBVARBINARY](data, pos)
- end,
- [DataTypes.BIGCHARTYPE] = function( data, pos )
- return ColumnInfo.Parse[DataTypes.XSYBNVARCHAR](data, pos)
- end,
- [DataTypes.XSYBNVARCHAR] = function( data, pos )
- local colinfo = {}
- local tmp
- pos, colinfo.lts, colinfo.codepage, colinfo.flags, colinfo.charset,
- colinfo.msglen = bin.unpack("<SSSCC", data, pos )
- pos, tmp = bin.unpack("A" .. (colinfo.msglen * 2), data, pos)
- colinfo.text = Util.FromWideChar(tmp)
- return pos, colinfo
- end,
- [DataTypes.SQLNCHAR] = function( data, pos )
- return ColumnInfo.Parse[DataTypes.XSYBNVARCHAR](data, pos)
- end,
-
- }
-
- }
- -- "static" ColumData parser class
- ColumnData =
- {
- Parse = {
- [DataTypes.SQLTEXT] = function( data, pos )
- local len, coldata
-
- -- The first len value is the size of the meta data block
- -- for non-null values this seems to be 0x10 / 16 bytes
- pos, len = bin.unpack( "<C", data, pos )
- if ( len == 0 ) then
- return pos, 'Null'
- end
- -- Skip over the text update time and date values, we don't need them
- -- We may come back add parsing for this information.
- pos = pos + len
- -- skip a label, should be 'dummyTS'
- pos = pos + 8
- -- extract the actual data
- pos, len = bin.unpack( "<I", data, pos )
- pos, coldata = bin.unpack( "A"..len, data, pos )
- return pos, coldata
- end,
- [DataTypes.GUIDTYPE] = function( data, pos )
- local len, coldata, index, nextdata
- local hex = {}
- pos, len = bin.unpack("C", data, pos)
-
- if ( len == 0 ) then
- return pos, 'Null'
-
- elseif ( len == 16 ) then
-
- -- Return the first 8 bytes
- for index=1, 8 do
- pos, hex[index] = bin.unpack("H", data, pos)
- end
-
- -- reorder the bytes
- coldata = hex[4] .. hex[3] .. hex[2] .. hex[1]
- coldata = coldata .. '-' .. hex[6] .. hex[5]
- coldata = coldata .. '-' .. hex[8] .. hex[7]
-
- pos, nextdata = bin.unpack("H2", data, pos)
- coldata = coldata .. '-' .. nextdata
-
- pos, nextdata = bin.unpack("H6", data, pos)
- coldata = coldata .. '-' .. nextdata
-
- else
- stdnse.print_debug("Unhandled length (%d) for GUIDTYPE", len)
- return pos + len, 'Unsupported Data'
- end
- return pos, coldata
- end,
-
- [DataTypes.SYBINTN] = function( data, pos )
- local len, num
- pos, len = bin.unpack("C", data, pos)
- if ( len == 0 ) then
- return pos, 'Null'
- elseif ( len == 1 ) then
- return bin.unpack("C", data, pos)
- elseif ( len == 2 ) then
- return bin.unpack("<s", data, pos)
- elseif ( len == 4 ) then
- return bin.unpack("<i", data, pos)
- elseif ( len == 8 ) then
- return bin.unpack("<l", data, pos)
- else
- return -1, ("Unhandled length (%d) for SYBINTN"):format(len)
- end
- return -1, "Error"
- end,
-
- [DataTypes.SYBINT2] = function( data, pos )
- local num
- pos, num = bin.unpack("<S", data, pos)
-
- return pos, num
- end,
-
- [DataTypes.SYBINT4] = function( data, pos )
- local num
- pos, num = bin.unpack("<I", data, pos)
-
- return pos, num
- end,
- [DataTypes.SYBDATETIME] = function( data, pos )
- local hi, lo, result_seconds, result
- local tds_epoch, system_epoch, tds_offset_seconds
- pos, hi, lo = bin.unpack("<iI", data, pos)
- tds_epoch = os.time( {year = 1900, month = 1, day = 1, hour = 00, min = 00, sec = 00, isdst = nil} )
- -- determine the offset between the tds_epoch and the local system epoch
- system_epoch = os.time( os.date("*t", 0))
- tds_offset_seconds = os.difftime(tds_epoch,system_epoch)
-
- result_seconds = (hi*24*60*60) + (lo/300)
- result = os.date("!%b %d, %Y %H:%M:%S", tds_offset_seconds + result_seconds )
- return pos, result
- end,
-
- [DataTypes.NTEXTTYPE] = function( data, pos )
- local len, coldata
- -- The first len value is the size of the meta data block
- pos, len = bin.unpack( "<C", data, pos )
- if ( len == 0 ) then
- return pos, 'Null'
- end
- -- Skip over the text update time and date values, we don't need them
- -- We may come back add parsing for this information.
- pos = pos + len
- -- skip a label, should be 'dummyTS'
- pos = pos + 8
- -- extract the actual data
- pos, len = bin.unpack( "<I", data, pos )
- pos, coldata = bin.unpack( "A"..len, data, pos )
- return pos, Util.FromWideChar(coldata)
- end,
-
- [DataTypes.BITNTYPE] = function( data, pos )
- return ColumnData.Parse[DataTypes.SYBINTN](data, pos)
- end,
- [DataTypes.DECIMALNTYPE] = function( precision, scale, data, pos )
- local len, sign, format_string, coldata
- pos, len = bin.unpack("<C", data, pos)
- if ( len == 0 ) then
- return pos, 'Null'
- end
- pos, sign = bin.unpack("<C", data, pos)
- -- subtract 1 from data len to account for sign byte
- len = len - 1
- if ( len == 2 ) then
- pos, coldata = bin.unpack("<S", data, pos)
- elseif ( len == 4 ) then
- pos, coldata = bin.unpack("<I", data, pos)
- elseif ( len == 8 ) then
- pos, coldata = bin.unpack("<L", data, pos)
- else
- stdnse.print_debug("Unhandled length (%d) for DECIMALNTYPE", len)
- return pos + len, 'Unsupported Data'
- end
- if ( sign == 0 ) then
- coldata = coldata * -1
- end
-
- coldata = coldata * (10^-scale)
- -- format the return information to reduce truncation by lua
- format_string = string.format("%%.%if", scale)
- coldata = string.format(format_string,coldata)
-
- return pos, coldata
- end,
-
- [DataTypes.NUMERICNTYPE] = function( precision, scale, data, pos )
- return ColumnData.Parse[DataTypes.DECIMALNTYPE]( precision, scale, data, pos )
- end,
-
- [DataTypes.SYBDATETIME] = function( data, pos )
- local hi, lo, result_seconds, result
- local tds_epoch, system_epoch, tds_offset_seconds
- pos, hi, lo = bin.unpack("<iI", data, pos)
- tds_epoch = os.time( {year = 1900, month = 1, day = 1, hour = 00, min = 00, sec = 00, isdst = nil} )
- -- determine the offset between the tds_epoch and the local system epoch
- system_epoch = os.time( os.date("*t", 0))
- tds_offset_seconds = os.difftime(tds_epoch,system_epoch)
-
- result_seconds = (hi*24*60*60) + (lo/300)
- result = os.date("!%b %d, %Y %H:%M:%S", tds_offset_seconds + result_seconds )
- return pos, result
- end,
-
- [DataTypes.BITNTYPE] = function( data, pos )
- return ColumnData.Parse[DataTypes.SYBINTN](data, pos)
- end,
- [DataTypes.NTEXTTYPE] = function( data, pos )
- local len, coldata
- -- The first len value is the size of the meta data block
- pos, len = bin.unpack( "<C", data, pos )
- if ( len == 0 ) then
- return pos, 'Null'
- end
- -- Skip over the text update time and date values, we don't need them
- -- We may come back add parsing for this information.
- pos = pos + len
- -- skip a label, should be 'dummyTS'
- pos = pos + 8
- -- extract the actual data
- pos, len = bin.unpack( "<I", data, pos )
- pos, coldata = bin.unpack( "A"..len, data, pos )
- return pos, Util.FromWideChar(coldata)
- end,
- [DataTypes.FLTNTYPE] = function( data, pos )
- local len, coldata
- pos, len = bin.unpack("<C", data, pos)
- if ( len == 0 ) then
- return pos, 'Null'
- elseif ( len == 4 ) then
- pos, coldata = bin.unpack("f", data, pos)
- elseif ( len == 8 ) then
- pos, coldata = bin.unpack("<d", data, pos)
- end
- return pos, coldata
- end,
- [DataTypes.MONEYNTYPE] = function( data, pos )
- local len, value, coldata, hi, lo
- pos, len = bin.unpack("C", data, pos)
- if ( len == 0 ) then
- return pos, 'Null'
- elseif ( len == 4 ) then
- --type smallmoney
- pos, value = bin.unpack("<i", data, pos)
- elseif ( len == 8 ) then
- -- type money
- pos, hi,lo = bin.unpack("<II", data, pos)
- value = ( hi * 4294967296 ) + lo
- else
- return -1, ("Unhandled length (%d) for MONEYNTYPE"):format(len)
- end
- -- the datatype allows for 4 decimal places after the period to support various currency types.
- -- forcing to string to avoid truncation
- coldata = string.format("%.4f",value/10000)
- return pos, coldata
- end,
- [DataTypes.SYBDATETIMN] = function( data, pos )
- local len, coldata
- pos, len = bin.unpack( "<C", data, pos )
- if ( len == 0 ) then
- return pos, 'Null'
- elseif ( len == 4 ) then
- -- format is smalldatetime
- local days, mins
- pos, days, mins = bin.unpack("<SS", data, pos)
-
- local tds_epoch = os.time( {year = 1900, month = 1, day = 1, hour = 00, min = 00, sec = 00, isdst = nil} )
- -- determine the offset between the tds_epoch and the local system epoch
- local system_epoch = os.time( os.date("*t", 0))
- local tds_offset_seconds = os.difftime(tds_epoch,system_epoch)
- local result_seconds = (days*24*60*60) + (mins*60)
- coldata = os.date("!%b %d, %Y %H:%M:%S", tds_offset_seconds + result_seconds )
-
- return pos,coldata
-
- elseif ( len == 8 ) then
- -- format is datetime
- return ColumnData.Parse[DataTypes.SYBDATETIME](data, pos)
- else
- return -1, ("Unhandled length (%d) for SYBDATETIMN"):format(len)
- end
-
- end,
- [DataTypes.XSYBVARBINARY] = function( data, pos )
- local len, coldata
- pos, len = bin.unpack( "<S", data, pos )
- if ( len == 65535 ) then
- return pos, 'Null'
- else
- pos, coldata = bin.unpack( "A"..len, data, pos )
- return pos, "0x" .. select(2, bin.unpack("H"..coldata:len(), coldata ) )
- end
- return -1, "Error"
- end,
-
- [DataTypes.XSYBVARCHAR] = function( data, pos )
- local len, coldata
- pos, len = bin.unpack( "<S", data, pos )
- if ( len == 65535 ) then
- return pos, 'Null'
- end
- pos, coldata = bin.unpack( "A"..len, data, pos )
- return pos, coldata
- end,
- [DataTypes.BIGBINARYTYPE] = function( data, pos )
- return ColumnData.Parse[DataTypes.XSYBVARBINARY](data, pos)
- end,
- [DataTypes.BIGCHARTYPE] = function( data, pos )
- return ColumnData.Parse[DataTypes.XSYBVARCHAR](data, pos)
- end,
- [DataTypes.XSYBNVARCHAR] = function( data, pos )
- local len, coldata
- pos, len = bin.unpack( "<S", data, pos )
- if ( len == 65535 ) then
- return pos, 'Null'
- end
- pos, coldata = bin.unpack( "A"..len, data, pos )
- return pos, Util.FromWideChar(coldata)
- end,
- [DataTypes.SQLNCHAR] = function( data, pos )
- return ColumnData.Parse[DataTypes.XSYBNVARCHAR](data, pos)
- end,
- }
- }
- -- "static" Token parser class
- Token =
- {
- Parse = {
- --- Parse error message tokens
- --
- -- @param data string containing "raw" data
- -- @param pos number containing offset into data
- -- @return pos number containing new offset after parse
- -- @return token table containing token specific fields
- [TokenType.ErrorMessage] = function( data, pos )
- local token = {}
- local tmp
- token.type = TokenType.ErrorMessage
- pos, token.size, token.errno, token.state, token.severity, token.errlen = bin.unpack( "<SICCS", data, pos )
- pos, tmp = bin.unpack("A" .. (token.errlen * 2), data, pos )
- token.error = Util.FromWideChar(tmp)
- pos, token.srvlen = bin.unpack("C", data, pos)
- pos, tmp = bin.unpack("A" .. (token.srvlen * 2), data, pos )
- token.server = Util.FromWideChar(tmp)
- pos, token.proclen = bin.unpack("C", data, pos)
- pos, tmp = bin.unpack("A" .. (token.proclen * 2), data, pos )
- token.proc = Util.FromWideChar(tmp)
- pos, token.lineno = bin.unpack("<S", data, pos)
- return pos, token
- end,
-
- --- Parse environment change tokens
- -- (This function is not implemented and simply moves the pos offset)
- --
- -- @param data string containing "raw" data
- -- @param pos number containing offset into data
- -- @return pos number containing new offset after parse
- -- @return token table containing token specific fields
- [TokenType.EnvironmentChange] = function( data, pos )
- local token = {}
- local tmp
-
- token.type = TokenType.EnvironmentChange
- pos, token.size = bin.unpack("<S", data, pos)
-
- return pos + token.size, token
- end,
-
- --- Parse information message tokens
- --
- -- @param data string containing "raw" data
- -- @param pos number containing offset into data
- -- @return pos number containing new offset after parse
- -- @return token table containing token specific fields
- [TokenType.InformationMessage] = function( data, pos )
- local pos, token = Token.Parse[TokenType.ErrorMessage]( data, pos )
- token.type = TokenType.InformationMessage
- return pos, token
- end,
-
- --- Parse login acknowledgment tokens
- --
- -- @param data string containing "raw" data
- -- @param pos number containing offset into data
- -- @return pos number containing new offset after parse
- -- @return token table containing token specific fields
- [TokenType.LoginAcknowledgement] = function( data, pos )
- local token = {}
- local _
-
- token.type = TokenType.LoginAcknowledgement
- pos, token.size, _, _, _, _, token.textlen = bin.unpack( "<SCCCSC", data, pos )
- pos, token.text = bin.unpack("A" .. token.textlen * 2, data, pos)
- pos, token.version = bin.unpack("<I", data, pos )
- return pos, token
- end,
-
- --- Parse done tokens
- --
- -- @param data string containing "raw" data
- -- @param pos number containing offset into data
- -- @return pos number containing new offset after parse
- -- @return token table containing token specific fields
- [TokenType.Done] = function( data, pos )
- local token = {}
-
- token.type = TokenType.Done
- pos, token.flags, token.operation, token.rowcount = bin.unpack( "<SSI", data, pos )
-
- return pos, token
- end,
- --- Parses a DoneProc token recieved after executing a SP
- --
- -- @param data string containing "raw" data
- -- @param pos number containing offset into data
- -- @return pos number containing new offset after parse
- -- @return token table containing token specific fields
- [TokenType.DoneProc] = function( data, pos )
- local token
- pos, token = Token.Parse[TokenType.Done]( data, pos )
- token.type = TokenType.DoneProc
-
- return pos, token
- end,
-
- --- Parses a DoneInProc token recieved after executing a SP
- --
- -- @param data string containing "raw" data
- -- @param pos number containing offset into data
- -- @return pos number containing new offset after parse
- -- @return token table containing token specific fields
- [TokenType.DoneInProc] = function( data, pos )
- local token
- pos, token = Token.Parse[TokenType.Done]( data, pos )
- token.type = TokenType.DoneInProc
-
- return pos, token
- end,
-
- --- Parses a ReturnStatus token
- --
- -- @param data string containing "raw" data
- -- @param pos number containing offset into data
- -- @return pos number containing new offset after parse
- -- @return token table containing token specific fields
- [TokenType.ReturnStatus] = function( data, pos )
- local token = {}
-
- pos, token.value = bin.unpack("<i", data, pos)
- token.type = TokenType.ReturnStatus
- return pos, token
- end,
-
- --- Parses a OrderBy token
- --
- -- @param data string containing "raw" data
- -- @param pos number containing offset into data
- -- @return pos number containing new offset after parse
- -- @return token table containing token specific fields
- [TokenType.OrderBy] = function( data, pos )
- local token = {}
-
- pos, token.size = bin.unpack("<S", data, pos)
- token.type = TokenType.OrderBy
- return pos + token.size, token
- end,
-
- --- Parse TDS result tokens
- --
- -- @param data string containing "raw" data
- -- @param pos number containing offset into data
- -- @return pos number containing new offset after parse
- -- @return token table containing token specific fields
- [TokenType.TDS7Results] = function( data, pos )
- local token = {}
- local _
- token.type = TokenType.TDS7Results
- pos, token.count = bin.unpack( "<S", data, pos )
- token.colinfo = {}
- for i=1, token.count do
- local colinfo = {}
- local usertype, flags, ttype
- pos, usertype, flags, ttype = bin.unpack("<SSC", data, pos )
- if ( not(ColumnInfo.Parse[ttype]) ) then
- return -1, ("Unhandled data type: 0x%X"):format(ttype)
- end
- pos, colinfo = ColumnInfo.Parse[ttype]( data, pos )
- colinfo.usertype = usertype
- colinfo.flags = flags
- colinfo.type = ttype
-
- table.insert( token.colinfo, colinfo )
- end
- return pos, token
- end,
-
-
- [TokenType.NTLMSSP_CHALLENGE] = function(data, pos)
- local pos, len, ntlmssp, msgtype = bin.unpack("<SA8I", data, pos)
- local NTLMSSP_CHALLENGE = 2
- if ( ntlmssp ~= "NTLMSSP\0" or msgtype ~= NTLMSSP_CHALLENGE ) then
- return -1, "Failed to process NTLMSSP Challenge"
- end
-
- local ntlm_challenge = data:sub( 28, 35 )
- pos = pos + len - 13
- return pos, ntlm_challenge
- end,
- },
-
- --- Parses the first token at positions pos
- --
- -- @param data string containing "raw" data
- -- @param pos number containing offset into data
- -- @return pos number containing new offset after parse or -1 on error
- -- @return token table containing token specific fields or error message on error
- ParseToken = function( data, pos )
- local ttype
- pos, ttype = bin.unpack("C", data, pos)
- if ( not(Token.Parse[ttype]) ) then
- stdnse.print_debug( 1, "%s: No parser for token type 0x%X", "MSSQL", ttype )
- return -1, ("No parser for token type: 0x%X"):format( ttype )
- end
-
- return Token.Parse[ttype](data, pos)
- end,
-
- }
- --- QueryPacket class
- QueryPacket =
- {
- new = function(self,o)
- o = o or {}
- setmetatable(o, self)
- self.__index = self
- return o
- end,
-
- SetQuery = function( self, query )
- self.query = query
- end,
-
- --- Returns the query packet as string
- --
- -- @return string containing the authentication packet
- ToString = function( self )
- return PacketType.Query, Util.ToWideChar( self.query )
- end,
-
- }
- --- PreLoginPacket class
- PreLoginPacket =
- {
- -- TDS pre-login option types
- OPTION_TYPE = {
- Version = 0x00,…