PageRenderTime 80ms CodeModel.GetById 25ms RepoModel.GetById 1ms app.codeStats 0ms

/nselib/mssql.lua

https://github.com/prakashgamit/nmap
Lua | 3173 lines | 1968 code | 518 blank | 687 comment | 313 complexity | 4f8391b5a235ef7c0a2568f454d42a87 MD5 | raw file
Possible License(s): BSD-3-Clause, GPL-2.0, LGPL-2.0, LGPL-2.1
  1. ---
  2. -- MSSQL Library supporting a very limited subset of operations.
  3. --
  4. -- The library was designed and tested against Microsoft SQL Server 2005.
  5. -- However, it should work with versions 7.0, 2000, 2005, 2008 and 2012.
  6. -- Only a minimal amount of parsers have been added for tokens, column types
  7. -- and column data in order to support the first scripts.
  8. --
  9. -- The code has been implemented based on traffic analysis and the following
  10. -- documentation:
  11. -- * SSRP Protocol Specification: http://msdn.microsoft.com/en-us/library/cc219703.aspx
  12. -- * TDS Protocol Specification: http://msdn.microsoft.com/en-us/library/dd304523.aspx
  13. -- * TDS Protocol Documentation: http://www.freetds.org/tds.html.
  14. -- * The JTDS source code: http://jtds.sourceforge.net/index.html.
  15. --
  16. -- * SSRP: Class that handles communication over the SQL Server Resolution Protocol, used for identifying instances on a host.
  17. -- * 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.
  18. -- * ColumnData: Class containing parsers for the actual column information.
  19. -- * 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.
  20. -- * QueryPacket: Class used to hold a query and convert it to a string suitable for transmission over a socket.
  21. -- * LoginPacket: Class used to hold login specific data which can easily be converted to a string suitable for transmission over a socket.
  22. -- * PreLoginPacket: Class used to (partially) implement the TDS PreLogin packet
  23. -- * 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.
  24. -- * Helper: Class which facilitates the use of the library by through action oriented functions with descriptive names.
  25. -- * Util: A "static" class containing mostly character and type conversion functions.
  26. --
  27. -- The following sample code illustrates how scripts can use the Helper class
  28. -- to interface the library:
  29. --
  30. -- <code>
  31. -- local helper = mssql.Helper:new()
  32. -- status, result = helper:Connect( host, port )
  33. -- status, result = helper:Login( username, password, "temdpb", host.ip )
  34. -- status, result = helper:Query( "SELECT name FROM master..syslogins" )
  35. -- helper:Disconnect()
  36. -- </code>
  37. --
  38. -- The following sample code illustrates how scripts can use the Helper class
  39. -- with pre-discovered instances (e.g. by <code>ms-sql-discover</code> or <code>broadcast-ms-sql-discover</code>):
  40. --
  41. -- <code>
  42. -- local instance = mssql.Helper.GetDiscoveredInstances( host, port )
  43. -- if ( instance ) then
  44. -- local helper = mssql.Helper:new()
  45. -- status, result = helper:ConnectEx( instance )
  46. -- status, result = helper:LoginEx( instance )
  47. -- status, result = helper:Query( "SELECT name FROM master..syslogins" )
  48. -- helper:Disconnect()
  49. -- end
  50. -- </code>
  51. --
  52. -- Known limitations:
  53. -- * 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.
  54. -- * Version 7 and ONLY version 7 of the protocol is supported. This should cover Microsoft SQL Server 7.0 and later.
  55. -- * 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.
  56. -- * 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.
  57. -- * No functionality for languages, localization or characted codepages has been considered or implemented.
  58. -- * The library does database authentication only. No OS authentication or use of the integrated security model is supported.
  59. -- * Queries using SELECT, INSERT, DELETE and EXEC of procedures have been tested while developing scripts.
  60. --
  61. -- @copyright Same as Nmap--See http://nmap.org/book/man-legal.html
  62. --
  63. -- @author "Patrik Karlsson <patrik@cqure.net>, Chris Woodbury"
  64. --
  65. -- @args mssql.username The username to use to connect to SQL Server instances.
  66. -- This username is used by scripts taking actions that require
  67. -- authentication (e.g. <code>ms-sql-query</code>) This username (and its
  68. -- associated password) takes precedence over any credentials discovered
  69. -- by the <code>ms-sql-brute</code> and <code>ms-sql-empty-password</code>
  70. -- scripts.
  71. --
  72. -- @args mssql.password The password for <code>mssql.username</code>. If this
  73. -- argument is not given but <code>mssql.username</code>, a blank password
  74. -- is used.
  75. --
  76. -- @args mssql.instance-name The name of the instance to connect to.
  77. --
  78. -- @args mssql.instance-port The port of the instance to connect to.
  79. --
  80. -- @args mssql.instance-all Targets all SQL server instances discovered
  81. -- throught the browser service.
  82. --
  83. -- @args mssql.domain The domain against which to perform integrated
  84. -- authentication. When set, the scripts assume integrated authentication
  85. -- should be performed, rather than the default sql login.
  86. --
  87. -- @args mssql.protocol The protocol to use to connect to the instance. The
  88. -- protocol may be either <code>NP</code>,<code>Named Pipes</code> or
  89. -- <code>TCP</code>.
  90. --
  91. -- @args mssql.timeout How long to wait for SQL responses. This is a number
  92. -- followed by <code>ms</code> for milliseconds, <code>s</code> for
  93. -- seconds, <code>m</code> for minutes, or <code>h</code> for hours.
  94. -- Default: <code>30s</code>.
  95. --
  96. -- @args mssql.scanned-ports-only If set, the script will only connect
  97. -- to ports that were included in the Nmap scan. This may result in
  98. -- instances not being discovered, particularly if UDP port 1434 is not
  99. -- included. Additionally, instances that are found to be running on
  100. -- ports that were not scanned (e.g. if 1434/udp is in the scan and the
  101. -- SQL Server Browser service on that port reports an instance
  102. -- listening on 43210/tcp, which was not scanned) will be reported but
  103. -- will not be stored for use by other ms-sql-* scripts.
  104. local bin = require "bin"
  105. local bit = require "bit"
  106. local math = require "math"
  107. local match = require "match"
  108. local nmap = require "nmap"
  109. local os = require "os"
  110. local shortport = require "shortport"
  111. local smb = require "smb"
  112. local smbauth = require "smbauth"
  113. local stdnse = require "stdnse"
  114. local strbuf = require "strbuf"
  115. local string = require "string"
  116. local table = require "table"
  117. _ENV = stdnse.module("mssql", stdnse.seeall)
  118. -- Created 01/17/2010 - v0.1 - created by Patrik Karlsson <patrik@cqure.net>
  119. -- Revised 03/28/2010 - v0.2 - fixed incorrect token types. added 30 seconds timeout
  120. -- Revised 01/23/2011 - v0.3 - fixed parsing error in discovery code with patch
  121. -- from Chris Woodbury
  122. -- Revised 02/01/2011 - v0.4 - numerous changes and additions to support new
  123. -- functionality in ms-sql- scripts and to be more
  124. -- robust in parsing and handling data. (Chris Woodbury)
  125. -- Revised 02/19/2011 - v0.5 - numerous changes in script, library behaviour
  126. -- * huge improvements in version detection
  127. -- * added support for named pipes
  128. -- * added support for integrated NTLMv1 authentication
  129. --
  130. -- (Patrik Karlsson, Chris Woodbury)
  131. -- Revised 08/19/2012 - v0.6 - added multiple data types
  132. -- * added detection and handling of null values when processing query responses from the server
  133. -- * added DoneProc response token support
  134. --
  135. -- (Tom Sellers)
  136. -- Updated 10/01/2012 - v0.7 - added support for 2012 and later service packs for 2005, 2008 and 2008 R2 (Rob Nicholls)
  137. local HAVE_SSL, openssl = pcall(require, "openssl")
  138. do
  139. namedpipes = smb.namedpipes
  140. local arg = stdnse.get_script_args( "mssql.timeout" ) or "30s"
  141. local timeout, err = stdnse.parse_timespec(arg)
  142. if not timeout then
  143. error(err)
  144. end
  145. MSSQL_TIMEOUT = timeout
  146. SCANNED_PORTS_ONLY = false
  147. if ( stdnse.get_script_args( "mssql.scanned-ports-only" ) ) then
  148. SCANNED_PORTS_ONLY = true
  149. end
  150. end
  151. -- *************************************
  152. -- Informational Classes
  153. -- *************************************
  154. --- SqlServerInstanceInfo class
  155. SqlServerInstanceInfo =
  156. {
  157. instanceName = nil,
  158. version = nil,
  159. serverName = nil,
  160. isClustered = nil,
  161. host = nil,
  162. port = nil,
  163. pipeName = nil,
  164. new = function(self,o)
  165. o = o or {}
  166. setmetatable(o, self)
  167. self.__index = self
  168. return o
  169. end,
  170. -- Compares two SqlServerInstanceInfo objects and determines whether they
  171. -- refer to the same SQL Server instance, judging by a combination of host,
  172. -- port, named pipe information and instance name.
  173. __eq = function( self, other )
  174. local areEqual
  175. if ( not (self.host and other.host) ) then
  176. -- if they don't both have host information, we certainly can't say
  177. -- whether they're the same
  178. areEqual = false
  179. else
  180. areEqual = (self.host.ip == other.host.ip)
  181. end
  182. if (self.port and other.port) then
  183. areEqual = areEqual and ( other.port.number == self.port.number and
  184. other.port.protocol == self.port.protocol )
  185. elseif (self.pipeName and other.pipeName) then
  186. areEqual = areEqual and (self.pipeName == other.pipeName)
  187. elseif (self.instanceName and other.instanceName) then
  188. areEqual = areEqual and (self.instanceName == other.instanceName)
  189. else
  190. -- if we have neither port nor named pipe info nor instance names,
  191. -- we can't say whether they're the same
  192. areEqual = false
  193. end
  194. return areEqual
  195. end,
  196. --- Merges the data from one SqlServerInstanceInfo object into another. Each
  197. -- field in the first object is populated with the data from that field in
  198. -- second object if the first object's field is nil OR if <code>overwrite</code>
  199. -- is set to true. A special case is made for the <code>version</code> field,
  200. -- which is only overwritten in the second object has more reliable version
  201. -- information. The second object is not modified.
  202. Merge = function( self, other, overwrite )
  203. local mergeFields = { "host", "port", "instanceName", "version", "isClustered", "pipeName" }
  204. for _, fieldname in ipairs( mergeFields ) do
  205. -- Add values from other only if self doesn't have a value, or if overwrite is true
  206. if ( other[ fieldname ] ~= nil and (overwrite or self[ fieldname ] == nil) ) then
  207. self[ fieldname ] = other[ fieldname ]
  208. end
  209. end
  210. if (self.version and self.version.source == "SSRP" and
  211. other.version and other.version.Source == "SSNetLib") then
  212. self.version = other.version
  213. end
  214. end,
  215. --- Returns a name for the instance, based on the available information. This
  216. -- may take one of the following forms:
  217. -- * HOST\INSTANCENAME
  218. -- * PIPENAME
  219. -- * HOST:PORT
  220. GetName = function( self )
  221. if (self.instanceName) then
  222. return string.format( "%s\\%s", self.host.ip or self.serverName or "[nil]", self.instanceName or "[nil]" )
  223. elseif (self.pipeName) then
  224. return string.format( "%s", self.pipeName )
  225. else
  226. return string.format( "%s:%s", self.host.ip or self.serverName or "[nil]", (self.port and self.port.number) or "[nil]" )
  227. end
  228. end,
  229. --- Sets whether the instance is in a cluster
  230. --
  231. -- @param self
  232. -- @param isClustered Boolean true or the string "Yes" are interpreted as true;
  233. -- all other values are interpreted as false.
  234. SetIsClustered = function( self, isClustered )
  235. self.isClustered = (isClustered == true) or (isClustered == "Yes")
  236. end,
  237. --- Indicates whether this instance has networking protocols enabled, such
  238. -- that scripts could attempt to connect to it.
  239. HasNetworkProtocols = function( self )
  240. return (self.pipeName ~= nil) or (self.port and self.port.number)
  241. end,
  242. }
  243. --- SqlServerVersionInfo class
  244. SqlServerVersionInfo =
  245. {
  246. versionNumber = "", -- The full version string (e.g. "9.00.2047.00")
  247. major = nil, -- The major version (e.g. 9)
  248. minor = nil, -- The minor version (e.g. 0)
  249. build = nil, -- The build number (e.g. 2047)
  250. subBuild = nil, -- The sub-build number (e.g. 0)
  251. productName = nil, -- The prodcut name (e.g. "SQL Server 2005")
  252. brandedVersion = nil, -- The branded version of the product (e.g. "2005")
  253. servicePackLevel = nil, -- The service pack leve (e.g. "SP1")
  254. patched = nil, -- Whether patches have been applied since SP installation (true/false/nil)
  255. source = nil, -- The source of the version info (e.g. "SSRP", "SSNetLib")
  256. new = function(self,o)
  257. o = o or {}
  258. setmetatable(o, self)
  259. self.__index = self
  260. return o
  261. end,
  262. --- Sets the version using a version number string.
  263. --
  264. -- @param versionNumber a version number string (e.g. "9.00.1399.00")
  265. -- @param source a string indicating the source of the version info (e.g. "SSRP", "SSNetLib")
  266. SetVersionNumber = function(self, versionNumber, source)
  267. local major, minor, revision, subBuild
  268. if versionNumber:match( "^%d+%.%d+%.%d+.%d+" ) then
  269. major, minor, revision, subBuild = versionNumber:match( "^(%d+)%.(%d+)%.(%d+)" )
  270. elseif versionNumber:match( "^%d+%.%d+%.%d+" ) then
  271. major, minor, revision = versionNumber:match( "^(%d+)%.(%d+)%.(%d+)" )
  272. else
  273. stdnse.print_debug( 1, "%s: SetVersionNumber: versionNumber is not in correct format: %s", "MSSQL", versionNumber or "nil" )
  274. end
  275. self:SetVersion( major, minor, revision, subBuild, source )
  276. end,
  277. --- Sets the version using the individual numeric components of the version
  278. -- number.
  279. --
  280. -- @param source a string indicating the source of the version info (e.g. "SSRP", "SSNetLib")
  281. SetVersion = function(self, major, minor, build, subBuild, source)
  282. self.source = source
  283. -- make sure our version numbers all end up as valid numbers
  284. self.major, self.minor, self.build, self.subBuild =
  285. tonumber( major or 0 ), tonumber( minor or 0 ), tonumber( build or 0 ), tonumber( subBuild or 0 )
  286. self.versionNumber = string.format( "%u.%02u.%u.%02u", self.major, self.minor, self.build, self.subBuild )
  287. self:_ParseVersionInfo()
  288. end,
  289. --- Using the version number, determines the product version
  290. _InferProductVersion = function(self)
  291. local VERSION_LOOKUP_TABLE = {
  292. ["^6%.0"] = "6.0", ["^6%.5"] = "6.5", ["^7%.0"] = "7.0",
  293. ["^8%.0"] = "2000", ["^9%.0"] = "2005", ["^10%.0"] = "2008",
  294. ["^10%.50"] = "2008 R2", ["^11%.0"] = "2012",
  295. }
  296. local product = ""
  297. for m, v in pairs(VERSION_LOOKUP_TABLE) do
  298. if ( self.versionNumber:match(m) ) then
  299. product = v
  300. self.brandedVersion = product
  301. break
  302. end
  303. end
  304. self.productName = ("Microsoft SQL Server %s"):format(product)
  305. end,
  306. --- Returns a lookup table that maps revision numbers to service pack levels for
  307. -- the applicable SQL Server version (e.g. { {1600, "RTM"}, {2531, "SP1"} }).
  308. _GetSpLookupTable = function(self)
  309. -- Service pack lookup tables:
  310. -- For instances where a revised service pack was released (e.g. 2000 SP3a), we will include the
  311. -- build number for the original SP and the build number for the revision. However, leaving it
  312. -- like this would make it appear that subsequent builds were a patched version of the revision
  313. -- (e.g. a patch applied to 2000 SP3 that increased the build number to 780 would get displayed
  314. -- as "SP3a+", when it was actually SP3+). To avoid this, we will include an additional fake build
  315. -- number that combines the two.
  316. local SP_LOOKUP_TABLE_6_5 = { {201, "RTM"}, {213, "SP1"}, {240, "SP2"}, {258, "SP3"}, {281, "SP4"},
  317. {415, "SP5"}, {416, "SP5a"}, {417, "SP5/SP5a"}, }
  318. local SP_LOOKUP_TABLE_7 = { {623, "RTM"}, {699, "SP1"}, {842, "SP2"}, {961, "SP3"}, {1063, "SP4"}, }
  319. local SP_LOOKUP_TABLE_2000 = { {194, "RTM"}, {384, "SP1"}, {532, "SP2"}, {534, "SP2"}, {760, "SP3"},
  320. {766, "SP3a"}, {767, "SP3/SP3a"}, {2039, "SP4"}, }
  321. local SP_LOOKUP_TABLE_2005 = { {1399, "RTM"}, {2047, "SP1"}, {3042, "SP2"}, {4035, "SP3"}, {5000, "SP4"}, }
  322. local SP_LOOKUP_TABLE_2008 = { {1600, "RTM"}, {2531, "SP1"}, {4000, "SP2"}, {5500, "SP3"}, }
  323. local SP_LOOKUP_TABLE_2008R2 = { {1600, "RTM"}, {2500, "SP1"}, {4000, "SP2"}, }
  324. local SP_LOOKUP_TABLE_2012 = { {2100, "RTM"}, }
  325. if ( not self.brandedVersion ) then
  326. self:_InferProductVersion()
  327. end
  328. local spLookupTable
  329. if self.brandedVersion == "6.5" then spLookupTable = SP_LOOKUP_TABLE_6_5
  330. elseif self.brandedVersion == "7.0" then spLookupTable = SP_LOOKUP_TABLE_7
  331. elseif self.brandedVersion == "2000" then spLookupTable = SP_LOOKUP_TABLE_2000
  332. elseif self.brandedVersion == "2005" then spLookupTable = SP_LOOKUP_TABLE_2005
  333. elseif self.brandedVersion == "2008" then spLookupTable = SP_LOOKUP_TABLE_2008
  334. elseif self.brandedVersion == "2008 R2" then spLookupTable = SP_LOOKUP_TABLE_2008R2
  335. elseif self.brandedVersion == "2012" then spLookupTable = SP_LOOKUP_TABLE_2012
  336. end
  337. return spLookupTable
  338. end,
  339. --- Processes version data to determine (if possible) the product version,
  340. -- service pack level and patch status.
  341. _ParseVersionInfo = function(self)
  342. local spLookupTable = self:_GetSpLookupTable()
  343. if spLookupTable then
  344. local spLookupItr = 0
  345. -- Loop through the service pack levels until we find one whose revision
  346. -- number is the same as or lower than our revision number.
  347. while spLookupItr < #spLookupTable do
  348. spLookupItr = spLookupItr + 1
  349. if (spLookupTable[ spLookupItr ][1] == self.build ) then
  350. spLookupItr = spLookupItr
  351. break
  352. elseif (spLookupTable[ spLookupItr ][1] > self.build ) then
  353. -- The target revision number is lower than the first release
  354. if spLookupItr == 1 then
  355. self.servicePackLevel = "Pre-RTM"
  356. else
  357. -- we went too far - it's the previous SP, but with patches applied
  358. spLookupItr = spLookupItr - 1
  359. end
  360. break
  361. end
  362. end
  363. -- Now that we've identified the proper service pack level:
  364. if self.servicePackLevel ~= "Pre-RTM" then
  365. self.servicePackLevel = spLookupTable[ spLookupItr ][2]
  366. if ( spLookupTable[ spLookupItr ][1] == self.build ) then
  367. self.patched = false
  368. else
  369. self.patched = true
  370. end
  371. end
  372. -- Clean up some of our inferences. If the source of our revision number
  373. -- was the SSRP (SQL Server Browser) response, we need to recognize its
  374. -- limitations:
  375. -- * Versions of SQL Server prior to 2005 are reported with the RTM build
  376. -- number, regardless of the actual version (e.g. SQL Server 2000 is
  377. -- always 8.00.194).
  378. -- * Versions of SQL Server starting with 2005 (and going through at least
  379. -- 2008) do better but are still only reported with the build number as
  380. -- of the last service pack (e.g. SQL Server 2005 SP3 with patches is
  381. -- still reported as 9.00.4035.00).
  382. if ( self.source == "SSRP" ) then
  383. self.patched = nil
  384. if ( self.major <= 8 ) then
  385. self.servicePackLevel = nil
  386. end
  387. end
  388. end
  389. return true
  390. end,
  391. ---
  392. ToString = function(self)
  393. local friendlyVersion = strbuf.new()
  394. if self.productName then
  395. friendlyVersion:concatbuf( self.productName )
  396. if self.servicePackLevel then
  397. friendlyVersion:concatbuf( " " )
  398. friendlyVersion:concatbuf( self.servicePackLevel )
  399. end
  400. if self.patched then
  401. friendlyVersion:concatbuf( "+" )
  402. end
  403. end
  404. return friendlyVersion:dump()
  405. end,
  406. --- Uses the information in this SqlServerVersionInformation object to
  407. -- populate the version information in an Nmap port table for a SQL Server
  408. -- TCP listener.
  409. --
  410. -- @param self A SqlServerVersionInformation object
  411. -- @param port An Nmap port table corresponding to the instance
  412. PopulateNmapPortVersion = function(self, port)
  413. port.service = "ms-sql-s"
  414. port.version = port.version or {}
  415. port.version.name = "ms-sql-s"
  416. port.version.product = self.productName
  417. local versionString = strbuf.new()
  418. if self.source ~= "SSRP" then
  419. versionString:concatbuf( self.versionNumber )
  420. if self.servicePackLevel then
  421. versionString:concatbuf( "; " )
  422. versionString:concatbuf( self.servicePackLevel )
  423. end
  424. if self.patched then
  425. versionString:concatbuf( "+" )
  426. end
  427. port.version.version = versionString:dump()
  428. end
  429. return port
  430. end,
  431. }
  432. -- *************************************
  433. -- SSRP (SQL Server Resolution Protocol)
  434. -- *************************************
  435. SSRP =
  436. {
  437. PORT = { number = 1434, protocol = "udp" },
  438. DEBUG_ID = "MSSQL-SSRP",
  439. MESSAGE_TYPE =
  440. {
  441. ClientBroadcast = 0x02,
  442. ClientUnicast = 0x03,
  443. ClientUnicastInstance = 0x04,
  444. ClientUnicastDAC = 0x0F,
  445. ServerResponse = 0x05,
  446. },
  447. --- Parses an SSRP string and returns a table containing one or more
  448. -- SqlServerInstanceInfo objects created from the parsed string.
  449. _ParseSsrpString = function( host, ssrpString )
  450. -- It would seem easier to just capture (.-;;) repeateadly, since
  451. -- each instance ends with ";;", but ";;" can also occur within the
  452. -- data, signifying an empty field (e.g. "...bv;;@COMPNAME;;tcp;1433;;...").
  453. -- So, instead, we'll split up the string ahead of time.
  454. -- See the SSRP specification for more details.
  455. local instanceStrings = {}
  456. local firstInstanceEnd, instanceString
  457. repeat
  458. firstInstanceEnd = ssrpString:find( ";ServerName;(.-);InstanceName;(.-);IsClustered;(.-);" )
  459. if firstInstanceEnd then
  460. instanceString = ssrpString:sub( 1, firstInstanceEnd )
  461. ssrpString = ssrpString:sub( firstInstanceEnd + 1 )
  462. else
  463. instanceString = ssrpString
  464. end
  465. table.insert( instanceStrings, instanceString )
  466. until (not firstInstanceEnd)
  467. stdnse.print_debug( 2, "%s: SSRP Substrings:\n %s", SSRP.DEBUG_ID, stdnse.strjoin( "\n ", instanceStrings ) )
  468. local instances = {}
  469. for _, instanceString in ipairs( instanceStrings ) do
  470. local instance = SqlServerInstanceInfo:new()
  471. local version = SqlServerVersionInfo:new()
  472. instance.version = version
  473. instance.host = host
  474. instance.serverName = instanceString:match( "ServerName;(.-);")
  475. instance.instanceName = instanceString:match( "InstanceName;(.-);")
  476. instance:SetIsClustered( instanceString:match( "IsClustered;(.-);") )
  477. version:SetVersionNumber( instanceString:match( "Version;(.-);"), "SSRP" )
  478. local tcpPort = tonumber( instanceString:match( ";tcp;(.-);") )
  479. if tcpPort then instance.port = {number = tcpPort, protocol = "tcp"} end
  480. local pipeName = instanceString:match( ";np;(.-);")
  481. local status, pipeSubPath = namedpipes.get_pipe_subpath( pipeName )
  482. if status then
  483. pipeName = namedpipes.make_pipe_name( host.ip, pipeSubPath )
  484. elseif pipeName ~= nil then
  485. stdnse.print_debug( 1, "%s: Invalid pipe name:\n%s", SSRP.DEBUG_ID, pipeName )
  486. end
  487. instance.pipeName = pipeName
  488. table.insert( instances, instance )
  489. end
  490. return instances
  491. end,
  492. ---
  493. _ProcessResponse = function( host, responseData )
  494. local instances
  495. local pos, messageType, dataLength = 1, nil, nil
  496. pos, messageType, dataLength = bin.unpack("<CS", responseData, 1)
  497. -- extract the response data (i.e. everything after the 3-byte header)
  498. responseData = responseData:sub(4)
  499. stdnse.print_debug( 2, "%s: SSRP Data: %s", SSRP.DEBUG_ID, responseData )
  500. if ( messageType ~= SSRP.MESSAGE_TYPE.ServerResponse or
  501. dataLength ~= responseData:len() ) then
  502. stdnse.print_debug( 2, "%s: Invalid SSRP response. Type: 0x%02x, Length: %d, Actual length: %d",
  503. SSRP.DEBUG_ID, messageType, dataLength, responseData:len() )
  504. else
  505. instances = SSRP._ParseSsrpString( host, responseData )
  506. end
  507. return instances
  508. end,
  509. --- Attempts to retrieve information about SQL Server instances by querying
  510. -- the SQL Server Browser service on a host.
  511. --
  512. -- @param host A host table for the target host
  513. -- @param port (Optional) A port table for the target SQL Server Browser service
  514. -- @return (status, result) If status is true, result is a table of
  515. -- SqlServerInstanceInfo objects. If status is false, result is an
  516. -- error message.
  517. DiscoverInstances = function( host, port )
  518. port = port or SSRP.PORT
  519. if ( SCANNED_PORTS_ONLY and nmap.get_port_state( host, port ) == nil ) then
  520. stdnse.print_debug( 2, "%s: Discovery disallowed: scanned-ports-only is set and port %d was not scanned", SSRP.DEBUG_ID, port.number )
  521. return false, "Discovery disallowed: scanned-ports-only"
  522. end
  523. local socket = nmap.new_socket("udp")
  524. socket:set_timeout(5000)
  525. if ( port.number ~= SSRP.PORT.number ) then
  526. stdnse.print_debug( 1, "%s: DiscoverInstances() called with non-standard port (%d)", SSRP.DEBUG_ID, port.number )
  527. end
  528. local status, err = socket:connect( host, port )
  529. if ( not(status) ) then return false, err end
  530. status, err = socket:send( bin.pack( "C", SSRP.MESSAGE_TYPE.ClientUnicast ) )
  531. if ( not(status) ) then return false, err end
  532. local responseData, instances_host
  533. status, responseData = socket:receive()
  534. if ( not(status) ) then return false, responseData
  535. else
  536. instances_host = SSRP._ProcessResponse( host, responseData )
  537. end
  538. socket:close()
  539. return status, instances_host
  540. end,
  541. --- Attempts to retrieve information about SQL Server instances by querying
  542. -- the SQL Server Browser service on a broadcast domain.
  543. --
  544. -- @param host A host table for the broadcast specification
  545. -- @param port (Optional) A port table for the target SQL Server Browser service
  546. -- @return (status, result) If status is true, result is a table of
  547. -- tables containing SqlServerInstanceInfo objects. The top-level table
  548. -- is indexed by IP address. If status is false, result is an
  549. -- error message.
  550. DiscoverInstances_Broadcast = function( host, port )
  551. port = port or SSRP.PORT
  552. local socket = nmap.new_socket("udp")
  553. socket:set_timeout(5000)
  554. local instances_all = {}
  555. if ( port.number ~= SSRP.PORT.number ) then
  556. stdnse.print_debug( 1, "%S: DiscoverInstances_Broadcast() called with non-standard port (%d)", SSRP.DEBUG_ID, port.number )
  557. end
  558. local status, err = socket:sendto(host, port, bin.pack( "C", SSRP.MESSAGE_TYPE.ClientBroadcast ))
  559. if ( not(status) ) then return false, err end
  560. while ( status ) do
  561. local responseData
  562. status, responseData = socket:receive()
  563. if ( status ) then
  564. local remoteIp, _
  565. status, _, _, remoteIp, _ = socket:get_info()
  566. local instances_host = SSRP._ProcessResponse( {ip = remoteIp, name = ""}, responseData )
  567. instances_all[ remoteIp ] = instances_host
  568. end
  569. end
  570. socket:close()
  571. return true, instances_all
  572. end,
  573. }
  574. -- *************************
  575. -- TDS (Tabular Data Stream)
  576. -- *************************
  577. -- TDS packet types
  578. PacketType =
  579. {
  580. Query = 0x01,
  581. Response = 0x04,
  582. Login = 0x10,
  583. NTAuthentication = 0x11,
  584. PreLogin = 0x12,
  585. }
  586. -- TDS response token types
  587. TokenType =
  588. {
  589. ReturnStatus = 0x79,
  590. TDS7Results = 0x81,
  591. ErrorMessage = 0xAA,
  592. InformationMessage = 0xAB,
  593. LoginAcknowledgement = 0xAD,
  594. Row = 0xD1,
  595. OrderBy = 0xA9,
  596. EnvironmentChange = 0xE3,
  597. NTLMSSP_CHALLENGE = 0xed,
  598. Done = 0xFD,
  599. DoneProc = 0xFE,
  600. DoneInProc = 0xFF,
  601. }
  602. -- SQL Server/Sybase data types
  603. DataTypes =
  604. {
  605. SQLTEXT = 0x23,
  606. GUIDTYPE = 0x24,
  607. SYBINTN = 0x26,
  608. SYBINT2 = 0x34,
  609. SYBINT4 = 0x38,
  610. SYBDATETIME = 0x3D,
  611. NTEXTTYPE = 0x63,
  612. BITNTYPE = 0x68,
  613. DECIMALNTYPE = 0x6A,
  614. NUMERICNTYPE = 0x6C,
  615. FLTNTYPE = 0x6D,
  616. MONEYNTYPE = 0x6E,
  617. SYBDATETIMN = 0x6F,
  618. XSYBVARBINARY = 0xA5,
  619. XSYBVARCHAR = 0xA7,
  620. BIGBINARYTYPE = 0xAD,
  621. BIGCHARTYPE = 0xAF,
  622. XSYBNVARCHAR = 0xE7,
  623. SQLNCHAR = 0xEF,
  624. }
  625. -- SQL Server login error codes
  626. -- See http://msdn.microsoft.com/en-us/library/ms131024.aspx
  627. LoginErrorType =
  628. {
  629. AccountLockedOut = 15113,
  630. NotAssociatedWithTrustedConnection = 18452, -- This probably means that the server is set for Windows authentication only
  631. InvalidUsernameOrPassword = 18456,
  632. PasswordChangeFailed_PasswordNotAllowed = 18463,
  633. PasswordChangeFailed_PasswordTooShort = 18464,
  634. PasswordChangeFailed_PasswordTooLong = 18465,
  635. PasswordChangeFailed_PasswordNotComplex = 18466,
  636. PasswordChangeFailed_PasswordFilter = 18467,
  637. PasswordChangeFailed_UnexpectedError = 18468,
  638. PasswordExpired = 18487,
  639. PasswordMustChange = 18488,
  640. }
  641. LoginErrorMessage = {}
  642. for i, v in pairs(LoginErrorType) do
  643. LoginErrorMessage[v] = i
  644. end
  645. -- "static" ColumInfo parser class
  646. ColumnInfo =
  647. {
  648. Parse =
  649. {
  650. [DataTypes.SQLTEXT] = function( data, pos )
  651. local colinfo = {}
  652. local tmp
  653. pos, colinfo.unknown, colinfo.codepage, colinfo.flags, colinfo.charset = bin.unpack("<ISSC", data, pos )
  654. pos, colinfo.tablenamelen = bin.unpack("s", data, pos )
  655. pos, colinfo.tablename = bin.unpack("A" .. (colinfo.tablenamelen * 2), data, pos)
  656. pos, colinfo.msglen = bin.unpack("<C", data, pos )
  657. pos, tmp = bin.unpack("A" .. (colinfo.msglen * 2), data, pos)
  658. colinfo.text = Util.FromWideChar(tmp)
  659. return pos, colinfo
  660. end,
  661. [DataTypes.GUIDTYPE] = function( data, pos )
  662. return ColumnInfo.Parse[DataTypes.SYBINTN](data, pos)
  663. end,
  664. [DataTypes.SYBINTN] = function( data, pos )
  665. local colinfo = {}
  666. local tmp
  667. pos, colinfo.unknown, colinfo.msglen = bin.unpack("<CC", data, pos)
  668. pos, tmp = bin.unpack("A" .. (colinfo.msglen * 2), data, pos )
  669. colinfo.text = Util.FromWideChar(tmp)
  670. return pos, colinfo
  671. end,
  672. [DataTypes.SYBINT2] = function( data, pos )
  673. return ColumnInfo.Parse[DataTypes.SYBDATETIME](data, pos)
  674. end,
  675. [DataTypes.SYBINT4] = function( data, pos )
  676. return ColumnInfo.Parse[DataTypes.SYBDATETIME](data, pos)
  677. end,
  678. [DataTypes.SYBDATETIME] = function( data, pos )
  679. local colinfo = {}
  680. local tmp
  681. pos, colinfo.msglen = bin.unpack("C", data, pos)
  682. pos, tmp = bin.unpack("A" .. (colinfo.msglen * 2), data, pos )
  683. colinfo.text = Util.FromWideChar(tmp)
  684. return pos, colinfo
  685. end,
  686. [DataTypes.NTEXTTYPE] = function( data, pos )
  687. return ColumnInfo.Parse[DataTypes.SQLTEXT](data, pos)
  688. end,
  689. [DataTypes.BITNTYPE] = function( data, pos )
  690. return ColumnInfo.Parse[DataTypes.SYBINTN](data, pos)
  691. end,
  692. [DataTypes.DECIMALNTYPE] = function( data, pos )
  693. local colinfo = {}
  694. local tmp
  695. pos, colinfo.unknown, colinfo.precision, colinfo.scale = bin.unpack("<CCC", data, pos)
  696. pos, colinfo.msglen = bin.unpack("<C",data,pos)
  697. pos, tmp = bin.unpack("A" .. (colinfo.msglen * 2), data, pos )
  698. colinfo.text = Util.FromWideChar(tmp)
  699. return pos, colinfo
  700. end,
  701. [DataTypes.NUMERICNTYPE] = function( data, pos )
  702. return ColumnInfo.Parse[DataTypes.DECIMALNTYPE](data, pos)
  703. end,
  704. [DataTypes.FLTNTYPE] = function( data, pos )
  705. return ColumnInfo.Parse[DataTypes.SYBINTN](data, pos)
  706. end,
  707. [DataTypes.MONEYNTYPE] = function( data, pos )
  708. return ColumnInfo.Parse[DataTypes.SYBINTN](data, pos)
  709. end,
  710. [DataTypes.SYBDATETIMN] = function( data, pos )
  711. return ColumnInfo.Parse[DataTypes.SYBINTN](data, pos)
  712. end,
  713. [DataTypes.XSYBVARBINARY] = function( data, pos )
  714. local colinfo = {}
  715. local tmp
  716. pos, colinfo.lts, colinfo.msglen = bin.unpack("<SC", data, pos)
  717. pos, tmp = bin.unpack("A" .. (colinfo.msglen * 2), data, pos )
  718. colinfo.text = Util.FromWideChar(tmp)
  719. return pos, colinfo
  720. end,
  721. [DataTypes.XSYBVARCHAR] = function( data, pos )
  722. return ColumnInfo.Parse[DataTypes.XSYBNVARCHAR](data, pos)
  723. end,
  724. [DataTypes.BIGBINARYTYPE] = function( data, pos )
  725. return ColumnInfo.Parse[DataTypes.XSYBVARBINARY](data, pos)
  726. end,
  727. [DataTypes.BIGCHARTYPE] = function( data, pos )
  728. return ColumnInfo.Parse[DataTypes.XSYBNVARCHAR](data, pos)
  729. end,
  730. [DataTypes.XSYBNVARCHAR] = function( data, pos )
  731. local colinfo = {}
  732. local tmp
  733. pos, colinfo.lts, colinfo.codepage, colinfo.flags, colinfo.charset,
  734. colinfo.msglen = bin.unpack("<SSSCC", data, pos )
  735. pos, tmp = bin.unpack("A" .. (colinfo.msglen * 2), data, pos)
  736. colinfo.text = Util.FromWideChar(tmp)
  737. return pos, colinfo
  738. end,
  739. [DataTypes.SQLNCHAR] = function( data, pos )
  740. return ColumnInfo.Parse[DataTypes.XSYBNVARCHAR](data, pos)
  741. end,
  742. }
  743. }
  744. -- "static" ColumData parser class
  745. ColumnData =
  746. {
  747. Parse = {
  748. [DataTypes.SQLTEXT] = function( data, pos )
  749. local len, coldata
  750. -- The first len value is the size of the meta data block
  751. -- for non-null values this seems to be 0x10 / 16 bytes
  752. pos, len = bin.unpack( "<C", data, pos )
  753. if ( len == 0 ) then
  754. return pos, 'Null'
  755. end
  756. -- Skip over the text update time and date values, we don't need them
  757. -- We may come back add parsing for this information.
  758. pos = pos + len
  759. -- skip a label, should be 'dummyTS'
  760. pos = pos + 8
  761. -- extract the actual data
  762. pos, len = bin.unpack( "<I", data, pos )
  763. pos, coldata = bin.unpack( "A"..len, data, pos )
  764. return pos, coldata
  765. end,
  766. [DataTypes.GUIDTYPE] = function( data, pos )
  767. local len, coldata, index, nextdata
  768. local hex = {}
  769. pos, len = bin.unpack("C", data, pos)
  770. if ( len == 0 ) then
  771. return pos, 'Null'
  772. elseif ( len == 16 ) then
  773. -- Return the first 8 bytes
  774. for index=1, 8 do
  775. pos, hex[index] = bin.unpack("H", data, pos)
  776. end
  777. -- reorder the bytes
  778. coldata = hex[4] .. hex[3] .. hex[2] .. hex[1]
  779. coldata = coldata .. '-' .. hex[6] .. hex[5]
  780. coldata = coldata .. '-' .. hex[8] .. hex[7]
  781. pos, nextdata = bin.unpack("H2", data, pos)
  782. coldata = coldata .. '-' .. nextdata
  783. pos, nextdata = bin.unpack("H6", data, pos)
  784. coldata = coldata .. '-' .. nextdata
  785. else
  786. stdnse.print_debug("Unhandled length (%d) for GUIDTYPE", len)
  787. return pos + len, 'Unsupported Data'
  788. end
  789. return pos, coldata
  790. end,
  791. [DataTypes.SYBINTN] = function( data, pos )
  792. local len, num
  793. pos, len = bin.unpack("C", data, pos)
  794. if ( len == 0 ) then
  795. return pos, 'Null'
  796. elseif ( len == 1 ) then
  797. return bin.unpack("C", data, pos)
  798. elseif ( len == 2 ) then
  799. return bin.unpack("<s", data, pos)
  800. elseif ( len == 4 ) then
  801. return bin.unpack("<i", data, pos)
  802. elseif ( len == 8 ) then
  803. return bin.unpack("<l", data, pos)
  804. else
  805. return -1, ("Unhandled length (%d) for SYBINTN"):format(len)
  806. end
  807. return -1, "Error"
  808. end,
  809. [DataTypes.SYBINT2] = function( data, pos )
  810. local num
  811. pos, num = bin.unpack("<S", data, pos)
  812. return pos, num
  813. end,
  814. [DataTypes.SYBINT4] = function( data, pos )
  815. local num
  816. pos, num = bin.unpack("<I", data, pos)
  817. return pos, num
  818. end,
  819. [DataTypes.SYBDATETIME] = function( data, pos )
  820. local hi, lo, result_seconds, result
  821. local tds_epoch, system_epoch, tds_offset_seconds
  822. pos, hi, lo = bin.unpack("<iI", data, pos)
  823. tds_epoch = os.time( {year = 1900, month = 1, day = 1, hour = 00, min = 00, sec = 00, isdst = nil} )
  824. -- determine the offset between the tds_epoch and the local system epoch
  825. system_epoch = os.time( os.date("*t", 0))
  826. tds_offset_seconds = os.difftime(tds_epoch,system_epoch)
  827. result_seconds = (hi*24*60*60) + (lo/300)
  828. result = os.date("!%b %d, %Y %H:%M:%S", tds_offset_seconds + result_seconds )
  829. return pos, result
  830. end,
  831. [DataTypes.NTEXTTYPE] = function( data, pos )
  832. local len, coldata
  833. -- The first len value is the size of the meta data block
  834. pos, len = bin.unpack( "<C", data, pos )
  835. if ( len == 0 ) then
  836. return pos, 'Null'
  837. end
  838. -- Skip over the text update time and date values, we don't need them
  839. -- We may come back add parsing for this information.
  840. pos = pos + len
  841. -- skip a label, should be 'dummyTS'
  842. pos = pos + 8
  843. -- extract the actual data
  844. pos, len = bin.unpack( "<I", data, pos )
  845. pos, coldata = bin.unpack( "A"..len, data, pos )
  846. return pos, Util.FromWideChar(coldata)
  847. end,
  848. [DataTypes.BITNTYPE] = function( data, pos )
  849. return ColumnData.Parse[DataTypes.SYBINTN](data, pos)
  850. end,
  851. [DataTypes.DECIMALNTYPE] = function( precision, scale, data, pos )
  852. local len, sign, format_string, coldata
  853. pos, len = bin.unpack("<C", data, pos)
  854. if ( len == 0 ) then
  855. return pos, 'Null'
  856. end
  857. pos, sign = bin.unpack("<C", data, pos)
  858. -- subtract 1 from data len to account for sign byte
  859. len = len - 1
  860. if ( len == 2 ) then
  861. pos, coldata = bin.unpack("<S", data, pos)
  862. elseif ( len == 4 ) then
  863. pos, coldata = bin.unpack("<I", data, pos)
  864. elseif ( len == 8 ) then
  865. pos, coldata = bin.unpack("<L", data, pos)
  866. else
  867. stdnse.print_debug("Unhandled length (%d) for DECIMALNTYPE", len)
  868. return pos + len, 'Unsupported Data'
  869. end
  870. if ( sign == 0 ) then
  871. coldata = coldata * -1
  872. end
  873. coldata = coldata * (10^-scale)
  874. -- format the return information to reduce truncation by lua
  875. format_string = string.format("%%.%if", scale)
  876. coldata = string.format(format_string,coldata)
  877. return pos, coldata
  878. end,
  879. [DataTypes.NUMERICNTYPE] = function( precision, scale, data, pos )
  880. return ColumnData.Parse[DataTypes.DECIMALNTYPE]( precision, scale, data, pos )
  881. end,
  882. [DataTypes.SYBDATETIME] = function( data, pos )
  883. local hi, lo, result_seconds, result
  884. local tds_epoch, system_epoch, tds_offset_seconds
  885. pos, hi, lo = bin.unpack("<iI", data, pos)
  886. tds_epoch = os.time( {year = 1900, month = 1, day = 1, hour = 00, min = 00, sec = 00, isdst = nil} )
  887. -- determine the offset between the tds_epoch and the local system epoch
  888. system_epoch = os.time( os.date("*t", 0))
  889. tds_offset_seconds = os.difftime(tds_epoch,system_epoch)
  890. result_seconds = (hi*24*60*60) + (lo/300)
  891. result = os.date("!%b %d, %Y %H:%M:%S", tds_offset_seconds + result_seconds )
  892. return pos, result
  893. end,
  894. [DataTypes.BITNTYPE] = function( data, pos )
  895. return ColumnData.Parse[DataTypes.SYBINTN](data, pos)
  896. end,
  897. [DataTypes.NTEXTTYPE] = function( data, pos )
  898. local len, coldata
  899. -- The first len value is the size of the meta data block
  900. pos, len = bin.unpack( "<C", data, pos )
  901. if ( len == 0 ) then
  902. return pos, 'Null'
  903. end
  904. -- Skip over the text update time and date values, we don't need them
  905. -- We may come back add parsing for this information.
  906. pos = pos + len
  907. -- skip a label, should be 'dummyTS'
  908. pos = pos + 8
  909. -- extract the actual data
  910. pos, len = bin.unpack( "<I", data, pos )
  911. pos, coldata = bin.unpack( "A"..len, data, pos )
  912. return pos, Util.FromWideChar(coldata)
  913. end,
  914. [DataTypes.FLTNTYPE] = function( data, pos )
  915. local len, coldata
  916. pos, len = bin.unpack("<C", data, pos)
  917. if ( len == 0 ) then
  918. return pos, 'Null'
  919. elseif ( len == 4 ) then
  920. pos, coldata = bin.unpack("f", data, pos)
  921. elseif ( len == 8 ) then
  922. pos, coldata = bin.unpack("<d", data, pos)
  923. end
  924. return pos, coldata
  925. end,
  926. [DataTypes.MONEYNTYPE] = function( data, pos )
  927. local len, value, coldata, hi, lo
  928. pos, len = bin.unpack("C", data, pos)
  929. if ( len == 0 ) then
  930. return pos, 'Null'
  931. elseif ( len == 4 ) then
  932. --type smallmoney
  933. pos, value = bin.unpack("<i", data, pos)
  934. elseif ( len == 8 ) then
  935. -- type money
  936. pos, hi,lo = bin.unpack("<II", data, pos)
  937. value = ( hi * 4294967296 ) + lo
  938. else
  939. return -1, ("Unhandled length (%d) for MONEYNTYPE"):format(len)
  940. end
  941. -- the datatype allows for 4 decimal places after the period to support various currency types.
  942. -- forcing to string to avoid truncation
  943. coldata = string.format("%.4f",value/10000)
  944. return pos, coldata
  945. end,
  946. [DataTypes.SYBDATETIMN] = function( data, pos )
  947. local len, coldata
  948. pos, len = bin.unpack( "<C", data, pos )
  949. if ( len == 0 ) then
  950. return pos, 'Null'
  951. elseif ( len == 4 ) then
  952. -- format is smalldatetime
  953. local days, mins
  954. pos, days, mins = bin.unpack("<SS", data, pos)
  955. local tds_epoch = os.time( {year = 1900, month = 1, day = 1, hour = 00, min = 00, sec = 00, isdst = nil} )
  956. -- determine the offset between the tds_epoch and the local system epoch
  957. local system_epoch = os.time( os.date("*t", 0))
  958. local tds_offset_seconds = os.difftime(tds_epoch,system_epoch)
  959. local result_seconds = (days*24*60*60) + (mins*60)
  960. coldata = os.date("!%b %d, %Y %H:%M:%S", tds_offset_seconds + result_seconds )
  961. return pos,coldata
  962. elseif ( len == 8 ) then
  963. -- format is datetime
  964. return ColumnData.Parse[DataTypes.SYBDATETIME](data, pos)
  965. else
  966. return -1, ("Unhandled length (%d) for SYBDATETIMN"):format(len)
  967. end
  968. end,
  969. [DataTypes.XSYBVARBINARY] = function( data, pos )
  970. local len, coldata
  971. pos, len = bin.unpack( "<S", data, pos )
  972. if ( len == 65535 ) then
  973. return pos, 'Null'
  974. else
  975. pos, coldata = bin.unpack( "A"..len, data, pos )
  976. return pos, "0x" .. select(2, bin.unpack("H"..coldata:len(), coldata ) )
  977. end
  978. return -1, "Error"
  979. end,
  980. [DataTypes.XSYBVARCHAR] = function( data, pos )
  981. local len, coldata
  982. pos, len = bin.unpack( "<S", data, pos )
  983. if ( len == 65535 ) then
  984. return pos, 'Null'
  985. end
  986. pos, coldata = bin.unpack( "A"..len, data, pos )
  987. return pos, coldata
  988. end,
  989. [DataTypes.BIGBINARYTYPE] = function( data, pos )
  990. return ColumnData.Parse[DataTypes.XSYBVARBINARY](data, pos)
  991. end,
  992. [DataTypes.BIGCHARTYPE] = function( data, pos )
  993. return ColumnData.Parse[DataTypes.XSYBVARCHAR](data, pos)
  994. end,
  995. [DataTypes.XSYBNVARCHAR] = function( data, pos )
  996. local len, coldata
  997. pos, len = bin.unpack( "<S", data, pos )
  998. if ( len == 65535 ) then
  999. return pos, 'Null'
  1000. end
  1001. pos, coldata = bin.unpack( "A"..len, data, pos )
  1002. return pos, Util.FromWideChar(coldata)
  1003. end,
  1004. [DataTypes.SQLNCHAR] = function( data, pos )
  1005. return ColumnData.Parse[DataTypes.XSYBNVARCHAR](data, pos)
  1006. end,
  1007. }
  1008. }
  1009. -- "static" Token parser class
  1010. Token =
  1011. {
  1012. Parse = {
  1013. --- Parse error message tokens
  1014. --
  1015. -- @param data string containing "raw" data
  1016. -- @param pos number containing offset into data
  1017. -- @return pos number containing new offset after parse
  1018. -- @return token table containing token specific fields
  1019. [TokenType.ErrorMessage] = function( data, pos )
  1020. local token = {}
  1021. local tmp
  1022. token.type = TokenType.ErrorMessage
  1023. pos, token.size, token.errno, token.state, token.severity, token.errlen = bin.unpack( "<SICCS", data, pos )
  1024. pos, tmp = bin.unpack("A" .. (token.errlen * 2), data, pos )
  1025. token.error = Util.FromWideChar(tmp)
  1026. pos, token.srvlen = bin.unpack("C", data, pos)
  1027. pos, tmp = bin.unpack("A" .. (token.srvlen * 2), data, pos )
  1028. token.server = Util.FromWideChar(tmp)
  1029. pos, token.proclen = bin.unpack("C", data, pos)
  1030. pos, tmp = bin.unpack("A" .. (token.proclen * 2), data, pos )
  1031. token.proc = Util.FromWideChar(tmp)
  1032. pos, token.lineno = bin.unpack("<S", data, pos)
  1033. return pos, token
  1034. end,
  1035. --- Parse environment change tokens
  1036. -- (This function is not implemented and simply moves the pos offset)
  1037. --
  1038. -- @param data string containing "raw" data
  1039. -- @param pos number containing offset into data
  1040. -- @return pos number containing new offset after parse
  1041. -- @return token table containing token specific fields
  1042. [TokenType.EnvironmentChange] = function( data, pos )
  1043. local token = {}
  1044. local tmp
  1045. token.type = TokenType.EnvironmentChange
  1046. pos, token.size = bin.unpack("<S", data, pos)
  1047. return pos + token.size, token
  1048. end,
  1049. --- Parse information message tokens
  1050. --
  1051. -- @param data string containing "raw" data
  1052. -- @param pos number containing offset into data
  1053. -- @return pos number containing new offset after parse
  1054. -- @return token table containing token specific fields
  1055. [TokenType.InformationMessage] = function( data, pos )
  1056. local pos, token = Token.Parse[TokenType.ErrorMessage]( data, pos )
  1057. token.type = TokenType.InformationMessage
  1058. return pos, token
  1059. end,
  1060. --- Parse login acknowledgment tokens
  1061. --
  1062. -- @param data string containing "raw" data
  1063. -- @param pos number containing offset into data
  1064. -- @return pos number containing new offset after parse
  1065. -- @return token table containing token specific fields
  1066. [TokenType.LoginAcknowledgement] = function( data, pos )
  1067. local token = {}
  1068. local _
  1069. token.type = TokenType.LoginAcknowledgement
  1070. pos, token.size, _, _, _, _, token.textlen = bin.unpack( "<SCCCSC", data, pos )
  1071. pos, token.text = bin.unpack("A" .. token.textlen * 2, data, pos)
  1072. pos, token.version = bin.unpack("<I", data, pos )
  1073. return pos, token
  1074. end,
  1075. --- Parse done tokens
  1076. --
  1077. -- @param data string containing "raw" data
  1078. -- @param pos number containing offset into data
  1079. -- @return pos number containing new offset after parse
  1080. -- @return token table containing token specific fields
  1081. [TokenType.Done] = function( data, pos )
  1082. local token = {}
  1083. token.type = TokenType.Done
  1084. pos, token.flags, token.operation, token.rowcount = bin.unpack( "<SSI", data, pos )
  1085. return pos, token
  1086. end,
  1087. --- Parses a DoneProc token recieved after executing a SP
  1088. --
  1089. -- @param data string containing "raw" data
  1090. -- @param pos number containing offset into data
  1091. -- @return pos number containing new offset after parse
  1092. -- @return token table containing token specific fields
  1093. [TokenType.DoneProc] = function( data, pos )
  1094. local token
  1095. pos, token = Token.Parse[TokenType.Done]( data, pos )
  1096. token.type = TokenType.DoneProc
  1097. return pos, token
  1098. end,
  1099. --- Parses a DoneInProc token recieved after executing a SP
  1100. --
  1101. -- @param data string containing "raw" data
  1102. -- @param pos number containing offset into data
  1103. -- @return pos number containing new offset after parse
  1104. -- @return token table containing token specific fields
  1105. [TokenType.DoneInProc] = function( data, pos )
  1106. local token
  1107. pos, token = Token.Parse[TokenType.Done]( data, pos )
  1108. token.type = TokenType.DoneInProc
  1109. return pos, token
  1110. end,
  1111. --- Parses a ReturnStatus token
  1112. --
  1113. -- @param data string containing "raw" data
  1114. -- @param pos number containing offset into data
  1115. -- @return pos number containing new offset after parse
  1116. -- @return token table containing token specific fields
  1117. [TokenType.ReturnStatus] = function( data, pos )
  1118. local token = {}
  1119. pos, token.value = bin.unpack("<i", data, pos)
  1120. token.type = TokenType.ReturnStatus
  1121. return pos, token
  1122. end,
  1123. --- Parses a OrderBy token
  1124. --
  1125. -- @param data string containing "raw" data
  1126. -- @param pos number containing offset into data
  1127. -- @return pos number containing new offset after parse
  1128. -- @return token table containing token specific fields
  1129. [TokenType.OrderBy] = function( data, pos )
  1130. local token = {}
  1131. pos, token.size = bin.unpack("<S", data, pos)
  1132. token.type = TokenType.OrderBy
  1133. return pos + token.size, token
  1134. end,
  1135. --- Parse TDS result tokens
  1136. --
  1137. -- @param data string containing "raw" data
  1138. -- @param pos number containing offset into data
  1139. -- @return pos number containing new offset after parse
  1140. -- @return token table containing token specific fields
  1141. [TokenType.TDS7Results] = function( data, pos )
  1142. local token = {}
  1143. local _
  1144. token.type = TokenType.TDS7Results
  1145. pos, token.count = bin.unpack( "<S", data, pos )
  1146. token.colinfo = {}
  1147. for i=1, token.count do
  1148. local colinfo = {}
  1149. local usertype, flags, ttype
  1150. pos, usertype, flags, ttype = bin.unpack("<SSC", data, pos )
  1151. if ( not(ColumnInfo.Parse[ttype]) ) then
  1152. return -1, ("Unhandled data type: 0x%X"):format(ttype)
  1153. end
  1154. pos, colinfo = ColumnInfo.Parse[ttype]( data, pos )
  1155. colinfo.usertype = usertype
  1156. colinfo.flags = flags
  1157. colinfo.type = ttype
  1158. table.insert( token.colinfo, colinfo )
  1159. end
  1160. return pos, token
  1161. end,
  1162. [TokenType.NTLMSSP_CHALLENGE] = function(data, pos)
  1163. local pos, len, ntlmssp, msgtype = bin.unpack("<SA8I", data, pos)
  1164. local NTLMSSP_CHALLENGE = 2
  1165. if ( ntlmssp ~= "NTLMSSP\0" or msgtype ~= NTLMSSP_CHALLENGE ) then
  1166. return -1, "Failed to process NTLMSSP Challenge"
  1167. end
  1168. local ntlm_challenge = data:sub( 28, 35 )
  1169. pos = pos + len - 13
  1170. return pos, ntlm_challenge
  1171. end,
  1172. },
  1173. --- Parses the first token at positions pos
  1174. --
  1175. -- @param data string containing "raw" data
  1176. -- @param pos number containing offset into data
  1177. -- @return pos number containing new offset after parse or -1 on error
  1178. -- @return token table containing token specific fields or error message on error
  1179. ParseToken = function( data, pos )
  1180. local ttype
  1181. pos, ttype = bin.unpack("C", data, pos)
  1182. if ( not(Token.Parse[ttype]) ) then
  1183. stdnse.print_debug( 1, "%s: No parser for token type 0x%X", "MSSQL", ttype )
  1184. return -1, ("No parser for token type: 0x%X"):format( ttype )
  1185. end
  1186. return Token.Parse[ttype](data, pos)
  1187. end,
  1188. }
  1189. --- QueryPacket class
  1190. QueryPacket =
  1191. {
  1192. new = function(self,o)
  1193. o = o or {}
  1194. setmetatable(o, self)
  1195. self.__index = self
  1196. return o
  1197. end,
  1198. SetQuery = function( self, query )
  1199. self.query = query
  1200. end,
  1201. --- Returns the query packet as string
  1202. --
  1203. -- @return string containing the authentication packet
  1204. ToString = function( self )
  1205. return PacketType.Query, Util.ToWideChar( self.query )
  1206. end,
  1207. }
  1208. --- PreLoginPacket class
  1209. PreLoginPacket =
  1210. {
  1211. -- TDS pre-login option types
  1212. OPTION_TYPE = {
  1213. Version = 0x00,
  1214. Encryption = 0x01,
  1215. InstOpt = 0x02,
  1216. ThreadId = 0x03,
  1217. MARS = 0x04,
  1218. Terminator = 0xFF,
  1219. },
  1220. versionInfo = nil,
  1221. _requestEncryption = 0,
  1222. _instanceName = "",
  1223. _threadId = 0, -- Dummy value; will be filled in later
  1224. _requestMars = nil,
  1225. new = function(self,o)
  1226. o = o or {}
  1227. setmetatable(o, self)
  1228. self.__index = self
  1229. return o
  1230. end,
  1231. --- Sets the client version (default = 9.00.1399.00)
  1232. --
  1233. -- @param versionInfo A SqlServerVersionInfo object with the client version information
  1234. SetVersion = function(self, versionInfo)
  1235. self._versionInfo = versionInfo
  1236. end,
  1237. --- Sets whether to request encryption (default = false)
  1238. --
  1239. -- @param requestEncryption A boolean indicating whether encryption will be requested
  1240. SetRequestEncryption = function(self, requestEncryption)
  1241. if requestEncryption then
  1242. self._requestEncryption = 1
  1243. else
  1244. self._requestEncryption = 0
  1245. end
  1246. end,
  1247. --- Sets whether to request MARS support (default = undefined)
  1248. --
  1249. -- @param requestMars A boolean indicating whether MARS support will be requested
  1250. SetRequestMars = function(self, requestMars)
  1251. if requestMars then
  1252. self._requestMars = 1
  1253. else
  1254. self._requestMars = 0
  1255. end
  1256. end,
  1257. --- Sets the instance name of the target
  1258. --
  1259. -- @param instanceName A string containing the name of the instance
  1260. SetInstanceName = function(self, instanceName)
  1261. self._instanceName = instanceName or ""
  1262. end,
  1263. --- Returns the pre-login packet as a byte string
  1264. --
  1265. -- @return byte string containing the pre-login packet
  1266. ToBytes = function(self)
  1267. -- Lengths for the values of TDS pre-login option fields
  1268. local OPTION_LENGTH_CLIENT = {
  1269. [PreLoginPacket.OPTION_TYPE.Version] = 6,
  1270. [PreLoginPacket.OPTION_TYPE.Encryption] = 1,
  1271. [PreLoginPacket.OPTION_TYPE.InstOpt] = -1,
  1272. [PreLoginPacket.OPTION_TYPE.ThreadId] = 4,
  1273. [PreLoginPacket.OPTION_TYPE.MARS] = 1,
  1274. [PreLoginPacket.OPTION_TYPE.Terminator] = 0,
  1275. }
  1276. local data, optionLength, optionType = "", 0, 0
  1277. local offset = 1 -- Terminator
  1278. offset = offset + 5 -- Version
  1279. offset = offset + 5 -- Encryption
  1280. offset = offset + 5 -- InstOpt
  1281. offset = offset + 5 -- ThreadId
  1282. if self._requestMars then offset = offset + 3 end -- MARS
  1283. if not self.versionInfo then
  1284. self.versionInfo = SqlServerVersionInfo:new()
  1285. self.versionInfo:SetVersionNumber( "9.00.1399.00" )
  1286. end
  1287. optionType = PreLoginPacket.OPTION_TYPE.Version
  1288. optionLength = OPTION_LENGTH_CLIENT[ optionType ]
  1289. data = data .. bin.pack( ">CSS", optionType, offset, optionLength )
  1290. offset = offset + optionLength
  1291. optionType = PreLoginPacket.OPTION_TYPE.Encryption
  1292. optionLength = OPTION_LENGTH_CLIENT[ optionType ]
  1293. data = data .. bin.pack( ">CSS", optionType, offset, optionLength )
  1294. offset = offset + optionLength
  1295. optionType = PreLoginPacket.OPTION_TYPE.InstOpt
  1296. optionLength = #self._instanceName + 1 --(string length + null-terminator)
  1297. data = data .. bin.pack( ">CSS", optionType, offset, optionLength )
  1298. offset = offset + optionLength
  1299. optionType = PreLoginPacket.OPTION_TYPE.ThreadId
  1300. optionLength = OPTION_LENGTH_CLIENT[ optionType ]
  1301. data = data .. bin.pack( ">CSS", optionType, offset, optionLength )
  1302. offset = offset + optionLength
  1303. if self.requestMars then
  1304. optionType = PreLoginPacket.OPTION_TYPE.MARS
  1305. optionLength = OPTION_LENGTH_CLIENT[ optionType ]
  1306. data = data .. bin.pack( ">CSS", optionType, offset, optionLength )
  1307. offset = offset + optionLength
  1308. end
  1309. data = data .. bin.pack( "C", PreLoginPacket.OPTION_TYPE.Terminator )
  1310. -- Now that the pre-login headers are done, write the data
  1311. data = data .. bin.pack( ">CCSS", self.versionInfo.major, self.versionInfo.minor,
  1312. self.versionInfo.build, self.versionInfo.subBuild )
  1313. data = data .. bin.pack( "C", self._requestEncryption )
  1314. data = data .. bin.pack( "z", self._instanceName )
  1315. data = data .. bin.pack( "<I", self._threadId )
  1316. if self.requestMars then
  1317. data = data .. bin.pack( "C", self._requestMars )
  1318. end
  1319. return PacketType.PreLogin, data
  1320. end,
  1321. --- Reads a byte-string and creates a PreLoginPacket object from it. This is
  1322. -- intended to handle the server's response to a pre-login request.
  1323. FromBytes = function( bytes )
  1324. local OPTION_LENGTH_SERVER = {
  1325. [PreLoginPacket.OPTION_TYPE.Version] = 6,
  1326. [PreLoginPacket.OPTION_TYPE.Encryption] = 1,
  1327. [PreLoginPacket.OPTION_TYPE.InstOpt] = -1,
  1328. [PreLoginPacket.OPTION_TYPE.ThreadId] = 0, -- According to the TDS spec, this value should be empty from the server
  1329. [PreLoginPacket.OPTION_TYPE.MARS] = 1,
  1330. [PreLoginPacket.OPTION_TYPE.Terminator] = 0,
  1331. }
  1332. local status, pos = false, 1
  1333. local preLoginPacket = PreLoginPacket:new()
  1334. while true do
  1335. local optionType, optionPos, optionLength, optionData, expectedOptionLength, _
  1336. pos, optionType = bin.unpack("C", bytes, pos)
  1337. if ( optionType == PreLoginPacket.OPTION_TYPE.Terminator ) then
  1338. status = true
  1339. break
  1340. end
  1341. expectedOptionLength = OPTION_LENGTH_SERVER[ optionType ]
  1342. if ( not expectedOptionLength ) then
  1343. stdnse.print_debug( 2, "%s: Unrecognized pre-login option type: %s", "MSSQL", optionType )
  1344. expectedOptionLength = -1
  1345. end
  1346. pos, optionPos, optionLength = bin.unpack(">SS", bytes, pos)
  1347. if not (optionPos and optionLength) then
  1348. stdnse.print_debug( 2, "%s: Could not unpack optionPos and optionLength.", "MSSQL" )
  1349. return false, "Invalid pre-login response"
  1350. end
  1351. optionPos = optionPos + 1 -- convert from 0-based index to 1-based index
  1352. if ( (optionPos + optionLength) > (#bytes + 1) ) then
  1353. stdnse.print_debug( 2, "%s: Pre-login response: pos+len for option type %s is beyond end of data.", "MSSQL", optionType )
  1354. stdnse.print_debug( 2, "%s: (optionPos: %s) (optionLength: %s)", "MSSQL", optionPos, optionLength )
  1355. return false, "Invalid pre-login response"
  1356. end
  1357. if ( optionLength ~= expectedOptionLength and expectedOptionLength ~= -1 ) then
  1358. stdnse.print_debug( 2, "%s: Option data is incorrect size in pre-login response. ", "MSSQL" )
  1359. stdnse.print_debug( 2, "%s: (optionType: %s) (optionLength: %s)", "MSSQL", optionType, optionLength )
  1360. return false, "Invalid pre-login response"
  1361. end
  1362. optionData = bytes:sub( optionPos, optionPos + optionLength - 1 )
  1363. if #optionData ~= optionLength then
  1364. stdnse.print_debug( 2, "%s: Could not read sufficient bytes from version data.", "MSSQL" )
  1365. return false, "Invalid pre-login response"
  1366. end
  1367. if ( optionType == PreLoginPacket.OPTION_TYPE.Version ) then
  1368. local major, minor, build, subBuild, version
  1369. major = string.byte( optionData:sub( 1, 1 ) )
  1370. minor = string.byte( optionData:sub( 2, 2 ) )
  1371. build = (string.byte( optionData:sub( 3, 3 ) ) * 256) + string.byte( optionData:sub( 4, 4 ) )
  1372. subBuild = (string.byte( optionData:sub( 5, 5 ) ) * 256) + string.byte( optionData:sub( 6, 6 ) )
  1373. version = SqlServerVersionInfo:new()
  1374. version:SetVersion( major, minor, build, subBuild, "SSNetLib" )
  1375. preLoginPacket.versionInfo = version
  1376. elseif ( optionType == PreLoginPacket.OPTION_TYPE.Encryption ) then
  1377. preLoginPacket:SetRequestEncryption( bin.unpack( "C", optionData ) )
  1378. elseif ( optionType == PreLoginPacket.OPTION_TYPE.InstOpt ) then
  1379. preLoginPacket:SetInstanceName( bin.unpack( "z", optionData ) )
  1380. elseif ( optionType == PreLoginPacket.OPTION_TYPE.ThreadId ) then
  1381. -- Do nothing. According to the TDS spec, this option is empty when sent from the server
  1382. elseif ( optionType == PreLoginPacket.OPTION_TYPE.MARS ) then
  1383. preLoginPacket:SetRequestMars( bin.unpack( "C", optionData ) )
  1384. end
  1385. end
  1386. return status, preLoginPacket
  1387. end,
  1388. }
  1389. --- LoginPacket class
  1390. LoginPacket =
  1391. {
  1392. -- options_1 possible values
  1393. -- 0x80 enable warning messages if SET LANGUAGE issued
  1394. -- 0x40 change to initial database must succeed
  1395. -- 0x20 enable warning messages if USE <database> issued
  1396. -- 0x10 enable BCP
  1397. -- options_2 possible values
  1398. -- 0x80 enable domain login security
  1399. -- 0x40 "USER_SERVER - reserved"
  1400. -- 0x20 user type is "DQ login"
  1401. -- 0x10 user type is "replication login"
  1402. -- 0x08 "fCacheConnect"
  1403. -- 0x04 "fTranBoundary"
  1404. -- 0x02 client is an ODBC driver
  1405. -- 0x01 change to initial language must succeed
  1406. length = 0,
  1407. version = 0x71000001, -- Version 7.1
  1408. size = 0,
  1409. cli_version = 7, -- From jTDS JDBC driver
  1410. cli_pid = 0, -- Dummy value
  1411. conn_id = 0,
  1412. options_1 = 0xa0,
  1413. options_2 = 0x03,
  1414. sqltype_flag = 0,
  1415. reserved_flag= 0,
  1416. time_zone = 0,
  1417. collation = 0,
  1418. -- Strings
  1419. client = "Nmap",
  1420. username = nil,
  1421. password = nil,
  1422. app = "Nmap NSE",
  1423. server = nil,
  1424. library = "mssql.lua",
  1425. locale = "",
  1426. database = "master", --nil,
  1427. MAC = string.char(0x00,0x00,0x00,0x00,0x00,0x00), -- should contain client MAC, jTDS uses all zeroes
  1428. new = function(self,o)
  1429. o = o or {}
  1430. setmetatable(o, self)
  1431. self.__index = self
  1432. return o
  1433. end,
  1434. --- Sets the username used for authentication
  1435. --
  1436. -- @param username string containing the username to user for authentication
  1437. SetUsername = function(self, username)
  1438. self.username = username
  1439. end,
  1440. --- Sets the password used for authentication
  1441. --
  1442. -- @param password string containing the password to user for authentication
  1443. SetPassword = function(self, password)
  1444. self.password = password
  1445. end,
  1446. --- Sets the database used in authentication
  1447. --
  1448. -- @param database string containing the database name
  1449. SetDatabase = function(self, database)
  1450. self.database = database
  1451. end,
  1452. --- Sets the server's name used in authentication
  1453. --
  1454. -- @param server string containing the name or ip of the server
  1455. SetServer = function(self, server)
  1456. self.server = server
  1457. end,
  1458. SetDomain = function(self, domain)
  1459. self.domain = domain
  1460. end,
  1461. --- Returns the authentication packet as string
  1462. --
  1463. -- @return string containing the authentication packet
  1464. ToString = function(self)
  1465. local data
  1466. local offset = 86
  1467. local ntlmAuth = not(not(self.domain))
  1468. local authLen = 0
  1469. self.cli_pid = math.random(100000)
  1470. self.length = offset + 2 * ( self.client:len() + self.app:len() + self.server:len() + self.library:len() + self.database:len() )
  1471. if ( ntlmAuth ) then
  1472. authLen = 32 + #self.domain
  1473. self.length = self.length + authLen
  1474. self.options_2 = self.options_2 + 0x80
  1475. else
  1476. self.length = self.length + 2 * (self.username:len() + self.password:len())
  1477. end
  1478. data = bin.pack("<IIIIII", self.length, self.version, self.size, self.cli_version, self.cli_pid, self.conn_id )
  1479. data = data .. bin.pack("CCCC", self.options_1, self.options_2, self.sqltype_flag, self.reserved_flag )
  1480. data = data .. bin.pack("<II", self.time_zone, self.collation )
  1481. -- offsets begin
  1482. data = data .. bin.pack("<SS", offset, self.client:len() )
  1483. offset = offset + self.client:len() * 2
  1484. if ( not(ntlmAuth) ) then
  1485. data = data .. bin.pack("<SS", offset, self.username:len() )
  1486. offset = offset + self.username:len() * 2
  1487. data = data .. bin.pack("<SS", offset, self.password:len() )
  1488. offset = offset + self.password:len() * 2
  1489. else
  1490. data = data .. bin.pack("<SS", offset, 0 )
  1491. data = data .. bin.pack("<SS", offset, 0 )
  1492. end
  1493. data = data .. bin.pack("<SS", offset, self.app:len() )
  1494. offset = offset + self.app:len() * 2
  1495. data = data .. bin.pack("<SS", offset, self.server:len() )
  1496. offset = offset + self.server:len() * 2
  1497. -- Offset to unused placeholder (reserved for future use in TDS spec)
  1498. data = data .. bin.pack("<SS", 0, 0 )
  1499. data = data .. bin.pack("<SS", offset, self.library:len() )
  1500. offset = offset + self.library:len() * 2
  1501. data = data .. bin.pack("<SS", offset, self.locale:len() )
  1502. offset = offset + self.locale:len() * 2
  1503. data = data .. bin.pack("<SS", offset, self.database:len() )
  1504. offset = offset + self.database:len() * 2
  1505. -- client MAC address, hardcoded to 00:00:00:00:00:00
  1506. data = data .. bin.pack("A", self.MAC)
  1507. -- offset to auth info
  1508. data = data .. bin.pack("<S", offset)
  1509. -- lenght of nt auth (should be 0 for sql auth)
  1510. data = data .. bin.pack("<S", authLen)
  1511. -- next position (same as total packet length)
  1512. data = data .. bin.pack("<S", self.length)
  1513. -- zero pad
  1514. data = data .. bin.pack("<S", 0)
  1515. -- Auth info wide strings
  1516. data = data .. bin.pack("A", Util.ToWideChar(self.client) )
  1517. if ( not(ntlmAuth) ) then
  1518. data = data .. bin.pack("A", Util.ToWideChar(self.username) )
  1519. data = data .. bin.pack("A", Auth.TDS7CryptPass(self.password) )
  1520. end
  1521. data = data .. bin.pack("A", Util.ToWideChar(self.app) )
  1522. data = data .. bin.pack("A", Util.ToWideChar(self.server) )
  1523. data = data .. bin.pack("A", Util.ToWideChar(self.library) )
  1524. data = data .. bin.pack("A", Util.ToWideChar(self.locale) )
  1525. data = data .. bin.pack("A", Util.ToWideChar(self.database) )
  1526. if ( ntlmAuth ) then
  1527. local NTLMSSP_NEGOTIATE = 1
  1528. local flags = 0x0000b201
  1529. local workstation = ""
  1530. data = data .. "NTLMSSP\0"
  1531. data = data .. bin.pack("<II", NTLMSSP_NEGOTIATE, flags)
  1532. data = data .. bin.pack("<SSI", #self.domain, #self.domain, 32)
  1533. data = data .. bin.pack("<SSI", #workstation, #workstation, 32)
  1534. data = data .. bin.pack("A", self.domain:upper())
  1535. end
  1536. return PacketType.Login, data
  1537. end,
  1538. }
  1539. NTAuthenticationPacket = {
  1540. new = function(self, username, password, domain, nonce)
  1541. local o = {}
  1542. setmetatable(o, self)
  1543. o.username = username
  1544. o.domain = domain
  1545. o.nonce = nonce
  1546. o.password = password
  1547. self.__index = self
  1548. return o
  1549. end,
  1550. ToString = function(self)
  1551. local ntlmssp = "NTLMSSP\0"
  1552. local NTLMSSP_AUTH = 3
  1553. local domain = Util.ToWideChar(self.domain:upper())
  1554. local user = Util.ToWideChar(self.username)
  1555. local hostname, sessionkey = "", ""
  1556. local flags = 0x00008201
  1557. local ntlm_response = Auth.NtlmResponse(self.password, self.nonce)
  1558. local lm_response = Auth.LmResponse(self.password, self.nonce)
  1559. local domain_offset = 64
  1560. local username_offset = domain_offset + #domain
  1561. local lm_response_offset = username_offset + #user
  1562. local ntlm_response_offset = lm_response_offset + #lm_response
  1563. local hostname_offset = ntlm_response_offset + #ntlm_response
  1564. local sessionkey_offset = hostname_offset + #hostname
  1565. local data = bin.pack("<AISSI", ntlmssp, NTLMSSP_AUTH, #lm_response, #lm_response, lm_response_offset)
  1566. data = data .. bin.pack("<SSI", #ntlm_response, #ntlm_response, ntlm_response_offset)
  1567. data = data .. bin.pack("<SSI", #domain, #domain, domain_offset)
  1568. data = data .. bin.pack("<SSI", #user, #user, username_offset)
  1569. data = data .. bin.pack("<SSI", #hostname, #hostname, hostname_offset)
  1570. data = data .. bin.pack("<SSI", #sessionkey, #sessionkey, sessionkey_offset)
  1571. data = data .. bin.pack("<I", flags)
  1572. data = data .. bin.pack("A", domain)
  1573. data = data .. bin.pack("A", user )
  1574. data = data .. lm_response .. ntlm_response
  1575. return PacketType.NTAuthentication, data
  1576. end,
  1577. }
  1578. -- Handles communication with SQL Server
  1579. TDSStream = {
  1580. -- Status flag constants
  1581. MESSAGE_STATUS_FLAGS = {
  1582. Normal = 0x0,
  1583. EndOfMessage = 0x1,
  1584. IgnoreThisEvent = 0x2,
  1585. ResetConnection = 0x4,
  1586. ResetConnectionSkipTran = 0x8,
  1587. },
  1588. _packetId = 0,
  1589. _pipe = nil,
  1590. _socket = nil,
  1591. _name = nil,
  1592. new = function(self,o)
  1593. o = o or {}
  1594. setmetatable(o, self)
  1595. self.__index = self
  1596. return o
  1597. end,
  1598. --- Establishes a connection to the SQL server.
  1599. --
  1600. -- @param self A mssql.Helper object
  1601. -- @param instanceInfo A SqlServerInstanceInfo object for the instance to
  1602. -- connect to.
  1603. -- @param connectionPreference (Optional) A list containing one or both of
  1604. -- the strings "TCP" and "Named Pipes", indicating which transport
  1605. -- methods to try and in what order.
  1606. -- @param smbOverrides (Optional) An overrides table for calls to the <code>smb</code>
  1607. -- library (for use with named pipes).
  1608. ConnectEx = function( self, instanceInfo, connectionPreference, smbOverrides )
  1609. if ( self._socket ) then return false, "Already connected via TCP" end
  1610. if ( self._pipe ) then return false, "Already connected via named pipes" end
  1611. connectionPreference = connectionPreference or stdnse.get_script_args('mssql.protocol') or { "TCP", "Named Pipes" }
  1612. if ( connectionPreference and 'string' == type(connectionPreference) ) then
  1613. connectionPreference = { connectionPreference }
  1614. end
  1615. local status, result, connectionType, errorMessage
  1616. stdnse.print_debug( 3, "%s: Connection preferences for %s: %s",
  1617. "MSSQL", instanceInfo:GetName(), stdnse.strjoin( ", ", connectionPreference ) )
  1618. for _, connectionType in ipairs( connectionPreference ) do
  1619. if connectionType == "TCP" then
  1620. if not ( instanceInfo.port ) then
  1621. stdnse.print_debug( 3, "%s: Cannot connect to %s via TCP because port table is not set.",
  1622. "MSSQL", instanceInfo:GetName() )
  1623. result = "No TCP port for this instance"
  1624. else
  1625. status, result = self:Connect( instanceInfo.host, instanceInfo.port )
  1626. if status then return true end
  1627. end
  1628. elseif connectionType == "Named Pipes" or connectionType == "NP" then
  1629. if not ( instanceInfo.pipeName ) then
  1630. stdnse.print_debug( 3, "%s: Cannot connect to %s via named pipes because pipe name is not set.",
  1631. "MSSQL", instanceInfo:GetName() )
  1632. result = "No named pipe for this instance"
  1633. else
  1634. status, result = self:ConnectToNamedPipe( instanceInfo.host, instanceInfo.pipeName, smbOverrides )
  1635. if status then return true end
  1636. end
  1637. else
  1638. stdnse.print_debug( 1, "%s: Unknown connection preference: %s", "MSSQL", connectionType )
  1639. return false, ("ERROR: Unknown connection preference: %s"):format(connectionType)
  1640. end
  1641. -- Handle any error messages
  1642. if not status then
  1643. if errorMessage then
  1644. errorMessage = string.format( "%s, %s: %s", errorMessage, connectionType, result or "nil" )
  1645. else
  1646. errorMessage = string.format( "%s: %s", connectionType, result or "nil" )
  1647. end
  1648. end
  1649. end
  1650. if not errorMessage then
  1651. errorMessage = string.format( "%s: None of the preferred connection types are available for %s\\%s",
  1652. "MSSQL", instanceInfo:GetName() )
  1653. end
  1654. return false, errorMessage
  1655. end,
  1656. --- Establishes a connection to the SQL server
  1657. --
  1658. -- @param host A host table for the target host
  1659. -- @param pipePath The path to the named pipe of the target SQL Server
  1660. -- (e.g. "\MSSQL$SQLEXPRESS\sql\query"). If nil, "\sql\query\" is used.
  1661. -- @param smbOverrides (Optional) An overrides table for calls to the <code>smb</code>
  1662. -- library (for use with named pipes).
  1663. -- @return status: true on success, false on failure
  1664. -- @return error_message: an error message, or nil
  1665. ConnectToNamedPipe = function( self, host, pipePath, overrides )
  1666. if ( self._socket ) then return false, "Already connected via TCP" end
  1667. if ( SCANNED_PORTS_ONLY and smb.get_port( host ) == nil ) then
  1668. stdnse.print_debug( 2, "%s: Connection disallowed: scanned-ports-only is set and no SMB port is available", "MSSQL" )
  1669. return false, "Connection disallowed: scanned-ports-only"
  1670. end
  1671. pipePath = pipePath or "\\sql\\query"
  1672. self._pipe = namedpipes.named_pipe:new()
  1673. local status, result = self._pipe:connect( host, pipePath, overrides )
  1674. if ( status ) then
  1675. self._name = self._pipe.pipe
  1676. else
  1677. self._pipe = nil
  1678. end
  1679. return status, result
  1680. end,
  1681. --- Establishes a connection to the SQL server
  1682. --
  1683. -- @param host table containing host information
  1684. -- @param port table containing port information
  1685. -- @return status true on success, false on failure
  1686. -- @return result containing error message on failure
  1687. Connect = function( self, host, port )
  1688. if ( self._pipe ) then return false, "Already connected via named pipes" end
  1689. if ( SCANNED_PORTS_ONLY and nmap.get_port_state( host, port ) == nil ) then
  1690. stdnse.print_debug( 2, "%s: Connection disallowed: scanned-ports-only is set and port %d was not scanned", "MSSQL", port.number )
  1691. return false, "Connection disallowed: scanned-ports-only"
  1692. end
  1693. local status, result, lport, _
  1694. self._socket = nmap.new_socket()
  1695. -- Set the timeout to something realistic for connects
  1696. self._socket:set_timeout( 5000 )
  1697. status, result = self._socket:connect(host, port)
  1698. if ( status ) then
  1699. -- Sometimes a Query can take a long time to respond, so we set
  1700. -- the timeout to 30 seconds. This shouldn't be a problem as the
  1701. -- library attempt to decode the protocol and avoid reading past
  1702. -- the end of the input buffer. So the only time the timeout is
  1703. -- triggered is when waiting for a response to a query.
  1704. self._socket:set_timeout( MSSQL_TIMEOUT * 1000 )
  1705. status, _, lport, _, _ = self._socket:get_info()
  1706. end
  1707. if ( not(status) ) then
  1708. self._socket = nil
  1709. stdnse.print_debug( 2, "%s: Socket connection failed on %s:%s", "MSSQL", host.ip, port.number )
  1710. return false, "Socket connection failed"
  1711. end
  1712. self._name = string.format( "%s:%s", host.ip, port.number )
  1713. return status, result
  1714. end,
  1715. --- Disconnects from the SQL Server
  1716. --
  1717. -- @return status true on success, false on failure
  1718. -- @return result containing error message on failure
  1719. Disconnect = function( self )
  1720. if ( self._socket ) then
  1721. local status, result = self._socket:close()
  1722. self._socket = nil
  1723. return status, result
  1724. elseif ( self._pipe ) then
  1725. local status, result = self._pipe:disconnect()
  1726. self._pipe = nil
  1727. return status, result
  1728. else
  1729. return false, "Not connected"
  1730. end
  1731. end,
  1732. --- Sets the timeout for communication over the socket
  1733. --
  1734. -- @param timeout number containing the new socket timeout in ms
  1735. SetTimeout = function( self, timeout )
  1736. if ( self._socket ) then
  1737. self._socket:set_timeout(timeout)
  1738. else
  1739. return false, "Not connected"
  1740. end
  1741. end,
  1742. --- Gets the name of the name pipe, or nil
  1743. GetNamedPipeName = function( self )
  1744. if ( self._pipe ) then
  1745. return self._pipe.name
  1746. else
  1747. return nil
  1748. end
  1749. end,
  1750. --- Send a TDS request to the server
  1751. --
  1752. -- @param packetType A <code>PacketType</code>, indicating the type of TDS
  1753. -- packet being sent.
  1754. -- @param packetData A string containing the raw data to send to the server
  1755. -- @return status true on success, false on failure
  1756. -- @return result containing error message on failure
  1757. Send = function( self, packetType, packetData )
  1758. local packetLength = packetData:len() + 8 -- +8 for TDS header
  1759. local messageStatus, spid, window = 1, 0, 0
  1760. if ( packetType ~= PacketType.NTAuthentication ) then self._packetId = self._packetId + 1 end
  1761. local assembledPacket = bin.pack(">CCSSCCA", packetType, messageStatus, packetLength, spid, self._packetId, window, packetData )
  1762. if ( self._socket ) then
  1763. return self._socket:send( assembledPacket )
  1764. elseif ( self._pipe ) then
  1765. return self._pipe:send( assembledPacket )
  1766. else
  1767. return false, "Not connected"
  1768. end
  1769. end,
  1770. --- Recieves responses from SQL Server
  1771. -- The function continues to read and assemble a response until the server
  1772. -- responds with the last response flag set
  1773. --
  1774. -- @return status true on success, false on failure
  1775. -- @return result containing raw data contents or error message on failure
  1776. -- @return errorDetail nil, or additional information about an error. In
  1777. -- the case of named pipes, this will be an SMB error name (e.g. NT_STATUS_PIPE_DISCONNECTED)
  1778. Receive = function( self )
  1779. local status, result, errorDetail
  1780. local combinedData, readBuffer = "", "" -- the buffer is solely for the benefit of TCP connections
  1781. local tdsPacketAvailable = true
  1782. if not ( self._socket or self._pipe ) then
  1783. return false, "Not connected"
  1784. end
  1785. -- Large messages (e.g. result sets) can be split across multiple TDS
  1786. -- packets from the server (which could themselves each be split across
  1787. -- multiple TCP packets or SMB messages).
  1788. while ( tdsPacketAvailable ) do
  1789. local packetType, messageStatus, packetLength, spid, window
  1790. local pos = 1
  1791. if ( self._socket ) then
  1792. -- If there is existing data in the readBuffer, see if there's
  1793. -- enough to read the TDS headers for the next packet. If not,
  1794. -- do another read so we have something to work with.
  1795. if ( readBuffer:len() < 8 ) then
  1796. status, result = self._socket:receive_bytes(8 - readBuffer:len())
  1797. readBuffer = readBuffer .. result
  1798. end
  1799. elseif ( self._pipe ) then
  1800. -- The named pipe takes care of all of its reassembly. We don't
  1801. -- have to mess with buffers and repeatedly reading until we get
  1802. -- the whole packet. We'll still write to readBuffer, though, so
  1803. -- that the common logic can be reused.
  1804. status, result, errorDetail = self._pipe:receive()
  1805. readBuffer = result
  1806. end
  1807. if not ( status and readBuffer ) then return false, result, errorDetail end
  1808. -- TDS packet validity check: packet at least as long as the TDS header
  1809. if ( readBuffer:len() < 8 ) then
  1810. stdnse.print_debug( 2, "%s: Receiving (%s): packet is invalid length", "MSSQL", self._name )
  1811. return false, "Server returned invalid packet"
  1812. end
  1813. -- read in the TDS headers
  1814. pos, packetType, messageStatus, packetLength = bin.unpack(">CCS", readBuffer, pos )
  1815. pos, spid, self._packetId, window = bin.unpack(">SCC", readBuffer, pos )
  1816. -- TDS packet validity check: packet type is Response (0x4)
  1817. if ( packetType ~= PacketType.Response ) then
  1818. stdnse.print_debug( 2, "%s: Receiving (%s): Expected type 0x4 (response), but received type 0x%x",
  1819. "MSSQL", self._name, packetType )
  1820. return false, "Server returned invalid packet"
  1821. end
  1822. if ( self._socket ) then
  1823. -- If we didn't previously read in enough data to complete this
  1824. -- TDS packet, let's do so.
  1825. while ( packetLength - readBuffer:len() > 0 ) do
  1826. status, result = self._socket:receive()
  1827. if not ( status and result ) then return false, result end
  1828. readBuffer = readBuffer .. result
  1829. end
  1830. end
  1831. -- We've read in an apparently valid TDS packet
  1832. local thisPacketData = readBuffer:sub( pos, packetLength )
  1833. -- Append its data to that of any previous TDS packets
  1834. combinedData = combinedData .. thisPacketData
  1835. if ( self._socket ) then
  1836. -- If we read in data beyond the end of this TDS packet, save it
  1837. -- so that we can use it in the next loop.
  1838. readBuffer = readBuffer:sub( packetLength + 1 )
  1839. end
  1840. -- TDS packet validity check: packet length matches length from header
  1841. if ( packetLength ~= (thisPacketData:len() + 8) ) then
  1842. stdnse.print_debug( 2, "%s: Receiving (%s): Header reports length %d, actual length is %d",
  1843. "MSSQL", self._name, packetLength, thisPacketData:len() )
  1844. return false, "Server returned invalid packet"
  1845. end
  1846. -- Check the status flags in the TDS packet to see if the message is
  1847. -- continued in another TDS packet.
  1848. tdsPacketAvailable = (bit.band( messageStatus, TDSStream.MESSAGE_STATUS_FLAGS.EndOfMessage) ~=
  1849. TDSStream.MESSAGE_STATUS_FLAGS.EndOfMessage)
  1850. end
  1851. -- return only the data section ie. without the headers
  1852. return status, combinedData
  1853. end,
  1854. }
  1855. --- Helper class
  1856. Helper =
  1857. {
  1858. new = function(self,o)
  1859. o = o or {}
  1860. setmetatable(o, self)
  1861. self.__index = self
  1862. return o
  1863. end,
  1864. --- Establishes a connection to the SQL server
  1865. --
  1866. -- @param host table containing host information
  1867. -- @param port table containing port information
  1868. -- @return status true on success, false on failure
  1869. -- @return result containing error message on failure
  1870. ConnectEx = function( self, instanceInfo )
  1871. local status, result
  1872. self.stream = TDSStream:new()
  1873. status, result = self.stream:ConnectEx( instanceInfo )
  1874. if ( not(status) ) then
  1875. return false, result
  1876. end
  1877. return true
  1878. end,
  1879. --- Establishes a connection to the SQL server
  1880. --
  1881. -- @param host table containing host information
  1882. -- @param port table containing port information
  1883. -- @return status true on success, false on failure
  1884. -- @return result containing error message on failure
  1885. Connect = function( self, host, port )
  1886. local status, result
  1887. self.stream = TDSStream:new()
  1888. status, result = self.stream:Connect(host, port)
  1889. if ( not(status) ) then
  1890. return false, result
  1891. end
  1892. return true
  1893. end,
  1894. --- Returns true if discovery has been performed to detect
  1895. -- SQL Server instances on the given host
  1896. WasDiscoveryPerformed = function( host )
  1897. local mutex = nmap.mutex( "discovery_performed for " .. host.ip )
  1898. mutex( "lock" )
  1899. nmap.registry.mssql = nmap.registry.mssql or {}
  1900. nmap.registry.mssql.discovery_performed = nmap.registry.mssql.discovery_performed or {}
  1901. local wasPerformed = nmap.registry.mssql.discovery_performed[ host.ip ] or false
  1902. mutex( "done" )
  1903. return wasPerformed
  1904. end,
  1905. --- Adds an instance to the list of instances kept in the Nmap registry for
  1906. -- shared use by SQL Server scripts. If the registry already contains the
  1907. -- instance, any new information is merged into the existing instance info.
  1908. -- This may happen, for example, when an instance is discovered via named
  1909. -- pipes, but the same instance has already been discovered via SSRP; this
  1910. -- will prevent duplicates, where possible.
  1911. AddOrMergeInstance = function( newInstance )
  1912. local instanceExists
  1913. nmap.registry.mssql = nmap.registry.mssql or {}
  1914. nmap.registry.mssql.instances = nmap.registry.mssql.instances or {}
  1915. nmap.registry.mssql.instances[ newInstance.host.ip ] = nmap.registry.mssql.instances[ newInstance.host.ip ] or {}
  1916. for _, existingInstance in ipairs( nmap.registry.mssql.instances[ newInstance.host.ip ] ) do
  1917. if existingInstance == newInstance then
  1918. existingInstance:Merge( newInstance )
  1919. instanceExists = true
  1920. break
  1921. end
  1922. end
  1923. if not instanceExists then
  1924. table.insert( nmap.registry.mssql.instances[ newInstance.host.ip ], newInstance )
  1925. end
  1926. end,
  1927. --- Gets a table containing SqlServerInstanceInfo objects discovered on
  1928. -- the specified host (and port, if specified).
  1929. --
  1930. -- @param host A host table for the target host
  1931. -- @param port (Optional) If omitted, all of the instances for the host
  1932. -- will be returned.
  1933. -- @return A table containing SqlServerInstanceInfo objects, or nil
  1934. GetDiscoveredInstances = function( host, port )
  1935. nmap.registry.mssql = nmap.registry.mssql or {}
  1936. nmap.registry.mssql.instances = nmap.registry.mssql.instances or {}
  1937. nmap.registry.mssql.instances[ host.ip ] = nmap.registry.mssql.instances[ host.ip ] or {}
  1938. if ( not port ) then
  1939. local instances = nmap.registry.mssql.instances[ host.ip ]
  1940. if ( instances and #instances == 0 ) then instances = nil end
  1941. return instances
  1942. else
  1943. for _, instance in ipairs( nmap.registry.mssql.instances[ host.ip ] ) do
  1944. if ( instance.port and instance.port.number == port.number and
  1945. instance.port.protocol == port.protocol ) then
  1946. return { instance }
  1947. end
  1948. end
  1949. return nil
  1950. end
  1951. end,
  1952. --- Attempts to discover SQL Server instances using SSRP to query one or
  1953. -- more (if <code>broadcast</code> is used) SQL Server Browser services.
  1954. -- Any discovered instances are returned, as well as being stored for use
  1955. -- by other scripts (see <code>mssql.Helper.GetDiscoveredInstances()</code>).
  1956. --
  1957. -- @param host A host table for the target.
  1958. -- @param port (Optional) A port table for the target port. If this is nil,
  1959. -- the default SSRP port (UDP 1434) is used.
  1960. -- @param broadcast If true, this will be done with an SSRP broadcast, and
  1961. -- <code>host</code> should contain the broadcast specification (e.g.
  1962. -- ip = "255.255.255.255").
  1963. -- @return (status, result) If status is true, result is a table of
  1964. -- tables containing SqlServerInstanceInfo objects. The top-level table
  1965. -- is indexed by IP address. If status is false, result is an
  1966. -- error message.
  1967. DiscoverBySsrp = function( host, port, broadcast )
  1968. if broadcast then
  1969. local status, result = SSRP.DiscoverInstances_Broadcast( host, port )
  1970. if not status then
  1971. return status, result
  1972. else
  1973. for ipAddress, host in pairs( result ) do
  1974. for _, instance in ipairs( host ) do
  1975. Helper.AddOrMergeInstance( instance )
  1976. -- Give some version info back to Nmap
  1977. if ( instance.port and instance.version ) then
  1978. instance.version:PopulateNmapPortVersion( instance.port )
  1979. --nmap.set_port_version( instance.host, instance.port)
  1980. end
  1981. end
  1982. end
  1983. return true, result
  1984. end
  1985. else
  1986. local status, result = SSRP.DiscoverInstances( host, port )
  1987. if not status then
  1988. return status, result
  1989. else
  1990. for _, instance in ipairs( result ) do
  1991. Helper.AddOrMergeInstance( instance )
  1992. -- Give some version info back to Nmap
  1993. if ( instance.port and instance.version ) then
  1994. instance.version:PopulateNmapPortVersion( instance.port )
  1995. nmap.set_port_version( host, instance.port)
  1996. end
  1997. end
  1998. local instances_all = {}
  1999. instances_all[ host.ip ] = result
  2000. return true, instances_all
  2001. end
  2002. end
  2003. end,
  2004. --- Attempts to discover a SQL Server instance listening on the specified
  2005. -- port. If an instance is discovered, it is returned, as well as being
  2006. -- stored for use by other scripts (see <code>mssql.Helper.GetDiscoveredInstances()</code>).
  2007. --
  2008. -- @param host A host table for the target.
  2009. -- @param port A port table for the target port.
  2010. -- @return (status, result) If status is true, result is a table of
  2011. -- SqlServerInstanceInfo objects. If status is false, result is an
  2012. -- error message or nil.
  2013. DiscoverByTcp = function( host, port )
  2014. local version, instance, status
  2015. -- Check to see if we've already discovered an instance on this port
  2016. instance = Helper.GetDiscoveredInstances( host, port )
  2017. if ( not instance ) then
  2018. instance = SqlServerInstanceInfo:new()
  2019. instance.host = host
  2020. instance.port = port
  2021. status, version = Helper.GetInstanceVersion( instance )
  2022. if ( status ) then
  2023. Helper.AddOrMergeInstance( instance )
  2024. -- The point of this wasn't to get the version, just to use the
  2025. -- pre-login packet to determine whether there was a SQL Server on
  2026. -- the port. However, since we have the version now, we'll store it.
  2027. instance.version = version
  2028. -- Give some version info back to Nmap
  2029. if ( instance.port and instance.version ) then
  2030. instance.version:PopulateNmapPortVersion( instance.port )
  2031. nmap.set_port_version( host, instance.port)
  2032. end
  2033. end
  2034. end
  2035. return (instance ~= nil), { instance }
  2036. end,
  2037. --- Attempts to discover SQL Server instances listening on default named
  2038. -- pipes. Any discovered instances are returned, as well as being stored
  2039. -- for use by other scripts (see <code>mssql.Helper.GetDiscoveredInstances()</code>).
  2040. --
  2041. -- @param host A host table for the target.
  2042. -- @param port A port table for the port to connect on for SMB
  2043. -- @return (status, result) If status is true, result is a table of
  2044. -- SqlServerInstanceInfo objects. If status is false, result is an
  2045. -- error message or nil.
  2046. DiscoverBySmb = function( host, port )
  2047. local defaultPipes = {
  2048. "\\sql\\query",
  2049. "\\MSSQL$SQLEXPRESS\\sql\\query",
  2050. "\\MSSQL$SQLSERVER\\sql\\query",
  2051. }
  2052. local tdsStream = TDSStream:new()
  2053. local status, result, instances_host
  2054. for _, pipeSubPath in ipairs( defaultPipes ) do
  2055. status, result = tdsStream:ConnectToNamedPipe( host, pipeSubPath, nil )
  2056. if status then
  2057. instances_host = {}
  2058. local instance = SqlServerInstanceInfo:new()
  2059. instance.pipeName = tdsStream:GetNamedPipeName()
  2060. tdsStream:Disconnect()
  2061. instance.host = host
  2062. Helper.AddOrMergeInstance( instance )
  2063. table.insert( instances_host, instance )
  2064. else
  2065. stdnse.print_debug( 3, "DiscoverBySmb \n pipe: %s\n result: %s", pipeSubPath, tostring( result ) )
  2066. end
  2067. end
  2068. return (instances_host ~= nil), instances_host
  2069. end,
  2070. --- Attempts to discover SQL Server instances by a variety of means.
  2071. -- This function calls the three DiscoverBy functions, which perform the
  2072. -- actual discovery. Any discovered instances can be retrieved using
  2073. -- <code>mssql.Helper.GetDiscoveredInstances()</code>.
  2074. --
  2075. -- @param host Host table as received by the script action function
  2076. Discover = function( host )
  2077. nmap.registry.mssql = nmap.registry.mssql or {}
  2078. nmap.registry.mssql.discovery_performed = nmap.registry.mssql.discovery_performed or {}
  2079. nmap.registry.mssql.discovery_performed[ host.ip ] = false
  2080. local mutex = nmap.mutex( "discovery_performed for " .. host.ip )
  2081. mutex( "lock" )
  2082. local sqlDefaultPort = nmap.get_port_state( host, {number = 1433, protocol = "tcp"} ) or {number = 1433, protocol = "tcp"}
  2083. local sqlBrowserPort = nmap.get_port_state( host, {number = 1434, protocol = "udp"} ) or {number = 1434, protocol = "udp"}
  2084. local smbPort
  2085. -- smb.get_port() will return nil if no SMB port was scanned OR if SMB ports were scanned but none was open
  2086. local smbPortNumber = smb.get_port( host )
  2087. if ( smbPortNumber ) then
  2088. smbPort = nmap.get_port_state( host, {number = smbPortNumber, protocol = "tcp"} )
  2089. -- There's no use in manually setting an SMB port; if no SMB port was
  2090. -- scanned and found open, the SMB library won't work
  2091. end
  2092. -- if the user has specified ports, we'll check those too
  2093. local targetInstancePorts = stdnse.get_script_args( "mssql.instance-port" )
  2094. if ( sqlBrowserPort and sqlBrowserPort.state ~= "closed" ) then
  2095. Helper.DiscoverBySsrp( host, sqlBrowserPort )
  2096. end
  2097. if ( sqlDefaultPort and sqlDefaultPort.state ~= "closed" ) then
  2098. Helper.DiscoverByTcp( host, sqlDefaultPort )
  2099. end
  2100. if ( smbPort ) then
  2101. Helper.DiscoverBySmb( host, smbPort )
  2102. end
  2103. if ( targetInstancePorts ) then
  2104. if ( type( targetInstancePorts ) == "string" ) then
  2105. targetInstancePorts = { targetInstancePorts }
  2106. end
  2107. for _, portNumber in ipairs( targetInstancePorts ) do
  2108. portNumber = tonumber( portNumber )
  2109. Helper.DiscoverByTcp( host, {number = portNumber, protocol = "tcp"} )
  2110. end
  2111. end
  2112. nmap.registry.mssql.discovery_performed[ host.ip ] = true
  2113. mutex( "done" )
  2114. end,
  2115. --- Returns all of the credentials available for the target instance,
  2116. -- including any set by the <code>mssql.username</code> and <code>mssql.password</code>
  2117. -- script arguments.
  2118. --
  2119. -- @param instanceInfo A SqlServerInstanceInfo object for the target instance
  2120. -- @return A table of usernames mapped to passwords (i.e. <code>creds[ username ] = password</code>)
  2121. GetLoginCredentials_All = function( instanceInfo )
  2122. local credentials = instanceInfo.credentials or {}
  2123. local credsExist = false
  2124. for _, _ in pairs( credentials ) do
  2125. credsExist = true
  2126. break
  2127. end
  2128. if ( not credsExist ) then credentials = nil end
  2129. if ( stdnse.get_script_args( "mssql.username" ) ) then
  2130. credentials = credentials or {}
  2131. local usernameArg = stdnse.get_script_args( "mssql.username" )
  2132. local passwordArg = stdnse.get_script_args( "mssql.password" ) or ""
  2133. credentials[ usernameArg ] = passwordArg
  2134. end
  2135. return credentials
  2136. end,
  2137. --- Returns a username-password set according to the following rules of
  2138. -- precedence:
  2139. -- * If the <code>mssql.username</code> and <code>mssql.password</code>
  2140. -- script arguments were set, their values are used. (If the username
  2141. -- argument was specified without the password argument, a blank
  2142. -- password is used.)
  2143. -- * If the password for the "sa" account has been discovered (e.g. by the
  2144. -- <code>ms-sql-empty-password</code> or <code>ms-sql-brute</code>
  2145. -- scripts), these credentials are used.
  2146. -- * If other credentials have been discovered, the first of these in the
  2147. -- table are used.
  2148. -- * Otherwise, nil is returned.
  2149. --
  2150. -- @param instanceInfo A SqlServerInstanceInfo object for the target instance
  2151. -- @return (username, password)
  2152. GetLoginCredentials = function( instanceInfo )
  2153. -- First preference goes to any user-specified credentials
  2154. local username = stdnse.get_script_args( "mssql.username" )
  2155. local password = stdnse.get_script_args( "mssql.password" ) or ""
  2156. -- Otherwise, use any valid credentials that have been discovered (e.g. by ms-sql-brute)
  2157. if ( not(username) and instanceInfo.credentials ) then
  2158. -- Second preference goes to the "sa" account
  2159. if ( instanceInfo.credentials.sa ) then
  2160. username = "sa"
  2161. password = instanceInfo.credentials.sa
  2162. else
  2163. -- ok were stuck with some n00b account, just get the first one
  2164. for user, pass in pairs( instanceInfo.credentials ) do
  2165. username = user
  2166. password = pass
  2167. break
  2168. end
  2169. end
  2170. end
  2171. return username, password
  2172. end,
  2173. --- Disconnects from the SQL Server
  2174. --
  2175. -- @return status true on success, false on failure
  2176. -- @return result containing error message on failure
  2177. Disconnect = function( self )
  2178. if ( not(self.stream) ) then
  2179. return false, "Not connected to server"
  2180. end
  2181. self.stream:Disconnect()
  2182. self.stream = nil
  2183. return true
  2184. end,
  2185. --- Authenticates to SQL Server. If login fails, one of the following error
  2186. -- messages will be returned:
  2187. -- * "Password is expired"
  2188. -- * "Must change password at next logon"
  2189. -- * "Account is locked out"
  2190. -- * "Login Failed"
  2191. --
  2192. -- @param username string containing the username for authentication
  2193. -- @param password string containing the password for authentication
  2194. -- @param database string containing the database to access
  2195. -- @param servername string containing the name or ip of the remote server
  2196. -- @return status true on success, false on failure
  2197. -- @return result containing error message on failure
  2198. -- @return errorDetail nil or a <code>LoginErrorType</code> value, if available
  2199. Login = function( self, username, password, database, servername )
  2200. local loginPacket = LoginPacket:new()
  2201. local status, result, data, errorDetail, token
  2202. local servername = servername or "DUMMY"
  2203. local pos = 1
  2204. local ntlmAuth = false
  2205. if ( not self.stream ) then
  2206. return false, "Not connected to server"
  2207. end
  2208. loginPacket:SetUsername(username)
  2209. loginPacket:SetPassword(password)
  2210. loginPacket:SetDatabase(database)
  2211. loginPacket:SetServer(servername)
  2212. local domain = stdnse.get_script_args("mssql.domain")
  2213. if (domain) then
  2214. if ( not(HAVE_SSL) ) then return false, "mssql: OpenSSL not present" end
  2215. ntlmAuth = true
  2216. -- if the domain was specified without an argument, set a default domain of "."
  2217. if (domain == 1 or domain == true ) then
  2218. domain = "."
  2219. end
  2220. loginPacket:SetDomain(domain)
  2221. end
  2222. status, result = self.stream:Send( loginPacket:ToString() )
  2223. if ( not(status) ) then
  2224. return false, result
  2225. end
  2226. status, data, errorDetail = self.stream:Receive()
  2227. if ( not(status) ) then
  2228. -- When logging in via named pipes, SQL Server will sometimes
  2229. -- disconnect the pipe if the login attempt failed (this only seems
  2230. -- to happen with non-"sa") accounts. At this point, having
  2231. -- successfully connected and sent a message, we can be reasonably
  2232. -- comfortable that a disconnected pipe indicates a failed login.
  2233. if ( errorDetail == "NT_STATUS_PIPE_DISCONNECTED" ) then
  2234. return false, "Bad username or password", LoginErrorType.InvalidUsernameOrPassword
  2235. end
  2236. return false, data
  2237. end
  2238. if ( ntlmAuth ) then
  2239. local pos, nonce = Token.ParseToken( data, pos )
  2240. local authpacket = NTAuthenticationPacket:new( username, password, domain, nonce )
  2241. status, result = self.stream:Send( authpacket:ToString() )
  2242. status, data = self.stream:Receive()
  2243. if ( not(status) ) then
  2244. return false, data
  2245. end
  2246. end
  2247. while( pos < data:len() ) do
  2248. pos, token = Token.ParseToken( data, pos )
  2249. if ( -1 == pos ) then
  2250. return false, token
  2251. end
  2252. if ( token.type == TokenType.ErrorMessage ) then
  2253. local errorMessageLookup = {
  2254. [LoginErrorType.AccountLockedOut] = "Account is locked out",
  2255. [LoginErrorType.NotAssociatedWithTrustedConnection] = "User is not associated with a trusted connection (instance may allow Windows authentication only)",
  2256. [LoginErrorType.InvalidUsernameOrPassword] = "Bad username or password",
  2257. [LoginErrorType.PasswordExpired] = "Password is expired",
  2258. [LoginErrorType.PasswordMustChange] = "Must change password at next logon",
  2259. }
  2260. local errorMessage = errorMessageLookup[ token.errno ] or string.format( "Login Failed (%s)", tostring(token.errno) )
  2261. return false, errorMessage, token.errno
  2262. elseif ( token.type == TokenType.LoginAcknowledgement ) then
  2263. return true, "Login Success"
  2264. end
  2265. end
  2266. return false, "Failed to process login response"
  2267. end,
  2268. --- Authenticates to SQL Server, using the credentials returned by
  2269. -- Helper.GetLoginCredentials(). If the login is rejected by the server,
  2270. -- the error code will be returned, as a number in the form of a
  2271. -- <code>mssql.LoginErrorType</code> (for which error messages can be
  2272. -- looked up in <code>mssql.LoginErrorMessage</code>).
  2273. --
  2274. -- @param instanceInfo a SqlServerInstanceInfo object for the instance to log into
  2275. -- @param database string containing the database to access
  2276. -- @param servername string containing the name or ip of the remote server
  2277. -- @return status true on success, false on failure
  2278. -- @return result containing error code or error message
  2279. LoginEx = function( self, instanceInfo, database, servername )
  2280. local servername = servername or instanceInfo.host.ip
  2281. local username, password = Helper.GetLoginCredentials( instanceInfo )
  2282. if ( not username ) then
  2283. return false, "No login credentials"
  2284. end
  2285. return self:Login( username, password, database, servername )
  2286. end,
  2287. --- Performs a SQL query and parses the response
  2288. --
  2289. -- @param query string containing the SQL query
  2290. -- @return status true on success, false on failure
  2291. -- @return table containing a table of columns for each row
  2292. -- or error message on failure
  2293. Query = function( self, query )
  2294. local queryPacket = QueryPacket:new()
  2295. local status, result, data, token, colinfo, rows
  2296. local pos = 1
  2297. if ( nil == self.stream ) then
  2298. return false, "Not connected to server"
  2299. end
  2300. queryPacket:SetQuery( query )
  2301. status, result = self.stream:Send( queryPacket:ToString() )
  2302. if ( not(status) ) then
  2303. return false, result
  2304. end
  2305. status, data = self.stream:Receive()
  2306. if ( not(status) ) then
  2307. return false, data
  2308. end
  2309. -- Iterate over tokens until we get to a rowtag
  2310. while( pos < data:len() ) do
  2311. local rowtag = select(2, bin.unpack("C", data, pos))
  2312. if ( rowtag == TokenType.Row ) then
  2313. break
  2314. end
  2315. pos, token = Token.ParseToken( data, pos )
  2316. if ( -1 == pos ) then
  2317. return false, token
  2318. end
  2319. if ( token.type == TokenType.ErrorMessage ) then
  2320. return false, token.error
  2321. elseif ( token.type == TokenType.TDS7Results ) then
  2322. colinfo = token.colinfo
  2323. end
  2324. end
  2325. rows = {}
  2326. while(true) do
  2327. local rowtag
  2328. pos, rowtag = bin.unpack("C", data, pos )
  2329. if ( rowtag ~= TokenType.Row ) then
  2330. break
  2331. end
  2332. if ( rowtag == TokenType.Row and colinfo and #colinfo > 0 ) then
  2333. local columns = {}
  2334. for i=1, #colinfo do
  2335. local val
  2336. if ( ColumnData.Parse[colinfo[i].type] ) then
  2337. if not ( colinfo[i].type == 106 or colinfo[i].type == 108) then
  2338. pos, val = ColumnData.Parse[colinfo[i].type](data, pos)
  2339. else
  2340. -- decimal / numeric types need precision and scale passed.
  2341. pos, val = ColumnData.Parse[colinfo[i].type]( colinfo[i].precision, colinfo[i].scale, data, pos)
  2342. end
  2343. if ( -1 == pos ) then
  2344. return false, val
  2345. end
  2346. table.insert(columns, val)
  2347. else
  2348. return false, ("unknown datatype=0x%X"):format(colinfo[i].type)
  2349. end
  2350. end
  2351. table.insert(rows, columns)
  2352. end
  2353. end
  2354. result = {}
  2355. result.rows = rows
  2356. result.colinfo = colinfo
  2357. return true, result
  2358. end,
  2359. --- Attempts to connect to a SQL Server instance listening on a TCP port in
  2360. -- order to determine the version of the SSNetLib DLL, which is an
  2361. -- authoritative version number for the SQL Server instance itself.
  2362. --
  2363. -- @param instanceInfo An instance of SqlServerInstanceInfo
  2364. -- @return status true on success, false on failure
  2365. -- @return versionInfo an instance of mssql.SqlServerVersionInfo, or nil
  2366. GetInstanceVersion = function( instanceInfo )
  2367. if ( not instanceInfo.host or not (instanceInfo:HasNetworkProtocols()) ) then return false, nil end
  2368. local status, response, version
  2369. local tdsStream = TDSStream:new()
  2370. status, response = tdsStream:ConnectEx( instanceInfo )
  2371. if ( not status ) then
  2372. stdnse.print_debug( 2, "%s: Connection to %s failed: %s", "MSSQL", instanceInfo:GetName(), response or "" )
  2373. return false, "Connect failed"
  2374. end
  2375. local preLoginRequest = PreLoginPacket:new()
  2376. preLoginRequest:SetInstanceName( instanceInfo.instanceName )
  2377. tdsStream:SetTimeout( 5000 )
  2378. tdsStream:Send( preLoginRequest:ToBytes() )
  2379. -- read in any response we might get
  2380. status, response = tdsStream:Receive()
  2381. tdsStream:Disconnect()
  2382. if status then
  2383. local preLoginResponse
  2384. status, preLoginResponse = PreLoginPacket.FromBytes( response )
  2385. if status then
  2386. version = preLoginResponse.versionInfo
  2387. else
  2388. stdnse.print_debug( 2, "%s: Parsing of pre-login packet from %s failed: %s",
  2389. "MSSQL", instanceInfo:GetName(), preLoginResponse or "" )
  2390. return false, "Parsing failed"
  2391. end
  2392. else
  2393. stdnse.print_debug( 2, "%s: Receive for %s failed: %s", "MSSQL", instanceInfo:GetName(), response or "" )
  2394. return false, "Receive failed"
  2395. end
  2396. return status, version
  2397. end,
  2398. --- Gets a table containing SqlServerInstanceInfo objects for the instances
  2399. -- that should be run against, based on the script-args (e.g. <code>mssql.instance</code>)
  2400. --
  2401. -- @param host Host table as received by the script action function
  2402. -- @param port (Optional) Port table as received by the script action function
  2403. -- @return status True on success, false on failure
  2404. -- @return instances If status is true, this will be a table with one or
  2405. -- more SqlServerInstanceInfo objects. If status is false, this will be
  2406. -- an error message.
  2407. GetTargetInstances = function( host, port )
  2408. if ( port ) then
  2409. local status = true
  2410. local instance = Helper.GetDiscoveredInstances( host, port )
  2411. if ( not instance ) then
  2412. status, instance = Helper.DiscoverByTcp( host, port )
  2413. end
  2414. if ( instance ) then
  2415. return true, instance
  2416. else
  2417. return false, "No SQL Server instance detected on this port"
  2418. end
  2419. else
  2420. local targetInstanceNames = stdnse.get_script_args( "mssql.instance-name" )
  2421. local targetInstancePorts = stdnse.get_script_args( "mssql.instance-port" )
  2422. local targetAllInstances = stdnse.get_script_args( "mssql.instance-all" )
  2423. if ( targetInstanceNames and targetInstancePorts ) then
  2424. return false, "Connections can be made either by instance name or port."
  2425. end
  2426. if ( targetAllInstances and ( targetInstanceNames or targetInstancePorts ) ) then
  2427. return false, "All instances cannot be specified together with an instance name or port."
  2428. end
  2429. if ( not (targetInstanceNames or targetInstancePorts or targetAllInstances) ) then
  2430. return false, "No instance(s) specified."
  2431. end
  2432. if ( not Helper.WasDiscoveryPerformed( host ) ) then
  2433. stdnse.print_debug( 2, "%s: Discovery has not been performed prior to GetTargetInstances() call. Performing discovery now.", "MSSQL" )
  2434. Helper.Discover( host )
  2435. end
  2436. local instanceList = Helper.GetDiscoveredInstances( host )
  2437. if ( not instanceList ) then
  2438. return false, "No instances found on target host"
  2439. end
  2440. local targetInstances = {}
  2441. if ( targetAllInstances ) then
  2442. targetInstances = instanceList
  2443. else
  2444. -- We want an easy way to look up whether an instance's name was
  2445. -- in our target list. So, we'll make a table of { instanceName = true, ... }
  2446. local temp = {}
  2447. if ( targetInstanceNames ) then
  2448. if ( type( targetInstanceNames ) == "string" ) then
  2449. targetInstanceNames = { targetInstanceNames }
  2450. end
  2451. for _, instanceName in ipairs( targetInstanceNames ) do
  2452. temp[ string.upper( instanceName ) ] = true
  2453. end
  2454. end
  2455. targetInstanceNames = temp
  2456. -- Do the same for the target ports
  2457. temp = {}
  2458. if ( targetInstancePorts ) then
  2459. if ( type( targetInstancePorts ) == "string" ) then
  2460. targetInstancePorts = { targetInstancePorts }
  2461. end
  2462. for _, portNumber in ipairs( targetInstancePorts ) do
  2463. portNumber = tonumber( portNumber )
  2464. temp[portNumber] = true
  2465. end
  2466. end
  2467. targetInstancePorts = temp
  2468. for _, instance in ipairs( instanceList ) do
  2469. if ( instance.instanceName and targetInstanceNames[ string.upper( instance.instanceName ) ] ) then
  2470. table.insert( targetInstances, instance )
  2471. elseif ( instance.port and targetInstancePorts[ tonumber( instance.port.number ) ] ) then
  2472. table.insert( targetInstances, instance )
  2473. end
  2474. end
  2475. end
  2476. if ( #targetInstances > 0 ) then
  2477. return true, targetInstances
  2478. else
  2479. return false, "Specified instance(s) not found on target host"
  2480. end
  2481. end
  2482. end,
  2483. --- Queries the SQL Browser service for the DAC port of the specified instance
  2484. -- The DAC (Dedicated Admin Connection) port allows DBA's to connect to
  2485. -- the database when normal connection attempts fail, for example, when
  2486. -- the server is hanging, out of memory or other bad states.
  2487. --
  2488. -- @param host Host table as received by the script action function
  2489. -- @param instanceName the instance name to probe for a DAC port
  2490. -- @return number containing the DAC port on success or nil on failure
  2491. DiscoverDACPort = function(host, instanceName)
  2492. local socket = nmap.new_socket()
  2493. socket:set_timeout(5000)
  2494. if ( not(socket:connect(host, 1434, "udp")) ) then
  2495. return false, "Failed to connect to sqlbrowser service"
  2496. end
  2497. if ( not(socket:send(bin.pack("Hz", "0F01", instanceName))) ) then
  2498. socket:close()
  2499. return false, "Failed to send request to sqlbrowser service"
  2500. end
  2501. local status, data = socket:receive_buf(match.numbytes(6), true)
  2502. if ( not(status) ) then
  2503. socket:close()
  2504. return nil
  2505. end
  2506. socket:close()
  2507. if ( #data < 6 ) then
  2508. return nil
  2509. end
  2510. return select(2, bin.unpack("<S", data, 5))
  2511. end,
  2512. --- Returns a hostrule for standard SQL Server scripts, which will return
  2513. -- true if one or more instances have been targeted with the <code>mssql.instance</code>
  2514. -- script argument. However, if a previous script has failed to find any
  2515. -- SQL Server instances on the host, the hostrule function will return
  2516. -- false to keep further scripts from running unnecessarily on that host.
  2517. --
  2518. -- @return A hostrule function (use as <code>hostrule = mssql.GetHostrule_Standard()</code>)
  2519. GetHostrule_Standard = function()
  2520. return function( host )
  2521. if ( stdnse.get_script_args( {"mssql.instance-all", "mssql.instance-name", "mssql.instance-port"} ) ~= nil ) then
  2522. if ( Helper.WasDiscoveryPerformed( host ) ) then
  2523. return Helper.GetDiscoveredInstances( host ) ~= nil
  2524. else
  2525. return true
  2526. end
  2527. else
  2528. return false
  2529. end
  2530. end
  2531. end,
  2532. --- Returns a portrule for standard SQL Server scripts, which will run return
  2533. -- true if BOTH of the following conditions are met:
  2534. -- * The port has been identified as "ms-sql-s"
  2535. -- * The <code>mssql.instance</code> script argument has NOT been used
  2536. --
  2537. -- @return A portrule function (use as <code>portrule = mssql.GetPortrule_Standard()</code>)
  2538. GetPortrule_Standard = function()
  2539. return function( host, port )
  2540. return ( shortport.service( "ms-sql-s" )(host, port) and
  2541. stdnse.get_script_args( {"mssql.instance-all", "mssql.instance-name", "mssql.instance-port"} ) == nil)
  2542. end
  2543. end,
  2544. }
  2545. Auth = {
  2546. --- Encrypts a password using the TDS7 *ultra secure* XOR encryption
  2547. --
  2548. -- @param password string containing the password to encrypt
  2549. -- @return string containing the encrypted password
  2550. TDS7CryptPass = function(password)
  2551. local xormask = 0x5a5a
  2552. local result = ""
  2553. for i=1, password:len() do
  2554. local c = bit.bxor( string.byte( password:sub( i, i ) ), xormask )
  2555. local m1= bit.band( bit.rshift( c, 4 ), 0x0F0F )
  2556. local m2= bit.band( bit.lshift( c, 4 ), 0xF0F0 )
  2557. result = result .. bin.pack("S", bit.bor( m1, m2 ) )
  2558. end
  2559. return result
  2560. end,
  2561. LmResponse = function( password, nonce )
  2562. if ( not(HAVE_SSL) ) then
  2563. stdnse.print_debug("ERROR: Nmap is missing OpenSSL")
  2564. return
  2565. end
  2566. if(#password < 14) then
  2567. password = password .. string.rep(string.char(0), 14 - #password)
  2568. end
  2569. password = password:upper()
  2570. -- Take the first and second half of the password (note that if it's longer than 14 characters, it's truncated)
  2571. local str1 = string.sub(password, 1, 7)
  2572. local str2 = string.sub(password, 8, 14)
  2573. -- Generate the keys
  2574. local key1 = openssl.DES_string_to_key(str1)
  2575. local key2 = openssl.DES_string_to_key(str2)
  2576. local result = openssl.encrypt("DES", key1, nil, nonce) .. openssl.encrypt("DES", key2, nil, nonce)
  2577. if(#result < 21) then
  2578. result = result .. string.rep(string.char(0), 21 - #result)
  2579. end
  2580. str1 = string.sub(result, 1, 7)
  2581. str2 = string.sub(result, 8, 14)
  2582. local str3 = string.sub(result, 15, 21)
  2583. key1 = openssl.DES_string_to_key(str1)
  2584. key2 = openssl.DES_string_to_key(str2)
  2585. local key3 = openssl.DES_string_to_key(str3)
  2586. result = openssl.encrypt("DES", key1, nil, nonce) .. openssl.encrypt("DES", key2, nil, nonce) .. openssl.encrypt("DES", key3, nil, nonce)
  2587. return result
  2588. end,
  2589. NtlmResponse = function( password, nonce )
  2590. local lm_response, ntlm_response, mac_key = smbauth.get_password_response(nil,
  2591. nil,
  2592. nil,
  2593. password,
  2594. nil,
  2595. "v1",
  2596. nonce,
  2597. false
  2598. )
  2599. return ntlm_response
  2600. end,
  2601. }
  2602. --- "static" Utility class containing mostly conversion functions
  2603. Util =
  2604. {
  2605. --- Converts a string to a wide string
  2606. --
  2607. -- @param str string to be converted
  2608. -- @return string containing a two byte representation of str where a zero
  2609. -- byte character has been tagged on to each character.
  2610. ToWideChar = function( str )
  2611. return str:gsub("(.)", "%1" .. string.char(0x00) )
  2612. end,
  2613. --- Concerts a wide string to string
  2614. --
  2615. -- @param wstr containing the wide string to convert
  2616. -- @return string with every other character removed
  2617. FromWideChar = function( wstr )
  2618. local str = ""
  2619. if ( nil == wstr ) then
  2620. return nil
  2621. end
  2622. for i=1, wstr:len(), 2 do
  2623. str = str .. wstr:sub(i, i)
  2624. end
  2625. return str
  2626. end,
  2627. --- Takes a table as returned by Query and does some fancy formatting
  2628. -- better suitable for <code>stdnse.output_result</code>
  2629. --
  2630. -- @param tbl as recieved by <code>Helper.Query</code>
  2631. -- @param with_headers boolean true if output should contain column headers
  2632. -- @return table suitable for <code>stdnse.output_result</code>
  2633. FormatOutputTable = function ( tbl, with_headers )
  2634. local new_tbl = {}
  2635. local col_names = {}
  2636. if ( not(tbl) ) then
  2637. return
  2638. end
  2639. if ( with_headers and tbl.rows and #tbl.rows > 0 ) then
  2640. local headers
  2641. for k, v in pairs( tbl.colinfo ) do
  2642. table.insert( col_names, v.text)
  2643. end
  2644. headers = stdnse.strjoin("\t", col_names)
  2645. table.insert( new_tbl, headers)
  2646. headers = headers:gsub("[^%s]", "=")
  2647. table.insert( new_tbl, headers )
  2648. end
  2649. for _, v in ipairs( tbl.rows ) do
  2650. table.insert( new_tbl, stdnse.strjoin("\t", v) )
  2651. end
  2652. return new_tbl
  2653. end,
  2654. }
  2655. return _ENV;