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 awkward 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 character 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 https://nmap.org/book/man-legal.html 62-- 63-- @author Patrik Karlsson <patrik@cqure.net> 64-- @author Chris Woodbury 65-- 66-- @args mssql.username The username to use to connect to SQL Server instances. 67-- This username is used by scripts taking actions that require 68-- authentication (e.g. <code>ms-sql-query</code>) This username (and its 69-- associated password) takes precedence over any credentials discovered 70-- by the <code>ms-sql-brute</code> and <code>ms-sql-empty-password</code> 71-- scripts. 72-- 73-- @args mssql.password The password for <code>mssql.username</code>. If this 74-- argument is not given but <code>mssql.username</code>, a blank password 75-- is used. 76-- 77-- @args mssql.instance-name The name of the instance to connect to. 78-- 79-- @args mssql.instance-port The port of the instance to connect to. 80-- 81-- @args mssql.instance-all Targets all SQL server instances discovered 82-- through the browser service. 83-- 84-- @args mssql.domain The domain against which to perform integrated 85-- authentication. When set, the scripts assume integrated authentication 86-- should be performed, rather than the default sql login. 87-- 88-- @args mssql.protocol The protocol to use to connect to the instance. The 89-- protocol may be either <code>NP</code>,<code>Named Pipes</code> or 90-- <code>TCP</code>. 91-- 92-- @args mssql.timeout How long to wait for SQL responses. This is a number 93-- followed by <code>ms</code> for milliseconds, <code>s</code> for 94-- seconds, <code>m</code> for minutes, or <code>h</code> for hours. 95-- Default: <code>30s</code>. 96-- 97-- @args mssql.scanned-ports-only If set, the script will only connect 98-- to ports that were included in the Nmap scan. This may result in 99-- instances not being discovered, particularly if UDP port 1434 is not 100-- included. Additionally, instances that are found to be running on 101-- ports that were not scanned (e.g. if 1434/udp is in the scan and the 102-- SQL Server Browser service on that port reports an instance 103-- listening on 43210/tcp, which was not scanned) will be reported but 104-- will not be stored for use by other ms-sql-* scripts. 105 106local math = require "math" 107local match = require "match" 108local nmap = require "nmap" 109local datetime = require "datetime" 110local shortport = require "shortport" 111local smb = require "smb" 112local smbauth = require "smbauth" 113local stdnse = require "stdnse" 114local strbuf = require "strbuf" 115local string = require "string" 116local table = require "table" 117local unicode = require "unicode" 118_ENV = stdnse.module("mssql", stdnse.seeall) 119 120-- Created 01/17/2010 - v0.1 - created by Patrik Karlsson <patrik@cqure.net> 121-- Revised 03/28/2010 - v0.2 - fixed incorrect token types. added 30 seconds timeout 122-- Revised 01/23/2011 - v0.3 - fixed parsing error in discovery code with patch 123-- from Chris Woodbury 124-- Revised 02/01/2011 - v0.4 - numerous changes and additions to support new 125-- functionality in ms-sql- scripts and to be more 126-- robust in parsing and handling data. (Chris Woodbury) 127-- Revised 02/19/2011 - v0.5 - numerous changes in script, library behaviour 128-- * huge improvements in version detection 129-- * added support for named pipes 130-- * added support for integrated NTLMv1 authentication 131-- 132-- (Patrik Karlsson, Chris Woodbury) 133-- Revised 08/19/2012 - v0.6 - added multiple data types 134-- * added detection and handling of null values when processing query responses from the server 135-- * added DoneProc response token support 136-- 137-- (Tom Sellers) 138-- Updated 10/01/2012 - v0.7 - added support for 2012 and later service packs for 2005, 2008 and 2008 R2 (Rob Nicholls) 139-- Updated 02/06/2015 - v0.8 - added support for 2014 and later service packs for older versions (Rob Nicholls) 140 141local HAVE_SSL, openssl = pcall(require, "openssl") 142 143do 144 namedpipes = smb.namedpipes 145 local arg = stdnse.get_script_args( "mssql.timeout" ) or "30s" 146 147 local timeout, err = stdnse.parse_timespec(arg) 148 if not timeout then 149 error(err) 150 end 151 MSSQL_TIMEOUT = timeout 152 153 SCANNED_PORTS_ONLY = false 154 if ( stdnse.get_script_args( "mssql.scanned-ports-only" ) ) then 155 SCANNED_PORTS_ONLY = true 156 end 157end 158 159-- This constant is number of seconds from 1900-01-01 to 1970-01-01 160local tds_offset_seconds = -2208988800 - datetime.utc_offset() 161 162-- ************************************* 163-- Informational Classes 164-- ************************************* 165 166--- SqlServerInstanceInfo class 167SqlServerInstanceInfo = 168{ 169 instanceName = nil, 170 version = nil, 171 serverName = nil, 172 isClustered = nil, 173 host = nil, 174 port = nil, 175 pipeName = nil, 176 177 new = function(self,o) 178 o = o or {} 179 setmetatable(o, self) 180 self.__index = self 181 return o 182 end, 183 184 -- Compares two SqlServerInstanceInfo objects and determines whether they 185 -- refer to the same SQL Server instance, judging by a combination of host, 186 -- port, named pipe information and instance name. 187 __eq = function( self, other ) 188 local areEqual 189 if ( not (self.host and other.host) ) then 190 -- if they don't both have host information, we certainly can't say 191 -- whether they're the same 192 areEqual = false 193 else 194 areEqual = (self.host.ip == other.host.ip) 195 end 196 197 if (self.port and other.port) then 198 areEqual = areEqual and ( other.port.number == self.port.number and 199 other.port.protocol == self.port.protocol ) 200 elseif (self.pipeName and other.pipeName) then 201 areEqual = areEqual and (self.pipeName == other.pipeName) 202 elseif (self.instanceName and other.instanceName) then 203 areEqual = areEqual and (self.instanceName == other.instanceName) 204 else 205 -- if we have neither port nor named pipe info nor instance names, 206 -- we can't say whether they're the same 207 areEqual = false 208 end 209 210 return areEqual 211 end, 212 213 --- Merges the data from one SqlServerInstanceInfo object into another. 214 -- 215 -- Each field in the first object is populated with the data from that field 216 -- in second object if the first object's field is nil OR if 217 -- <code>overwrite</code> is set to true. A special case is made for the 218 -- <code>version</code> field, which is only overwritten in the second object 219 -- has more reliable version information. The second object is not modified. 220 Merge = function( self, other, overwrite ) 221 local mergeFields = { "host", "port", "instanceName", "version", "isClustered", "pipeName" } 222 for _, fieldname in ipairs( mergeFields ) do 223 -- Add values from other only if self doesn't have a value, or if overwrite is true 224 if ( other[ fieldname ] ~= nil and (overwrite or self[ fieldname ] == nil) ) then 225 self[ fieldname ] = other[ fieldname ] 226 end 227 end 228 if (self.version and self.version.source == "SSRP" and 229 other.version and other.version.Source == "SSNetLib") then 230 self.version = other.version 231 end 232 end, 233 234 --- Returns a name for the instance, based on the available information. 235 -- 236 -- This may take one of the following forms: 237 -- * HOST\INSTANCENAME 238 -- * PIPENAME 239 -- * HOST:PORT 240 GetName = function( self ) 241 if (self.instanceName) then 242 return string.format( "%s\\%s", self.host.ip or self.serverName or "[nil]", self.instanceName or "[nil]" ) 243 elseif (self.pipeName) then 244 return string.format( "%s", self.pipeName ) 245 else 246 return string.format( "%s:%s", self.host.ip or self.serverName or "[nil]", (self.port and self.port.number) or "[nil]" ) 247 end 248 end, 249 250 --- Sets whether the instance is in a cluster 251 -- 252 -- @param self 253 -- @param isClustered Boolean true or the string "Yes" are interpreted as true; 254 -- all other values are interpreted as false. 255 SetIsClustered = function( self, isClustered ) 256 self.isClustered = (isClustered == true) or (isClustered == "Yes") 257 end, 258 259 --- Indicates whether this instance has networking protocols enabled, such 260 -- that scripts could attempt to connect to it. 261 HasNetworkProtocols = function( self ) 262 return (self.pipeName ~= nil) or (self.port and self.port.number) 263 end, 264} 265 266 267--- SqlServerVersionInfo class 268SqlServerVersionInfo = 269{ 270 versionNumber = "", -- The full version string (e.g. "9.00.2047.00") 271 major = nil, -- The major version (e.g. 9) 272 minor = nil, -- The minor version (e.g. 0) 273 build = nil, -- The build number (e.g. 2047) 274 subBuild = nil, -- The sub-build number (e.g. 0) 275 productName = nil, -- The product name (e.g. "SQL Server 2005") 276 brandedVersion = nil, -- The branded version of the product (e.g. "2005") 277 servicePackLevel = nil, -- The service pack level (e.g. "SP1") 278 patched = nil, -- Whether patches have been applied since SP installation (true/false/nil) 279 source = nil, -- The source of the version info (e.g. "SSRP", "SSNetLib") 280 281 new = function(self,o) 282 o = o or {} 283 setmetatable(o, self) 284 self.__index = self 285 return o 286 end, 287 288 --- Sets the version using a version number string. 289 -- 290 -- @param versionNumber a version number string (e.g. "9.00.1399.00") 291 -- @param source a string indicating the source of the version info (e.g. "SSRP", "SSNetLib") 292 SetVersionNumber = function(self, versionNumber, source) 293 local major, minor, revision, subBuild 294 if versionNumber:match( "^%d+%.%d+%.%d+.%d+" ) then 295 major, minor, revision, subBuild = versionNumber:match( "^(%d+)%.(%d+)%.(%d+)" ) 296 elseif versionNumber:match( "^%d+%.%d+%.%d+" ) then 297 major, minor, revision = versionNumber:match( "^(%d+)%.(%d+)%.(%d+)" ) 298 else 299 stdnse.debug1("%s: SetVersionNumber: versionNumber is not in correct format: %s", "MSSQL", versionNumber or "nil" ) 300 end 301 302 self:SetVersion( major, minor, revision, subBuild, source ) 303 end, 304 305 --- Sets the version using the individual numeric components of the version 306 -- number. 307 -- 308 -- @param source a string indicating the source of the version info (e.g. "SSRP", "SSNetLib") 309 SetVersion = function(self, major, minor, build, subBuild, source) 310 self.source = source 311 -- make sure our version numbers all end up as valid numbers 312 self.major, self.minor, self.build, self.subBuild = 313 tonumber( major or 0 ), tonumber( minor or 0 ), tonumber( build or 0 ), tonumber( subBuild or 0 ) 314 315 self.versionNumber = string.format( "%u.%02u.%u.%02u", self.major, self.minor, self.build, self.subBuild ) 316 317 self:_ParseVersionInfo() 318 end, 319 320 --- Using the version number, determines the product version 321 _InferProductVersion = function(self) 322 323 local VERSION_LOOKUP_TABLE = { 324 ["^6%.0"] = "6.0", ["^6%.5"] = "6.5", ["^7%.0"] = "7.0", 325 ["^8%.0"] = "2000", ["^9%.0"] = "2005", ["^10%.0"] = "2008", 326 ["^10%.50"] = "2008 R2", ["^11%.0"] = "2012", ["^12%.0"] = "2014", 327 ["^13%.0"] = "2016", ["^14%.0"] = "2017", ["^15%.0"] = "2019" 328 } 329 330 local product = "" 331 332 for m, v in pairs(VERSION_LOOKUP_TABLE) do 333 if ( self.versionNumber:match(m) ) then 334 product = v 335 self.brandedVersion = product 336 break 337 end 338 end 339 340 self.productName = ("Microsoft SQL Server %s"):format(product) 341 342 end, 343 344 345 --- Returns a lookup table that maps revision numbers to service pack and 346 -- cumulative update levels for the applicable SQL Server version, 347 -- e.g., {{1913, "RC1"}, {2100, "RTM"}, {2316, "RTMCU1"}, ..., 348 -- {3000, "SP1"}, {3321, "SP1CU1"}, ..., {3368, "SP1CU4"}, ...} 349 _GetSpLookupTable = function(self) 350 351 -- Service pack lookup tables: 352 -- For instances where a revised service pack was released, e.g. 2000 SP3a, 353 -- we will include the build number for the original SP and the build number 354 -- for the revision. However, leaving it like this would make it appear that 355 -- subsequent builds were a patched version of the revision, e.g., a patch 356 -- applied to 2000 SP3 that increased the build number to 780 would get 357 -- displayed as "SP3a+", when it was actually SP3+. To avoid this, we will 358 -- include an additional fake build number that combines the two. 359 -- Source: https://sqlserverbuilds.blogspot.com/ 360 local SP_LOOKUP_TABLE = { 361 ["6.5"] = { 362 {201, "RTM"}, 363 {213, "SP1"}, 364 {240, "SP2"}, 365 {258, "SP3"}, 366 {281, "SP4"}, 367 {415, "SP5"}, 368 {416, "SP5a"}, 369 {417, "SP5/SP5a"}, 370 }, 371 372 ["7.0"] = { 373 {623, "RTM"}, 374 {699, "SP1"}, 375 {842, "SP2"}, 376 {961, "SP3"}, 377 {1063, "SP4"}, 378 }, 379 380 ["2000"] = { 381 {194, "RTM"}, 382 {384, "SP1"}, 383 {532, "SP2"}, 384 {534, "SP2"}, 385 {760, "SP3"}, 386 {766, "SP3a"}, 387 {767, "SP3/SP3a"}, 388 {2039, "SP4"}, 389 }, 390 391 ["2005"] = { 392 {1399, "RTM"}, 393 {2047, "SP1"}, 394 {3042, "SP2"}, 395 {4035, "SP3"}, 396 {5000, "SP4"}, 397 }, 398 399 ["2008"] = { 400 {1600, "RTM"}, 401 {2531, "SP1"}, 402 {4000, "SP2"}, 403 {5500, "SP3"}, 404 {6000, "SP4"}, 405 }, 406 407 ["2008 R2"] = { 408 {1600, "RTM"}, 409 {2500, "SP1"}, 410 {4000, "SP2"}, 411 {6000, "SP3"}, 412 }, 413 414 ["2012"] = { 415 {1103, "CTP1"}, 416 {1440, "CTP3"}, 417 {1750, "RC0"}, 418 {1913, "RC1"}, 419 {2100, "RTM"}, 420 {2316, "RTMCU1"}, 421 {2325, "RTMCU2"}, 422 {2332, "RTMCU3"}, 423 {2383, "RTMCU4"}, 424 {2395, "RTMCU5"}, 425 {2401, "RTMCU6"}, 426 {2405, "RTMCU7"}, 427 {2410, "RTMCU8"}, 428 {2419, "RTMCU9"}, 429 {2420, "RTMCU10"}, 430 {2424, "RTMCU11"}, 431 {3000, "SP1"}, 432 {3321, "SP1CU1"}, 433 {3339, "SP1CU2"}, 434 {3349, "SP1CU3"}, 435 {3368, "SP1CU4"}, 436 {3373, "SP1CU5"}, 437 {3381, "SP1CU6"}, 438 {3393, "SP1CU7"}, 439 {3401, "SP1CU8"}, 440 {3412, "SP1CU9"}, 441 {3431, "SP1CU10"}, 442 {3449, "SP1CU11"}, 443 {3470, "SP1CU12"}, 444 {3482, "SP1CU13"}, 445 {3486, "SP1CU14"}, 446 {3487, "SP1CU15"}, 447 {3492, "SP1CU16"}, 448 {5058, "SP2"}, 449 {5532, "SP2CU1"}, 450 {5548, "SP2CU2"}, 451 {5556, "SP2CU3"}, 452 {5569, "SP2CU4"}, 453 {5582, "SP2CU5"}, 454 {5592, "SP2CU6"}, 455 {5623, "SP2CU7"}, 456 {5634, "SP2CU8"}, 457 {5641, "SP2CU9"}, 458 {5644, "SP2CU10"}, 459 {5646, "SP2CU11"}, 460 {5649, "SP2CU12"}, 461 {5655, "SP2CU13"}, 462 {5657, "SP2CU14"}, 463 {5676, "SP2CU15"}, 464 {5678, "SP2CU16"}, 465 {6020, "SP3"}, 466 {6518, "SP3CU1"}, 467 {6523, "SP3CU2"}, 468 {6537, "SP3CU3"}, 469 {6540, "SP3CU4"}, 470 {6544, "SP3CU5"}, 471 {6567, "SP3CU6"}, 472 {6579, "SP3CU7"}, 473 {6594, "SP3CU8"}, 474 {6598, "SP3CU9"}, 475 {6607, "SP3CU10"}, 476 {7001, "SP4"}, 477 }, 478 479 ["2014"] = { 480 {1524, "CTP2"}, 481 {2000, "RTM"}, 482 {2342, "RTMCU1"}, 483 {2370, "RTMCU2"}, 484 {2402, "RTMCU3"}, 485 {2430, "RTMCU4"}, 486 {2456, "RTMCU5"}, 487 {2480, "RTMCU6"}, 488 {2495, "RTMCU7"}, 489 {2546, "RTMCU8"}, 490 {2553, "RTMCU9"}, 491 {2556, "RTMCU10"}, 492 {2560, "RTMCU11"}, 493 {2564, "RTMCU12"}, 494 {2568, "RTMCU13"}, 495 {2569, "RTMCU14"}, 496 {4100, "SP1"}, 497 {4416, "SP1CU1"}, 498 {4422, "SP1CU2"}, 499 {4427, "SP1CU3"}, 500 {4436, "SP1CU4"}, 501 {4439, "SP1CU5"}, 502 {4449, "SP1CU6"}, 503 {4459, "SP1CU7"}, 504 {4468, "SP1CU8"}, 505 {4474, "SP1CU9"}, 506 {4491, "SP1CU10"}, 507 {4502, "SP1CU11"}, 508 {4511, "SP1CU12"}, 509 {4522, "SP1CU13"}, 510 {5000, "SP2"}, 511 {5511, "SP2CU1"}, 512 {5522, "SP2CU2"}, 513 {5538, "SP2CU3"}, 514 {5540, "SP2CU4"}, 515 {5546, "SP2CU5"}, 516 {5553, "SP2CU6"}, 517 {5556, "SP2CU7"}, 518 {5557, "SP2CU8"}, 519 {5563, "SP2CU9"}, 520 {5571, "SP2CU10"}, 521 {5579, "SP2CU11"}, 522 {5589, "SP2CU12"}, 523 {5590, "SP2CU13"}, 524 {5600, "SP2CU14"}, 525 {5605, "SP2CU15"}, 526 {5626, "SP2CU16"}, 527 {5632, "SP2CU17"}, 528 {5687, "SP2CU18"}, 529 {6024, "SP3"}, 530 {6205, "SP3CU1"}, 531 {6214, "SP3CU2"}, 532 {6259, "SP3CU3"}, 533 {6329, "SP3CU4"}, 534 }, 535 536 ["2016"] = { 537 { 200, "CTP2"}, 538 { 300, "CTP2.1"}, 539 { 407, "CTP2.2"}, 540 { 500, "CTP2.3"}, 541 { 600, "CTP2.4"}, 542 { 700, "CTP3.0"}, 543 { 800, "CTP3.1"}, 544 { 900, "CTP3.2"}, 545 {1000, "CTP3.3"}, 546 {1100, "RC0"}, 547 {1200, "RC1"}, 548 {1300, "RC2"}, 549 {1400, "RC3"}, 550 {1601, "RTM"}, 551 {2149, "RTMCU1"}, 552 {2164, "RTMCU2"}, 553 {2186, "RTMCU3"}, 554 {2193, "RTMCU4"}, 555 {2197, "RTMCU5"}, 556 {2204, "RTMCU6"}, 557 {2210, "RTMCU7"}, 558 {2213, "RTMCU8"}, 559 {2216, "RTMCU9"}, 560 {4001, "SP1"}, 561 {4411, "SP1CU1"}, 562 {4422, "SP1CU2"}, 563 {4435, "SP1CU3"}, 564 {4446, "SP1CU4"}, 565 {4451, "SP1CU5"}, 566 {4457, "SP1CU6"}, 567 {4466, "SP1CU7"}, 568 {4474, "SP1CU8"}, 569 {4502, "SP1CU9"}, 570 {4514, "SP1CU10"}, 571 {4528, "SP1CU11"}, 572 {4541, "SP1CU12"}, 573 {4550, "SP1CU13"}, 574 {4560, "SP1CU14"}, 575 {4574, "SP1CU15"}, 576 {5026, "SP2"}, 577 {5149, "SP2CU1"}, 578 {5153, "SP2CU2"}, 579 {5216, "SP2CU3"}, 580 {5233, "SP2CU4"}, 581 {5264, "SP2CU5"}, 582 {5292, "SP2CU6"}, 583 {5337, "SP2CU7"}, 584 {5426, "SP2CU8"}, 585 {5479, "SP2CU9"}, 586 {5492, "SP2CU10"}, 587 {5598, "SP2CU11"}, 588 {5698, "SP2CU12"}, 589 {5820, "SP2CU13"}, 590 }, 591 592 ["2017"] = { 593 { 1, "CTP1"}, 594 { 100, "CTP1.1"}, 595 { 200, "CTP1.2"}, 596 { 304, "CTP1.3"}, 597 { 405, "CTP1.4"}, 598 { 500, "CTP2.0"}, 599 { 600, "CTP2.1"}, 600 { 800, "RC1"}, 601 { 900, "RC2"}, 602 {1000, "RTM"}, 603 {3006, "CU1"}, 604 {3008, "CU2"}, 605 {3015, "CU3"}, 606 {3022, "CU4"}, 607 {3023, "CU5"}, 608 {3025, "CU6"}, 609 {3026, "CU7"}, 610 {3029, "CU8"}, 611 {3030, "CU9"}, 612 {3037, "CU10"}, 613 {3038, "CU11"}, 614 {3045, "CU12"}, 615 {3048, "CU13"}, 616 {3076, "CU14"}, 617 {3162, "CU15"}, 618 {3223, "CU16"}, 619 {3238, "CU17"}, 620 {3257, "CU18"}, 621 {3281, "CU19"}, 622 {3294, "CU20"}, 623 {3335, "CU21"}, 624 }, 625 626 ["2019"] = { 627 {1000, "CTP2.0"}, 628 {1100, "CTP2.1"}, 629 {1200, "CTP2.2"}, 630 {1300, "CTP2.3"}, 631 {1400, "CTP2.4"}, 632 {1500, "CTP2.5"}, 633 {1600, "CTP3.0"}, 634 {1700, "CTP3.1"}, 635 {1800, "CTP3.2"}, 636 {1900, "RC1"}, 637 {2000, "RTM"}, 638 {2070, "GDR1"}, 639 {4003, "CU1"}, 640 {4013, "CU2"}, 641 {4023, "CU3"}, 642 {4033, "CU4"}, 643 {4043, "CU5"}, 644 }, 645 } 646 647 648 if ( not self.brandedVersion ) then 649 self:_InferProductVersion() 650 end 651 652 local spLookupTable = SP_LOOKUP_TABLE[self.brandedVersion] 653 stdnse.debug1("brandedVersion: %s, #lookup: %d", self.brandedVersion, spLookupTable and #spLookupTable or 0) 654 655 return spLookupTable 656 657 end, 658 659 660 --- Processes version data to determine (if possible) the product version, 661 -- service pack level and patch status. 662 _ParseVersionInfo = function(self) 663 664 local spLookupTable = self:_GetSpLookupTable() 665 666 if spLookupTable then 667 668 local spLookupItr = 0 669 -- Loop through the service pack levels until we find one whose revision 670 -- number is the same as or lower than our revision number. 671 while spLookupItr < #spLookupTable do 672 spLookupItr = spLookupItr + 1 673 674 if (spLookupTable[ spLookupItr ][1] == self.build ) then 675 spLookupItr = spLookupItr 676 break 677 elseif (spLookupTable[ spLookupItr ][1] > self.build ) then 678 -- The target revision number is lower than the first release 679 if spLookupItr == 1 then 680 self.servicePackLevel = "Pre-RTM" 681 else 682 -- we went too far - it's the previous SP, but with patches applied 683 spLookupItr = spLookupItr - 1 684 end 685 break 686 end 687 end 688 689 -- Now that we've identified the proper service pack level: 690 if self.servicePackLevel ~= "Pre-RTM" then 691 self.servicePackLevel = spLookupTable[ spLookupItr ][2] 692 693 if ( spLookupTable[ spLookupItr ][1] == self.build ) then 694 self.patched = false 695 else 696 self.patched = true 697 end 698 end 699 700 -- Clean up some of our inferences. If the source of our revision number 701 -- was the SSRP (SQL Server Browser) response, we need to recognize its 702 -- limitations: 703 -- * Versions of SQL Server prior to 2005 are reported with the RTM build 704 -- number, regardless of the actual version (e.g. SQL Server 2000 is 705 -- always 8.00.194). 706 -- * Versions of SQL Server starting with 2005 (and going through at least 707 -- 2008) do better but are still only reported with the build number as 708 -- of the last service pack (e.g. SQL Server 2005 SP3 with patches is 709 -- still reported as 9.00.4035.00). 710 if ( self.source == "SSRP" ) then 711 self.patched = nil 712 713 if ( self.major <= 8 ) then 714 self.servicePackLevel = nil 715 end 716 end 717 end 718 719 return true 720 end, 721 722 --- 723 ToString = function(self) 724 local friendlyVersion = strbuf.new() 725 if self.productName then 726 friendlyVersion:concatbuf( self.productName ) 727 if self.servicePackLevel then 728 friendlyVersion:concatbuf( " " ) 729 friendlyVersion:concatbuf( self.servicePackLevel ) 730 end 731 if self.patched then 732 friendlyVersion:concatbuf( "+" ) 733 end 734 end 735 736 return friendlyVersion:dump() 737 end, 738 739 --- Uses the information in this SqlServerVersionInformation object to 740 -- populate the version information in an Nmap port table for a SQL Server 741 -- TCP listener. 742 -- 743 -- @param self A SqlServerVersionInformation object 744 -- @param port An Nmap port table corresponding to the instance 745 PopulateNmapPortVersion = function(self, port) 746 747 port.service = "ms-sql-s" 748 port.version = port.version or {} 749 port.version.name = "ms-sql-s" 750 port.version.product = self.productName 751 752 local versionString = strbuf.new() 753 if self.source ~= "SSRP" then 754 versionString:concatbuf( self.versionNumber ) 755 if self.servicePackLevel then 756 versionString:concatbuf( "; " ) 757 versionString:concatbuf( self.servicePackLevel ) 758 end 759 if self.patched then 760 versionString:concatbuf( "+" ) 761 end 762 port.version.version = versionString:dump() 763 end 764 765 return port 766 end, 767} 768 769 770-- ************************************* 771-- SSRP (SQL Server Resolution Protocol) 772-- ************************************* 773SSRP = 774{ 775 PORT = { number = 1434, protocol = "udp" }, 776 DEBUG_ID = "MSSQL-SSRP", 777 778 MESSAGE_TYPE = 779 { 780 ClientBroadcast = 0x02, 781 ClientUnicast = 0x03, 782 ClientUnicastInstance = 0x04, 783 ClientUnicastDAC = 0x0F, 784 ServerResponse = 0x05, 785 }, 786 787 --- Parses an SSRP string and returns a table containing one or more 788 -- SqlServerInstanceInfo objects created from the parsed string. 789 _ParseSsrpString = function( host, ssrpString ) 790 -- It would seem easier to just capture (.-;;) repeatedly, since 791 -- each instance ends with ";;", but ";;" can also occur within the 792 -- data, signifying an empty field (e.g. "...bv;;@COMPNAME;;tcp;1433;;..."). 793 -- So, instead, we'll split up the string ahead of time. 794 -- See the SSRP specification for more details. 795 796 local instanceStrings = {} 797 local firstInstanceEnd, instanceString 798 repeat 799 firstInstanceEnd = ssrpString:find( ";ServerName;(.-);InstanceName;(.-);IsClustered;(.-);" ) 800 if firstInstanceEnd then 801 instanceString = ssrpString:sub( 1, firstInstanceEnd ) 802 ssrpString = ssrpString:sub( firstInstanceEnd + 1 ) 803 else 804 instanceString = ssrpString 805 end 806 807 table.insert( instanceStrings, instanceString ) 808 until (not firstInstanceEnd) 809 stdnse.debug2("%s: SSRP Substrings:\n %s", SSRP.DEBUG_ID, table.concat(instanceStrings , "\n ") ) 810 811 local instances = {} 812 for _, instanceString in ipairs( instanceStrings ) do 813 local instance = SqlServerInstanceInfo:new() 814 local version = SqlServerVersionInfo:new() 815 instance.version = version 816 817 instance.host = host 818 instance.serverName = instanceString:match( "ServerName;(.-);") 819 instance.instanceName = instanceString:match( "InstanceName;(.-);") 820 instance:SetIsClustered( instanceString:match( "IsClustered;(.-);") ) 821 version:SetVersionNumber( instanceString:match( "Version;(.-);"), "SSRP" ) 822 823 local tcpPort = tonumber( instanceString:match( ";tcp;(.-);") ) 824 if tcpPort then instance.port = {number = tcpPort, protocol = "tcp"} end 825 826 local pipeName = instanceString:match( ";np;(.-);") 827 local status, pipeSubPath = namedpipes.get_pipe_subpath( pipeName ) 828 if status then 829 pipeName = namedpipes.make_pipe_name( host.ip, pipeSubPath ) 830 elseif pipeName ~= nil then 831 stdnse.debug1("%s: Invalid pipe name:\n%s", SSRP.DEBUG_ID, pipeName ) 832 end 833 instance.pipeName = pipeName 834 835 table.insert( instances, instance ) 836 end 837 838 return instances 839 end, 840 841 --- 842 _ProcessResponse = function( host, responseData ) 843 local instances 844 845 local pos, messageType, dataLength = 1, nil, nil 846 messageType, dataLength, pos = string.unpack("<BI2", responseData, 1) 847 -- extract the response data (i.e. everything after the 3-byte header) 848 responseData = responseData:sub(4) 849 stdnse.debug2("%s: SSRP Data: %s", SSRP.DEBUG_ID, responseData ) 850 if ( messageType ~= SSRP.MESSAGE_TYPE.ServerResponse or 851 dataLength ~= responseData:len() ) then 852 853 stdnse.debug2("%s: Invalid SSRP response. Type: 0x%02x, Length: %d, Actual length: %d", 854 SSRP.DEBUG_ID, messageType, dataLength, responseData:len() ) 855 else 856 instances = SSRP._ParseSsrpString( host, responseData ) 857 end 858 859 return instances 860 end, 861 862 --- Attempts to retrieve information about SQL Server instances by querying 863 -- the SQL Server Browser service on a host. 864 -- 865 -- @param host A host table for the target host 866 -- @param port (Optional) A port table for the target SQL Server Browser service 867 -- @return (status, result) If status is true, result is a table of 868 -- SqlServerInstanceInfo objects. If status is false, result is an 869 -- error message. 870 DiscoverInstances = function( host, port ) 871 port = port or SSRP.PORT 872 873 if ( SCANNED_PORTS_ONLY and nmap.get_port_state( host, port ) == nil ) then 874 stdnse.debug2("%s: Discovery disallowed: scanned-ports-only is set and port %d was not scanned", SSRP.DEBUG_ID, port.number ) 875 return false, "Discovery disallowed: scanned-ports-only" 876 end 877 878 local socket = nmap.new_socket("udp") 879 socket:set_timeout(5000) 880 881 if ( port.number ~= SSRP.PORT.number ) then 882 stdnse.debug1("%s: DiscoverInstances() called with non-standard port (%d)", SSRP.DEBUG_ID, port.number ) 883 end 884 885 local status, err = socket:connect( host, port ) 886 if ( not(status) ) then return false, err end 887 status, err = socket:send( string.pack( "B", SSRP.MESSAGE_TYPE.ClientUnicast ) ) 888 if ( not(status) ) then return false, err end 889 890 local responseData, instances_host 891 status, responseData = socket:receive() 892 if ( not(status) ) then return false, responseData 893 else 894 instances_host = SSRP._ProcessResponse( host, responseData ) 895 end 896 socket:close() 897 898 return status, instances_host 899 end, 900 901 902 --- Attempts to retrieve information about SQL Server instances by querying 903 -- the SQL Server Browser service on a broadcast domain. 904 -- 905 -- @param host A host table for the broadcast specification 906 -- @param port (Optional) A port table for the target SQL Server Browser service 907 -- @return (status, result) If status is true, result is a table of 908 -- tables containing SqlServerInstanceInfo objects. The top-level table 909 -- is indexed by IP address. If status is false, result is an 910 -- error message. 911 DiscoverInstances_Broadcast = function( host, port ) 912 port = port or SSRP.PORT 913 914 local socket = nmap.new_socket("udp") 915 socket:set_timeout(5000) 916 local instances_all = {} 917 918 if ( port.number ~= SSRP.PORT.number ) then 919 stdnse.debug1("%S: DiscoverInstances_Broadcast() called with non-standard port (%d)", SSRP.DEBUG_ID, port.number ) 920 end 921 922 local status, err = socket:sendto(host, port, string.pack( "B", SSRP.MESSAGE_TYPE.ClientBroadcast )) 923 if ( not(status) ) then return false, err end 924 925 while ( status ) do 926 local responseData 927 status, responseData = socket:receive() 928 if ( status ) then 929 local remoteIp, _ 930 status, _, _, remoteIp, _ = socket:get_info() 931 local instances_host = SSRP._ProcessResponse( {ip = remoteIp, name = ""}, responseData ) 932 instances_all[ remoteIp ] = instances_host 933 end 934 end 935 socket:close() 936 937 return true, instances_all 938 end, 939} 940 941 942 943-- ************************* 944-- TDS (Tabular Data Stream) 945-- ************************* 946 947-- TDS packet types 948PacketType = 949{ 950 Query = 0x01, 951 Response = 0x04, 952 Login = 0x10, 953 NTAuthentication = 0x11, 954 PreLogin = 0x12, 955} 956 957-- TDS response token types 958TokenType = 959{ 960 ReturnStatus = 0x79, 961 TDS7Results = 0x81, 962 ErrorMessage = 0xAA, 963 InformationMessage = 0xAB, 964 LoginAcknowledgement = 0xAD, 965 Row = 0xD1, 966 OrderBy = 0xA9, 967 EnvironmentChange = 0xE3, 968 NTLMSSP_CHALLENGE = 0xed, 969 Done = 0xFD, 970 DoneProc = 0xFE, 971 DoneInProc = 0xFF, 972} 973 974-- SQL Server/Sybase data types 975DataTypes = 976{ 977 SQLTEXT = 0x23, 978 GUIDTYPE = 0x24, 979 SYBINTN = 0x26, 980 SYBINT2 = 0x34, 981 SYBINT4 = 0x38, 982 SYBDATETIME = 0x3D, 983 NTEXTTYPE = 0x63, 984 BITNTYPE = 0x68, 985 DECIMALNTYPE = 0x6A, 986 NUMERICNTYPE = 0x6C, 987 FLTNTYPE = 0x6D, 988 MONEYNTYPE = 0x6E, 989 SYBDATETIMN = 0x6F, 990 XSYBVARBINARY = 0xA5, 991 XSYBVARCHAR = 0xA7, 992 BIGBINARYTYPE = 0xAD, 993 BIGCHARTYPE = 0xAF, 994 XSYBNVARCHAR = 0xE7, 995 SQLNCHAR = 0xEF, 996} 997 998-- SQL Server login error codes 999-- See http://msdn.microsoft.com/en-us/library/ms131024.aspx 1000LoginErrorType = 1001{ 1002 AccountLockedOut = 15113, 1003 NotAssociatedWithTrustedConnection = 18452, -- This probably means that the server is set for Windows authentication only 1004 InvalidUsernameOrPassword = 18456, 1005 PasswordChangeFailed_PasswordNotAllowed = 18463, 1006 PasswordChangeFailed_PasswordTooShort = 18464, 1007 PasswordChangeFailed_PasswordTooLong = 18465, 1008 PasswordChangeFailed_PasswordNotComplex = 18466, 1009 PasswordChangeFailed_PasswordFilter = 18467, 1010 PasswordChangeFailed_UnexpectedError = 18468, 1011 PasswordExpired = 18487, 1012 PasswordMustChange = 18488, 1013} 1014 1015LoginErrorMessage = {} 1016for i, v in pairs(LoginErrorType) do 1017 LoginErrorMessage[v] = i 1018end 1019 1020-- "static" ColumnInfo parser class 1021ColumnInfo = 1022{ 1023 1024 Parse = 1025 { 1026 1027 [DataTypes.SQLTEXT] = function( data, pos ) 1028 local colinfo = {} 1029 local tmp 1030 1031 colinfo.unknown, colinfo.codepage, colinfo.flags, colinfo.charset, pos = string.unpack("<I4I2I2B", data, pos ) 1032 1033 colinfo.tablenamelen, pos = string.unpack("<i2", data, pos ) 1034 colinfo.tablename, pos = string.unpack("c" .. (colinfo.tablenamelen * 2), data, pos) 1035 colinfo.msglen, pos = string.unpack("<B", data, pos ) 1036 tmp, pos = string.unpack("c" .. (colinfo.msglen * 2), data, pos) 1037 1038 colinfo.text = unicode.utf16to8(tmp) 1039 1040 return pos, colinfo 1041 end, 1042 1043 [DataTypes.GUIDTYPE] = function( data, pos ) 1044 return ColumnInfo.Parse[DataTypes.SYBINTN](data, pos) 1045 end, 1046 1047 [DataTypes.SYBINTN] = function( data, pos ) 1048 local colinfo = {} 1049 local tmp 1050 1051 colinfo.unknown, colinfo.msglen, pos = string.unpack("<BB", data, pos) 1052 tmp, pos = string.unpack("c" .. (colinfo.msglen * 2), data, pos ) 1053 colinfo.text = unicode.utf16to8(tmp) 1054 1055 return pos, colinfo 1056 end, 1057 1058 [DataTypes.SYBINT2] = function( data, pos ) 1059 return ColumnInfo.Parse[DataTypes.SYBDATETIME](data, pos) 1060 end, 1061 1062 [DataTypes.SYBINT4] = function( data, pos ) 1063 return ColumnInfo.Parse[DataTypes.SYBDATETIME](data, pos) 1064 end, 1065 1066 [DataTypes.SYBDATETIME] = function( data, pos ) 1067 local colinfo = {} 1068 local tmp 1069 1070 colinfo.msglen, pos = string.unpack("B", data, pos) 1071 tmp, pos = string.unpack("c" .. (colinfo.msglen * 2), data, pos ) 1072 colinfo.text = unicode.utf16to8(tmp) 1073 1074 return pos, colinfo 1075 end, 1076 1077 [DataTypes.NTEXTTYPE] = function( data, pos ) 1078 return ColumnInfo.Parse[DataTypes.SQLTEXT](data, pos) 1079 end, 1080 1081 [DataTypes.BITNTYPE] = function( data, pos ) 1082 return ColumnInfo.Parse[DataTypes.SYBINTN](data, pos) 1083 end, 1084 1085 [DataTypes.DECIMALNTYPE] = function( data, pos ) 1086 local colinfo = {} 1087 local tmp 1088 1089 colinfo.unknown, colinfo.precision, colinfo.scale, pos = string.unpack("<BBB", data, pos) 1090 colinfo.msglen, pos = string.unpack("<B",data,pos) 1091 tmp, pos = string.unpack("c" .. (colinfo.msglen * 2), data, pos ) 1092 colinfo.text = unicode.utf16to8(tmp) 1093 1094 return pos, colinfo 1095 end, 1096 1097 [DataTypes.NUMERICNTYPE] = function( data, pos ) 1098 return ColumnInfo.Parse[DataTypes.DECIMALNTYPE](data, pos) 1099 end, 1100 1101 [DataTypes.FLTNTYPE] = function( data, pos ) 1102 return ColumnInfo.Parse[DataTypes.SYBINTN](data, pos) 1103 end, 1104 1105 [DataTypes.MONEYNTYPE] = function( data, pos ) 1106 return ColumnInfo.Parse[DataTypes.SYBINTN](data, pos) 1107 end, 1108 1109 [DataTypes.SYBDATETIMN] = function( data, pos ) 1110 return ColumnInfo.Parse[DataTypes.SYBINTN](data, pos) 1111 end, 1112 1113 [DataTypes.XSYBVARBINARY] = function( data, pos ) 1114 local colinfo = {} 1115 local tmp 1116 1117 colinfo.lts, colinfo.msglen, pos = string.unpack("<I2B", data, pos) 1118 tmp, pos = string.unpack("c" .. (colinfo.msglen * 2), data, pos ) 1119 colinfo.text = unicode.utf16to8(tmp) 1120 1121 return pos, colinfo 1122 end, 1123 1124 [DataTypes.XSYBVARCHAR] = function( data, pos ) 1125 return ColumnInfo.Parse[DataTypes.XSYBNVARCHAR](data, pos) 1126 end, 1127 1128 [DataTypes.BIGBINARYTYPE] = function( data, pos ) 1129 return ColumnInfo.Parse[DataTypes.XSYBVARBINARY](data, pos) 1130 end, 1131 1132 [DataTypes.BIGCHARTYPE] = function( data, pos ) 1133 return ColumnInfo.Parse[DataTypes.XSYBNVARCHAR](data, pos) 1134 end, 1135 1136 [DataTypes.XSYBNVARCHAR] = function( data, pos ) 1137 local colinfo = {} 1138 local tmp 1139 1140 colinfo.lts, colinfo.codepage, colinfo.flags, colinfo.charset, 1141 colinfo.msglen, pos = string.unpack("<I2I2I2BB", data, pos ) 1142 tmp, pos = string.unpack("c" .. (colinfo.msglen * 2), data, pos) 1143 colinfo.text = unicode.utf16to8(tmp) 1144 1145 return pos, colinfo 1146 end, 1147 1148 [DataTypes.SQLNCHAR] = function( data, pos ) 1149 return ColumnInfo.Parse[DataTypes.XSYBNVARCHAR](data, pos) 1150 end, 1151 1152 } 1153 1154} 1155 1156-- "static" ColumnData parser class 1157ColumnData = 1158{ 1159 Parse = { 1160 1161 [DataTypes.SQLTEXT] = function( data, pos ) 1162 local len, coldata 1163 1164 -- The first len value is the size of the meta data block 1165 -- for non-null values this seems to be 0x10 / 16 bytes 1166 len, pos = string.unpack( "<B", data, pos ) 1167 1168 if ( len == 0 ) then 1169 return pos, 'Null' 1170 end 1171 1172 -- Skip over the text update time and date values, we don't need them 1173 -- We may come back add parsing for this information. 1174 pos = pos + len 1175 1176 -- skip a label, should be 'dummyTS' 1177 pos = pos + 8 1178 1179 -- extract the actual data 1180 coldata, pos = string.unpack( "<s4", data, pos ) 1181 1182 return pos, coldata 1183 end, 1184 1185 [DataTypes.GUIDTYPE] = function( data, pos ) 1186 local len, coldata, index, nextdata 1187 local hex = {} 1188 len, pos = string.unpack("B", data, pos) 1189 1190 if ( len == 0 ) then 1191 return pos, 'Null' 1192 1193 elseif ( len == 16 ) then 1194 -- Mixed-endian; first 3 parts are little-endian, next 2 are big-endian 1195 local A, B, C, D, E, pos = string.unpack("<I4I2I2>c2c6", data, pos) 1196 coldata = ("%08x-%04x-%04x-%s-%s"):format(A, B, C, stdnse.tohex(D), stdnse.tohex(E)) 1197 else 1198 stdnse.debug1("Unhandled length (%d) for GUIDTYPE", len) 1199 return pos + len, 'Unsupported Data' 1200 end 1201 1202 return pos, coldata 1203 end, 1204 1205 [DataTypes.SYBINTN] = function( data, pos ) 1206 local len, num 1207 len, pos = string.unpack("B", data, pos) 1208 1209 if ( len == 0 ) then 1210 return pos, 'Null' 1211 elseif ( len <= 16 ) then 1212 local v, pos = string.unpack("<i" .. len, data, pos) 1213 return pos, v 1214 else 1215 return -1, ("Unhandled length (%d) for SYBINTN"):format(len) 1216 end 1217 1218 return -1, "Error" 1219 end, 1220 1221 [DataTypes.SYBINT2] = function( data, pos ) 1222 local num 1223 num, pos = string.unpack("<I2", data, pos) 1224 1225 return pos, num 1226 end, 1227 1228 [DataTypes.SYBINT4] = function( data, pos ) 1229 local num 1230 num, pos = string.unpack("<I4", data, pos) 1231 1232 return pos, num 1233 end, 1234 1235 [DataTypes.SYBDATETIME] = function( data, pos ) 1236 local hi, lo 1237 1238 hi, lo, pos = string.unpack("<i4I4", data, pos) 1239 1240 local result_seconds = (hi*24*60*60) + (lo/300) 1241 1242 local result = datetime.format_timestamp(tds_offset_seconds + result_seconds) 1243 return pos, result 1244 end, 1245 1246 [DataTypes.NTEXTTYPE] = function( data, pos ) 1247 local len, coldata 1248 1249 -- The first len value is the size of the meta data block 1250 len, pos = string.unpack( "<B", data, pos ) 1251 1252 if ( len == 0 ) then 1253 return pos, 'Null' 1254 end 1255 1256 -- Skip over the text update time and date values, we don't need them 1257 -- We may come back add parsing for this information. 1258 pos = pos + len 1259 1260 -- skip a label, should be 'dummyTS' 1261 pos = pos + 8 1262 1263 -- extract the actual data 1264 coldata, pos = string.unpack( "<s4", data, pos ) 1265 1266 return pos, unicode.utf16to8(coldata) 1267 end, 1268 1269 [DataTypes.BITNTYPE] = function( data, pos ) 1270 return ColumnData.Parse[DataTypes.SYBINTN](data, pos) 1271 end, 1272 1273 [DataTypes.DECIMALNTYPE] = function( precision, scale, data, pos ) 1274 local len, sign, format_string, coldata 1275 1276 len, pos = string.unpack("<B", data, pos) 1277 1278 if ( len == 0 ) then 1279 return pos, 'Null' 1280 end 1281 1282 sign, pos = string.unpack("<B", data, pos) 1283 1284 -- subtract 1 from data len to account for sign byte 1285 len = len - 1 1286 1287 if ( len > 0 and len <= 16 ) then 1288 coldata, pos = string.unpack("<I" .. len, data, pos) 1289 else 1290 stdnse.debug1("Unhandled length (%d) for DECIMALNTYPE", len) 1291 return pos + len, 'Unsupported Data' 1292 end 1293 1294 if ( sign == 0 ) then 1295 coldata = coldata * -1 1296 end 1297 1298 coldata = coldata * (10^-scale) 1299 -- format the return information to reduce truncation by lua 1300 format_string = string.format("%%.%if", scale) 1301 coldata = string.format(format_string,coldata) 1302 1303 return pos, coldata 1304 end, 1305 1306 [DataTypes.NUMERICNTYPE] = function( precision, scale, data, pos ) 1307 return ColumnData.Parse[DataTypes.DECIMALNTYPE]( precision, scale, data, pos ) 1308 end, 1309 1310 [DataTypes.BITNTYPE] = function( data, pos ) 1311 return ColumnData.Parse[DataTypes.SYBINTN](data, pos) 1312 end, 1313 1314 [DataTypes.NTEXTTYPE] = function( data, pos ) 1315 local len, coldata 1316 1317 -- The first len value is the size of the meta data block 1318 len, pos = string.unpack( "<B", data, pos ) 1319 1320 if ( len == 0 ) then 1321 return pos, 'Null' 1322 end 1323 1324 -- Skip over the text update time and date values, we don't need them 1325 -- We may come back add parsing for this information. 1326 pos = pos + len 1327 1328 -- skip a label, should be 'dummyTS' 1329 pos = pos + 8 1330 1331 -- extract the actual data 1332 coldata, pos = string.unpack( "<s4", data, pos ) 1333 1334 return pos, unicode.utf16to8(coldata) 1335 end, 1336 1337 [DataTypes.FLTNTYPE] = function( data, pos ) 1338 local len, coldata 1339 len, pos = string.unpack("<B", data, pos) 1340 1341 if ( len == 0 ) then 1342 return pos, 'Null' 1343 elseif ( len == 4 ) then 1344 coldata, pos = string.unpack("<f", data, pos) 1345 elseif ( len == 8 ) then 1346 coldata, pos = string.unpack("<d", data, pos) 1347 end 1348 1349 return pos, coldata 1350 end, 1351 1352 [DataTypes.MONEYNTYPE] = function( data, pos ) 1353 local len, value, coldata, hi, lo 1354 len, pos = string.unpack("B", data, pos) 1355 1356 if ( len == 0 ) then 1357 return pos, 'Null' 1358 elseif ( len == 4 ) then 1359 --type smallmoney 1360 value, pos = string.unpack("<i4", data, pos) 1361 elseif ( len == 8 ) then 1362 -- type money 1363 hi, lo, pos = string.unpack("<I4I4", data, pos) 1364 value = ( hi * 0x100000000 ) + lo 1365 else 1366 return -1, ("Unhandled length (%d) for MONEYNTYPE"):format(len) 1367 end 1368 1369 -- the datatype allows for 4 decimal places after the period to support various currency types. 1370 -- forcing to string to avoid truncation 1371 coldata = string.format("%.4f",value/10000) 1372 1373 return pos, coldata 1374 end, 1375 1376 [DataTypes.SYBDATETIMN] = function( data, pos ) 1377 local len, coldata 1378 1379 len, pos = string.unpack( "<B", data, pos ) 1380 1381 if ( len == 0 ) then 1382 return pos, 'Null' 1383 elseif ( len == 4 ) then 1384 -- format is smalldatetime 1385 local days, mins 1386 days, mins, pos = string.unpack("<I2I2", data, pos) 1387 1388 local result_seconds = (days*24*60*60) + (mins*60) 1389 coldata = datetime.format_timestamp(tds_offset_seconds + result_seconds) 1390 1391 return pos,coldata 1392 1393 elseif ( len == 8 ) then 1394 -- format is datetime 1395 return ColumnData.Parse[DataTypes.SYBDATETIME](data, pos) 1396 else 1397 return -1, ("Unhandled length (%d) for SYBDATETIMN"):format(len) 1398 end 1399 1400 end, 1401 1402 [DataTypes.XSYBVARBINARY] = function( data, pos ) 1403 local len, coldata 1404 1405 len, pos = string.unpack( "<I2", data, pos ) 1406 1407 if ( len == 65535 ) then 1408 return pos, 'Null' 1409 else 1410 coldata, pos = string.unpack( "c"..len, data, pos ) 1411 return pos, "0x" .. stdnse.tohex(coldata) 1412 end 1413 1414 return -1, "Error" 1415 end, 1416 1417 [DataTypes.XSYBVARCHAR] = function( data, pos ) 1418 local len, coldata 1419 1420 len, pos = string.unpack( "<I2", data, pos ) 1421 if ( len == 65535 ) then 1422 return pos, 'Null' 1423 end 1424 1425 coldata, pos = string.unpack( "c"..len, data, pos ) 1426 1427 return pos, coldata 1428 end, 1429 1430 [DataTypes.BIGBINARYTYPE] = function( data, pos ) 1431 return ColumnData.Parse[DataTypes.XSYBVARBINARY](data, pos) 1432 end, 1433 1434 [DataTypes.BIGCHARTYPE] = function( data, pos ) 1435 return ColumnData.Parse[DataTypes.XSYBVARCHAR](data, pos) 1436 end, 1437 1438 [DataTypes.XSYBNVARCHAR] = function( data, pos ) 1439 local len, coldata 1440 1441 len, pos = string.unpack( "<I2", data, pos ) 1442 if ( len == 65535 ) then 1443 return pos, 'Null' 1444 end 1445 coldata, pos = string.unpack( "c"..len, data, pos ) 1446 1447 return pos, unicode.utf16to8(coldata) 1448 end, 1449 1450 [DataTypes.SQLNCHAR] = function( data, pos ) 1451 return ColumnData.Parse[DataTypes.XSYBNVARCHAR](data, pos) 1452 end, 1453 1454 } 1455} 1456 1457-- "static" Token parser class 1458Token = 1459{ 1460 1461 Parse = { 1462 --- Parse error message tokens 1463 -- 1464 -- @param data string containing "raw" data 1465 -- @param pos number containing offset into data 1466 -- @return pos number containing new offset after parse 1467 -- @return token table containing token specific fields 1468 [TokenType.ErrorMessage] = function( data, pos ) 1469 local token = {} 1470 local tmp 1471 1472 token.type = TokenType.ErrorMessage 1473 token.size, token.errno, token.state, token.severity, token.errlen, pos = string.unpack( "<I2I4BBI2", data, pos ) 1474 tmp, pos = string.unpack("c" .. (token.errlen * 2), data, pos ) 1475 token.error = unicode.utf16to8(tmp) 1476 token.srvlen, pos = string.unpack("B", data, pos) 1477 tmp, pos = string.unpack("c" .. (token.srvlen * 2), data, pos ) 1478 token.server = unicode.utf16to8(tmp) 1479 token.proclen, pos = string.unpack("B", data, pos) 1480 tmp, pos = string.unpack("c" .. (token.proclen * 2), data, pos ) 1481 token.proc = unicode.utf16to8(tmp) 1482 token.lineno, pos = string.unpack("<I2", data, pos) 1483 1484 return pos, token 1485 end, 1486 1487 --- Parse environment change tokens 1488 -- (This function is not implemented and simply moves the pos offset) 1489 -- 1490 -- @param data string containing "raw" data 1491 -- @param pos number containing offset into data 1492 -- @return pos number containing new offset after parse 1493 -- @return token table containing token specific fields 1494 [TokenType.EnvironmentChange] = function( data, pos ) 1495 local token = {} 1496 local tmp 1497 1498 token.type = TokenType.EnvironmentChange 1499 token.size, pos = string.unpack("<I2", data, pos) 1500 1501 return pos + token.size, token 1502 end, 1503 1504 --- Parse information message tokens 1505 -- 1506 -- @param data string containing "raw" data 1507 -- @param pos number containing offset into data 1508 -- @return pos number containing new offset after parse 1509 -- @return token table containing token specific fields 1510 [TokenType.InformationMessage] = function( data, pos ) 1511 local pos, token = Token.Parse[TokenType.ErrorMessage]( data, pos ) 1512 token.type = TokenType.InformationMessage 1513 return pos, token 1514 end, 1515 1516 --- Parse login acknowledgment tokens 1517 -- 1518 -- @param data string containing "raw" data 1519 -- @param pos number containing offset into data 1520 -- @return pos number containing new offset after parse 1521 -- @return token table containing token specific fields 1522 [TokenType.LoginAcknowledgement] = function( data, pos ) 1523 local token = {} 1524 local _ 1525 1526 token.type = TokenType.LoginAcknowledgement 1527 token.size, _, _, _, _, token.textlen, pos = string.unpack( "<I2BBBI2B", data, pos ) 1528 token.text, pos = string.unpack("c" .. token.textlen * 2, data, pos) 1529 token.version, pos = string.unpack("<I4", data, pos ) 1530 1531 return pos, token 1532 end, 1533 1534 --- Parse done tokens 1535 -- 1536 -- @param data string containing "raw" data 1537 -- @param pos number containing offset into data 1538 -- @return pos number containing new offset after parse 1539 -- @return token table containing token specific fields 1540 [TokenType.Done] = function( data, pos ) 1541 local token = {} 1542 1543 token.type = TokenType.Done 1544 token.flags, token.operation, token.rowcount, pos = string.unpack( "<I2I2I4", data, pos ) 1545 1546 return pos, token 1547 end, 1548 1549 --- Parses a DoneProc token received after executing a SP 1550 -- 1551 -- @param data string containing "raw" data 1552 -- @param pos number containing offset into data 1553 -- @return pos number containing new offset after parse 1554 -- @return token table containing token specific fields 1555 [TokenType.DoneProc] = function( data, pos ) 1556 local token 1557 pos, token = Token.Parse[TokenType.Done]( data, pos ) 1558 token.type = TokenType.DoneProc 1559 1560 return pos, token 1561 end, 1562 1563 1564 --- Parses a DoneInProc token received after executing a SP 1565 -- 1566 -- @param data string containing "raw" data 1567 -- @param pos number containing offset into data 1568 -- @return pos number containing new offset after parse 1569 -- @return token table containing token specific fields 1570 [TokenType.DoneInProc] = function( data, pos ) 1571 local token 1572 pos, token = Token.Parse[TokenType.Done]( data, pos ) 1573 token.type = TokenType.DoneInProc 1574 1575 return pos, token 1576 end, 1577 1578 --- Parses a ReturnStatus token 1579 -- 1580 -- @param data string containing "raw" data 1581 -- @param pos number containing offset into data 1582 -- @return pos number containing new offset after parse 1583 -- @return token table containing token specific fields 1584 [TokenType.ReturnStatus] = function( data, pos ) 1585 local token = {} 1586 1587 token.value, pos = string.unpack("<i4", data, pos) 1588 token.type = TokenType.ReturnStatus 1589 return pos, token 1590 end, 1591 1592 --- Parses a OrderBy token 1593 -- 1594 -- @param data string containing "raw" data 1595 -- @param pos number containing offset into data 1596 -- @return pos number containing new offset after parse 1597 -- @return token table containing token specific fields 1598 [TokenType.OrderBy] = function( data, pos ) 1599 local token = {} 1600 1601 token.size, pos = string.unpack("<I2", data, pos) 1602 token.type = TokenType.OrderBy 1603 return pos + token.size, token 1604 end, 1605 1606 1607 --- Parse TDS result tokens 1608 -- 1609 -- @param data string containing "raw" data 1610 -- @param pos number containing offset into data 1611 -- @return pos number containing new offset after parse 1612 -- @return token table containing token specific fields 1613 [TokenType.TDS7Results] = function( data, pos ) 1614 local token = {} 1615 local _ 1616 1617 token.type = TokenType.TDS7Results 1618 token.count, pos = string.unpack( "<I2", data, pos ) 1619 token.colinfo = {} 1620 1621 for i=1, token.count do 1622 local colinfo = {} 1623 local usertype, flags, ttype 1624 1625 usertype, flags, ttype, pos = string.unpack("<I2I2B", data, pos ) 1626 if ( not(ColumnInfo.Parse[ttype]) ) then 1627 return -1, ("Unhandled data type: 0x%X"):format(ttype) 1628 end 1629 1630 pos, colinfo = ColumnInfo.Parse[ttype]( data, pos ) 1631 colinfo.usertype = usertype 1632 colinfo.flags = flags 1633 colinfo.type = ttype 1634 1635 table.insert( token.colinfo, colinfo ) 1636 end 1637 return pos, token 1638 end, 1639 1640 1641 [TokenType.NTLMSSP_CHALLENGE] = function(data, pos) 1642 local len, ntlmssp, msgtype, pos = string.unpack("<I2c8I4", data, pos) 1643 local NTLMSSP_CHALLENGE = 2 1644 1645 if ( ntlmssp ~= "NTLMSSP\0" or msgtype ~= NTLMSSP_CHALLENGE ) then 1646 return -1, "Failed to process NTLMSSP Challenge" 1647 end 1648 1649 local ntlm_challenge = data:sub( 28, 35 ) 1650 pos = pos + len - 13 1651 return pos, ntlm_challenge 1652 end, 1653 }, 1654 1655 --- Parses the first token at positions pos 1656 -- 1657 -- @param data string containing "raw" data 1658 -- @param pos number containing offset into data 1659 -- @return pos number containing new offset after parse or -1 on error 1660 -- @return token table containing token specific fields or error message on error 1661 ParseToken = function( data, pos ) 1662 local ttype 1663 ttype, pos = string.unpack("B", data, pos) 1664 if ( not(Token.Parse[ttype]) ) then 1665 stdnse.debug1("%s: No parser for token type 0x%X", "MSSQL", ttype ) 1666 return -1, ("No parser for token type: 0x%X"):format( ttype ) 1667 end 1668 1669 return Token.Parse[ttype](data, pos) 1670 end, 1671 1672} 1673 1674 1675--- QueryPacket class 1676QueryPacket = 1677{ 1678 new = function(self,o) 1679 o = o or {} 1680 setmetatable(o, self) 1681 self.__index = self 1682 return o 1683 end, 1684 1685 SetQuery = function( self, query ) 1686 self.query = query 1687 end, 1688 1689 --- Returns the query packet as string 1690 -- 1691 -- @return string containing the authentication packet 1692 ToString = function( self ) 1693 return PacketType.Query, unicode.utf8to16( self.query ) 1694 end, 1695 1696} 1697 1698 1699--- PreLoginPacket class 1700PreLoginPacket = 1701{ 1702 -- TDS pre-login option types 1703 OPTION_TYPE = { 1704 Version = 0x00, 1705 Encryption = 0x01, 1706 InstOpt = 0x02, 1707 ThreadId = 0x03, 1708 MARS = 0x04, 1709 Terminator = 0xFF, 1710 }, 1711 1712 1713 versionInfo = nil, 1714 _requestEncryption = 0, 1715 _instanceName = "", 1716 _threadId = 0, -- Dummy value; will be filled in later 1717 _requestMars = nil, 1718 1719 new = function(self,o) 1720 o = o or {} 1721 setmetatable(o, self) 1722 self.__index = self 1723 return o 1724 end, 1725 1726 --- Sets the client version (default = 9.00.1399.00) 1727 -- 1728 -- @param versionInfo A SqlServerVersionInfo object with the client version information 1729 SetVersion = function(self, versionInfo) 1730 self._versionInfo = versionInfo 1731 end, 1732 1733 --- Sets whether to request encryption (default = false) 1734 -- 1735 -- @param requestEncryption A boolean indicating whether encryption will be requested 1736 SetRequestEncryption = function(self, requestEncryption) 1737 if requestEncryption then 1738 self._requestEncryption = 1 1739 else 1740 self._requestEncryption = 0 1741 end 1742 end, 1743 1744 --- Sets whether to request MARS support (default = undefined) 1745 -- 1746 -- @param requestMars A boolean indicating whether MARS support will be requested 1747 SetRequestMars = function(self, requestMars) 1748 if requestMars then 1749 self._requestMars = 1 1750 else 1751 self._requestMars = 0 1752 end 1753 end, 1754 1755 --- Sets the instance name of the target 1756 -- 1757 -- @param instanceName A string containing the name of the instance 1758 SetInstanceName = function(self, instanceName) 1759 self._instanceName = instanceName or "" 1760 end, 1761 1762 --- Returns the pre-login packet as a byte string 1763 -- 1764 -- @return byte string containing the pre-login packet 1765 ToBytes = function(self) 1766 -- Lengths for the values of TDS pre-login option fields 1767 local OPTION_LENGTH_CLIENT = { 1768 [PreLoginPacket.OPTION_TYPE.Version] = 6, 1769 [PreLoginPacket.OPTION_TYPE.Encryption] = 1, 1770 [PreLoginPacket.OPTION_TYPE.InstOpt] = -1, 1771 [PreLoginPacket.OPTION_TYPE.ThreadId] = 4, 1772 [PreLoginPacket.OPTION_TYPE.MARS] = 1, 1773 [PreLoginPacket.OPTION_TYPE.Terminator] = 0, 1774 } 1775 1776 local optionLength, optionType = 0, 0 1777 local offset = 1 -- Terminator 1778 offset = offset + 5 -- Version 1779 offset = offset + 5 -- Encryption 1780 offset = offset + 5 -- InstOpt 1781 offset = offset + 5 -- ThreadId 1782 if self._requestMars then offset = offset + 3 end -- MARS 1783 1784 if not self.versionInfo then 1785 self.versionInfo = SqlServerVersionInfo:new() 1786 self.versionInfo:SetVersionNumber( "9.00.1399.00" ) 1787 end 1788 1789 optionType = PreLoginPacket.OPTION_TYPE.Version 1790 optionLength = OPTION_LENGTH_CLIENT[ optionType ] 1791 local data = { string.pack( ">BI2I2", optionType, offset, optionLength ) } 1792 offset = offset + optionLength 1793 1794 optionType = PreLoginPacket.OPTION_TYPE.Encryption 1795 optionLength = OPTION_LENGTH_CLIENT[ optionType ] 1796 data[#data+1] = string.pack( ">BI2I2", optionType, offset, optionLength ) 1797 offset = offset + optionLength 1798 1799 optionType = PreLoginPacket.OPTION_TYPE.InstOpt 1800 optionLength = #self._instanceName + 1 --(string length + null-terminator) 1801 data[#data+1] = string.pack( ">BI2I2", optionType, offset, optionLength ) 1802 offset = offset + optionLength 1803 1804 optionType = PreLoginPacket.OPTION_TYPE.ThreadId 1805 optionLength = OPTION_LENGTH_CLIENT[ optionType ] 1806 data[#data+1] = string.pack( ">BI2I2", optionType, offset, optionLength ) 1807 offset = offset + optionLength 1808 1809 if self.requestMars then 1810 optionType = PreLoginPacket.OPTION_TYPE.MARS 1811 optionLength = OPTION_LENGTH_CLIENT[ optionType ] 1812 data[#data+1] = string.pack( ">BI2I2", optionType, offset, optionLength ) 1813 offset = offset + optionLength 1814 end 1815 1816 data[#data+1] = string.pack( "B", PreLoginPacket.OPTION_TYPE.Terminator ) 1817 1818 -- Now that the pre-login headers are done, write the data 1819 data[#data+1] = string.pack( ">BBI2I2", self.versionInfo.major, self.versionInfo.minor, 1820 self.versionInfo.build, self.versionInfo.subBuild ) 1821 data[#data+1] = string.pack( "<BzI4", self._requestEncryption, self._instanceName, self._threadId ) 1822 if self.requestMars then 1823 data[#data+1] = string.pack( "B", self._requestMars ) 1824 end 1825 1826 return PacketType.PreLogin, table.concat(data) 1827 end, 1828 1829 --- Reads a byte-string and creates a PreLoginPacket object from it. This is 1830 -- intended to handle the server's response to a pre-login request. 1831 FromBytes = function( bytes ) 1832 local OPTION_LENGTH_SERVER = { 1833 [PreLoginPacket.OPTION_TYPE.Version] = 6, 1834 [PreLoginPacket.OPTION_TYPE.Encryption] = 1, 1835 [PreLoginPacket.OPTION_TYPE.InstOpt] = -1, 1836 [PreLoginPacket.OPTION_TYPE.ThreadId] = 0, -- According to the TDS spec, this value should be empty from the server 1837 [PreLoginPacket.OPTION_TYPE.MARS] = 1, 1838 [PreLoginPacket.OPTION_TYPE.Terminator] = 0, 1839 } 1840 1841 1842 local status, pos = false, 1 1843 local preLoginPacket = PreLoginPacket:new() 1844 1845 while true do 1846 1847 local optionType, optionPos, optionLength, optionData, expectedOptionLength, _ 1848 if pos > #bytes then 1849 stdnse.debug2("%s: Could not extract optionType.", "MSSQL" ) 1850 return false, "Invalid pre-login response" 1851 end 1852 optionType, pos = ("B"):unpack(bytes, pos) 1853 if ( optionType == PreLoginPacket.OPTION_TYPE.Terminator ) then 1854 status = true 1855 break 1856 end 1857 expectedOptionLength = OPTION_LENGTH_SERVER[ optionType ] 1858 if ( not expectedOptionLength ) then 1859 stdnse.debug2("%s: Unrecognized pre-login option type: %s", "MSSQL", optionType ) 1860 expectedOptionLength = -1 1861 end 1862 1863 if pos + 4 > #bytes + 1 then 1864 stdnse.debug2("%s: Could not unpack optionPos and optionLength.", "MSSQL" ) 1865 return false, "Invalid pre-login response" 1866 end 1867 optionPos, optionLength, pos = (">I2I2"):unpack(bytes, pos) 1868 1869 optionPos = optionPos + 1 -- convert from 0-based index to 1-based index 1870 1871 if ( optionLength ~= expectedOptionLength and expectedOptionLength ~= -1 ) then 1872 stdnse.debug2("%s: Option data is incorrect size in pre-login response. ", "MSSQL" ) 1873 stdnse.debug2("%s: (optionType: %s) (optionLength: %s)", "MSSQL", optionType, optionLength ) 1874 return false, "Invalid pre-login response" 1875 end 1876 optionData = bytes:sub( optionPos, optionPos + optionLength - 1 ) 1877 if #optionData ~= optionLength then 1878 stdnse.debug2("%s: Could not read sufficient bytes from version data.", "MSSQL" ) 1879 return false, "Invalid pre-login response" 1880 end 1881 1882 if ( optionType == PreLoginPacket.OPTION_TYPE.Version ) then 1883 local major, minor, build, subBuild = (">BBI2I2"):unpack(optionData) 1884 local version = SqlServerVersionInfo:new() 1885 version:SetVersion( major, minor, build, subBuild, "SSNetLib" ) 1886 preLoginPacket.versionInfo = version 1887 elseif ( optionType == PreLoginPacket.OPTION_TYPE.Encryption ) then 1888 preLoginPacket:SetRequestEncryption( ("B"):unpack(optionData) ) 1889 elseif ( optionType == PreLoginPacket.OPTION_TYPE.InstOpt ) then 1890 preLoginPacket:SetInstanceName( ("z"):unpack(optionData) ) 1891 elseif ( optionType == PreLoginPacket.OPTION_TYPE.ThreadId ) then 1892 -- Do nothing. According to the TDS spec, this option is empty when sent from the server 1893 elseif ( optionType == PreLoginPacket.OPTION_TYPE.MARS ) then 1894 preLoginPacket:SetRequestMars( ("B"):unpack(optionData) ) 1895 end 1896 end 1897 1898 return status, preLoginPacket 1899 end, 1900} 1901 1902 1903--- LoginPacket class 1904LoginPacket = 1905{ 1906 1907 -- options_1 possible values 1908 -- 0x80 enable warning messages if SET LANGUAGE issued 1909 -- 0x40 change to initial database must succeed 1910 -- 0x20 enable warning messages if USE <database> issued 1911 -- 0x10 enable BCP 1912 1913 -- options_2 possible values 1914 -- 0x80 enable domain login security 1915 -- 0x40 "USER_SERVER - reserved" 1916 -- 0x20 user type is "DQ login" 1917 -- 0x10 user type is "replication login" 1918 -- 0x08 "fCacheConnect" 1919 -- 0x04 "fTranBoundary" 1920 -- 0x02 client is an ODBC driver 1921 -- 0x01 change to initial language must succeed 1922 length = 0, 1923 version = 0x71000001, -- Version 7.1 1924 size = 0, 1925 cli_version = 7, -- From jTDS JDBC driver 1926 cli_pid = 0, -- Dummy value 1927 conn_id = 0, 1928 options_1 = 0xa0, 1929 options_2 = 0x03, 1930 sqltype_flag = 0, 1931 reserved_flag= 0, 1932 time_zone = 0, 1933 collation = 0, 1934 1935 -- Strings 1936 client = "Nmap", 1937 username = nil, 1938 password = nil, 1939 app = "Nmap NSE", 1940 server = nil, 1941 library = "mssql.lua", 1942 locale = "", 1943 database = "master", --nil, 1944 MAC = "\x00\x00\x00\x00\x00\x00", -- should contain client MAC, jTDS uses all zeroes 1945 1946 new = function(self,o) 1947 o = o or {} 1948 setmetatable(o, self) 1949 self.__index = self 1950 return o 1951 end, 1952 1953 --- Sets the username used for authentication 1954 -- 1955 -- @param username string containing the username to user for authentication 1956 SetUsername = function(self, username) 1957 self.username = username 1958 end, 1959 1960 --- Sets the password used for authentication 1961 -- 1962 -- @param password string containing the password to user for authentication 1963 SetPassword = function(self, password) 1964 self.password = password 1965 end, 1966 1967 --- Sets the database used in authentication 1968 -- 1969 -- @param database string containing the database name 1970 SetDatabase = function(self, database) 1971 self.database = database 1972 end, 1973 1974 --- Sets the server's name used in authentication 1975 -- 1976 -- @param server string containing the name or ip of the server 1977 SetServer = function(self, server) 1978 self.server = server 1979 end, 1980 1981 SetDomain = function(self, domain) 1982 self.domain = domain 1983 end, 1984 1985 --- Returns the authentication packet as string 1986 -- 1987 -- @return string containing the authentication packet 1988 ToString = function(self) 1989 local data 1990 local offset = 86 1991 local ntlmAuth = not(not(self.domain)) 1992 local authLen = 0 1993 1994 self.cli_pid = math.random(100000) 1995 1996 self.length = offset + 2 * ( self.client:len() + self.app:len() + self.server:len() + self.library:len() + self.database:len() ) 1997 1998 if ( ntlmAuth ) then 1999 authLen = 32 + #self.domain 2000 self.length = self.length + authLen 2001 self.options_2 = self.options_2 + 0x80 2002 else 2003 self.length = self.length + 2 * (self.username:len() + self.password:len()) 2004 end 2005 2006 data = { 2007 string.pack("<I4I4I4I4I4I4", self.length, self.version, self.size, self.cli_version, self.cli_pid, self.conn_id ), 2008 string.pack("BBBB", self.options_1, self.options_2, self.sqltype_flag, self.reserved_flag ), 2009 string.pack("<I4I4", self.time_zone, self.collation ), 2010 2011 -- offsets begin 2012 string.pack("<I2I2", offset, self.client:len() ), 2013 } 2014 offset = offset + self.client:len() * 2 2015 2016 if ( not(ntlmAuth) ) then 2017 data[#data+1] = string.pack("<I2I2", offset, self.username:len() ) 2018 2019 offset = offset + self.username:len() * 2 2020 data[#data+1] = string.pack("<I2I2", offset, self.password:len() ) 2021 offset = offset + self.password:len() * 2 2022 else 2023 data[#data+1] = string.pack("<I2I2", offset, 0 ) 2024 data[#data+1] = string.pack("<I2I2", offset, 0 ) 2025 end 2026 2027 data[#data+1] = string.pack("<I2I2", offset, self.app:len() ) 2028 offset = offset + self.app:len() * 2 2029 2030 data[#data+1] = string.pack("<I2I2", offset, self.server:len() ) 2031 offset = offset + self.server:len() * 2 2032 2033 -- Offset to unused placeholder (reserved for future use in TDS spec) 2034 data[#data+1] = string.pack("<I2I2", 0, 0 ) 2035 2036 data[#data+1] = string.pack("<I2I2", offset, self.library:len() ) 2037 offset = offset + self.library:len() * 2 2038 2039 data[#data+1] = string.pack("<I2I2", offset, self.locale:len() ) 2040 offset = offset + self.locale:len() * 2 2041 2042 data[#data+1] = string.pack("<I2I2", offset, self.database:len() ) 2043 offset = offset + self.database:len() * 2 2044 2045 -- client MAC address, hardcoded to 00:00:00:00:00:00 2046 data[#data+1] = self.MAC 2047 2048 -- offset to auth info 2049 data[#data+1] = string.pack("<I2", offset) 2050 -- length of nt auth (should be 0 for sql auth) 2051 data[#data+1] = string.pack("<I2", authLen) 2052 -- next position (same as total packet length) 2053 data[#data+1] = string.pack("<I2", self.length) 2054 -- zero pad 2055 data[#data+1] = string.pack("<I2", 0) 2056 2057 -- Auth info wide strings 2058 data[#data+1] = unicode.utf8to16(self.client) 2059 if ( not(ntlmAuth) ) then 2060 data[#data+1] = unicode.utf8to16(self.username) 2061 data[#data+1] = Auth.TDS7CryptPass(self.password) 2062 end 2063 data[#data+1] = unicode.utf8to16(self.app) 2064 data[#data+1] = unicode.utf8to16(self.server) 2065 data[#data+1] = unicode.utf8to16(self.library) 2066 data[#data+1] = unicode.utf8to16(self.locale) 2067 data[#data+1] = unicode.utf8to16(self.database) 2068 2069 if ( ntlmAuth ) then 2070 local NTLMSSP_NEGOTIATE = 1 2071 local flags = 0x0000b201 2072 local workstation = "" 2073 2074 data[#data+1] = "NTLMSSP\0" 2075 data[#data+1] = string.pack("<I4I4", NTLMSSP_NEGOTIATE, flags) 2076 data[#data+1] = string.pack("<I2I2I4", #self.domain, #self.domain, 32) 2077 data[#data+1] = string.pack("<I2I2I4", #workstation, #workstation, 32) 2078 data[#data+1] = self.domain:upper() 2079 end 2080 2081 return PacketType.Login, table.concat(data) 2082 end, 2083 2084} 2085 2086NTAuthenticationPacket = { 2087 2088 new = function(self, username, password, domain, nonce) 2089 local o = {} 2090 setmetatable(o, self) 2091 o.username = username 2092 o.domain = domain 2093 o.nonce = nonce 2094 o.password = password 2095 self.__index = self 2096 return o 2097 end, 2098 2099 ToString = function(self) 2100 local ntlmssp = "NTLMSSP\0" 2101 local NTLMSSP_AUTH = 3 2102 local domain = unicode.utf8to16(self.domain:upper()) 2103 local user = unicode.utf8to16(self.username) 2104 local hostname, sessionkey = "", "" 2105 local flags = 0x00008201 2106 local ntlm_response = Auth.NtlmResponse(self.password, self.nonce) 2107 local lm_response = Auth.LmResponse(self.password, self.nonce) 2108 2109 local domain_offset = 64 2110 local username_offset = domain_offset + #domain 2111 local lm_response_offset = username_offset + #user 2112 local ntlm_response_offset = lm_response_offset + #lm_response 2113 local hostname_offset = ntlm_response_offset + #ntlm_response 2114 local sessionkey_offset = hostname_offset + #hostname 2115 2116 local data = ntlmssp .. string.pack("<I4I2I2I4", NTLMSSP_AUTH, #lm_response, #lm_response, lm_response_offset) 2117 .. string.pack("<I2I2I4", #ntlm_response, #ntlm_response, ntlm_response_offset) 2118 .. string.pack("<I2I2I4", #domain, #domain, domain_offset) 2119 .. string.pack("<I2I2I4", #user, #user, username_offset) 2120 .. string.pack("<I2I2I4", #hostname, #hostname, hostname_offset) 2121 .. string.pack("<I2I2I4", #sessionkey, #sessionkey, sessionkey_offset) 2122 .. string.pack("<I4", flags) 2123 .. domain 2124 .. user 2125 .. lm_response .. ntlm_response 2126 2127 return PacketType.NTAuthentication, data 2128 end, 2129 2130} 2131 2132-- Handles communication with SQL Server 2133TDSStream = { 2134 2135 -- Status flag constants 2136 MESSAGE_STATUS_FLAGS = { 2137 Normal = 0x0, 2138 EndOfMessage = 0x1, 2139 IgnoreThisEvent = 0x2, 2140 ResetConnection = 0x4, 2141 ResetConnectionSkipTran = 0x8, 2142 }, 2143 2144 _packetId = 0, 2145 _pipe = nil, 2146 _socket = nil, 2147 _name = nil, 2148 2149 new = function(self,o) 2150 o = o or {} 2151 setmetatable(o, self) 2152 self.__index = self 2153 return o 2154 end, 2155 2156 --- Establishes a connection to the SQL server. 2157 -- 2158 -- @param self A mssql.Helper object 2159 -- @param instanceInfo A SqlServerInstanceInfo object for the instance to 2160 -- connect to. 2161 -- @param connectionPreference (Optional) A list containing one or both of 2162 -- the strings "TCP" and "Named Pipes", indicating which transport 2163 -- methods to try and in what order. 2164 -- @param smbOverrides (Optional) An overrides table for calls to the <code>smb</code> 2165 -- library (for use with named pipes). 2166 ConnectEx = function( self, instanceInfo, connectionPreference, smbOverrides ) 2167 if ( self._socket ) then return false, "Already connected via TCP" end 2168 if ( self._pipe ) then return false, "Already connected via named pipes" end 2169 connectionPreference = connectionPreference or stdnse.get_script_args('mssql.protocol') or { "TCP", "Named Pipes" } 2170 if ( connectionPreference and 'string' == type(connectionPreference) ) then 2171 connectionPreference = { connectionPreference } 2172 end 2173 2174 local status, result, connectionType, errorMessage 2175 stdnse.debug3("%s: Connection preferences for %s: %s", 2176 "MSSQL", instanceInfo:GetName(), table.concat(connectionPreference, ", ") ) 2177 2178 for _, connectionType in ipairs( connectionPreference ) do 2179 if connectionType == "TCP" then 2180 2181 if not ( instanceInfo.port ) then 2182 stdnse.debug3("%s: Cannot connect to %s via TCP because port table is not set.", 2183 "MSSQL", instanceInfo:GetName() ) 2184 result = "No TCP port for this instance" 2185 else 2186 status, result = self:Connect( instanceInfo.host, instanceInfo.port ) 2187 if status then return true end 2188 end 2189 2190 elseif connectionType == "Named Pipes" or connectionType == "NP" then 2191 2192 if not ( instanceInfo.pipeName ) then 2193 stdnse.debug3("%s: Cannot connect to %s via named pipes because pipe name is not set.", 2194 "MSSQL", instanceInfo:GetName() ) 2195 result = "No named pipe for this instance" 2196 else 2197 status, result = self:ConnectToNamedPipe( instanceInfo.host, instanceInfo.pipeName, smbOverrides ) 2198 if status then return true end 2199 end 2200 2201 else 2202 stdnse.debug1("%s: Unknown connection preference: %s", "MSSQL", connectionType ) 2203 return false, ("ERROR: Unknown connection preference: %s"):format(connectionType) 2204 end 2205 2206 -- Handle any error messages 2207 if not status then 2208 if errorMessage then 2209 errorMessage = string.format( "%s, %s: %s", errorMessage, connectionType, result or "nil" ) 2210 else 2211 errorMessage = string.format( "%s: %s", connectionType, result or "nil" ) 2212 end 2213 end 2214 end 2215 2216 if not errorMessage then 2217 errorMessage = string.format( "%s: None of the preferred connection types are available for %s\\%s", 2218 "MSSQL", instanceInfo:GetName() ) 2219 end 2220 2221 return false, errorMessage 2222 end, 2223 2224 --- Establishes a connection to the SQL server 2225 -- 2226 -- @param host A host table for the target host 2227 -- @param pipePath The path to the named pipe of the target SQL Server 2228 -- (e.g. "\MSSQL$SQLEXPRESS\sql\query"). If nil, "\sql\query\" is used. 2229 -- @param smbOverrides (Optional) An overrides table for calls to the <code>smb</code> 2230 -- library (for use with named pipes). 2231 -- @return status: true on success, false on failure 2232 -- @return error_message: an error message, or nil 2233 ConnectToNamedPipe = function( self, host, pipePath, overrides ) 2234 if ( self._socket ) then return false, "Already connected via TCP" end 2235 2236 if ( SCANNED_PORTS_ONLY and smb.get_port( host ) == nil ) then 2237 stdnse.debug2("%s: Connection disallowed: scanned-ports-only is set and no SMB port is available", "MSSQL" ) 2238 return false, "Connection disallowed: scanned-ports-only" 2239 end 2240 2241 pipePath = pipePath or "\\sql\\query" 2242 2243 self._pipe = namedpipes.named_pipe:new() 2244 local status, result = self._pipe:connect( host, pipePath, overrides ) 2245 if ( status ) then 2246 self._name = self._pipe.pipe 2247 else 2248 self._pipe = nil 2249 end 2250 2251 return status, result 2252 end, 2253 2254 --- Establishes a connection to the SQL server 2255 -- 2256 -- @param host table containing host information 2257 -- @param port table containing port information 2258 -- @return status true on success, false on failure 2259 -- @return result containing error message on failure 2260 Connect = function( self, host, port ) 2261 if ( self._pipe ) then return false, "Already connected via named pipes" end 2262 2263 if ( SCANNED_PORTS_ONLY and nmap.get_port_state( host, port ) == nil ) then 2264 stdnse.debug2("%s: Connection disallowed: scanned-ports-only is set and port %d was not scanned", "MSSQL", port.number ) 2265 return false, "Connection disallowed: scanned-ports-only" 2266 end 2267 2268 local status, result, lport, _ 2269 2270 self._socket = nmap.new_socket() 2271 2272 -- Set the timeout to something realistic for connects 2273 self._socket:set_timeout( 5000 ) 2274 status, result = self._socket:connect(host, port) 2275 2276 if ( status ) then 2277 -- Sometimes a Query can take a long time to respond, so we set 2278 -- the timeout to 30 seconds. This shouldn't be a problem as the 2279 -- library attempt to decode the protocol and avoid reading past 2280 -- the end of the input buffer. So the only time the timeout is 2281 -- triggered is when waiting for a response to a query. 2282 self._socket:set_timeout( MSSQL_TIMEOUT * 1000 ) 2283 2284 status, _, lport, _, _ = self._socket:get_info() 2285 end 2286 2287 if ( not(status) ) then 2288 self._socket = nil 2289 stdnse.debug2("%s: Socket connection failed on %s:%s", "MSSQL", host.ip, port.number ) 2290 return false, "Socket connection failed" 2291 end 2292 self._name = string.format( "%s:%s", host.ip, port.number ) 2293 2294 return status, result 2295 end, 2296 2297 --- Disconnects from the SQL Server 2298 -- 2299 -- @return status true on success, false on failure 2300 -- @return result containing error message on failure 2301 Disconnect = function( self ) 2302 if ( self._socket ) then 2303 local status, result = self._socket:close() 2304 self._socket = nil 2305 return status, result 2306 elseif ( self._pipe ) then 2307 local status, result = self._pipe:disconnect() 2308 self._pipe = nil 2309 return status, result 2310 else 2311 return false, "Not connected" 2312 end 2313 end, 2314 2315 --- Sets the timeout for communication over the socket 2316 -- 2317 -- @param timeout number containing the new socket timeout in ms 2318 SetTimeout = function( self, timeout ) 2319 if ( self._socket ) then 2320 self._socket:set_timeout(timeout) 2321 else 2322 return false, "Not connected" 2323 end 2324 end, 2325 2326 --- Gets the name of the name pipe, or nil 2327 GetNamedPipeName = function( self ) 2328 if ( self._pipe ) then 2329 return self._pipe.name 2330 else 2331 return nil 2332 end 2333 end, 2334 2335 --- Send a TDS request to the server 2336 -- 2337 -- @param packetType A <code>PacketType</code>, indicating the type of TDS 2338 -- packet being sent. 2339 -- @param packetData A string containing the raw data to send to the server 2340 -- @return status true on success, false on failure 2341 -- @return result containing error message on failure 2342 Send = function( self, packetType, packetData ) 2343 local packetLength = packetData:len() + 8 -- +8 for TDS header 2344 local messageStatus, spid, window = 1, 0, 0 2345 2346 2347 if ( packetType ~= PacketType.NTAuthentication ) then self._packetId = self._packetId + 1 end 2348 local assembledPacket = string.pack(">BBI2I2BB", packetType, messageStatus, packetLength, spid, self._packetId, window) .. packetData 2349 2350 if ( self._socket ) then 2351 return self._socket:send( assembledPacket ) 2352 elseif ( self._pipe ) then 2353 return self._pipe:send( assembledPacket ) 2354 else 2355 return false, "Not connected" 2356 end 2357 end, 2358 2359 --- Receives responses from SQL Server 2360 -- 2361 -- The function continues to read and assemble a response until the server 2362 -- responds with the last response flag set 2363 -- 2364 -- @return status true on success, false on failure 2365 -- @return result containing raw data contents or error message on failure 2366 -- @return errorDetail nil, or additional information about an error. In 2367 -- the case of named pipes, this will be an SMB error name (e.g. NT_STATUS_PIPE_DISCONNECTED) 2368 Receive = function( self ) 2369 local status, result, errorDetail 2370 local combinedData, readBuffer = "", "" -- the buffer is solely for the benefit of TCP connections 2371 local tdsPacketAvailable = true 2372 2373 if not ( self._socket or self._pipe ) then 2374 return false, "Not connected" 2375 end 2376 2377 -- Large messages (e.g. result sets) can be split across multiple TDS 2378 -- packets from the server (which could themselves each be split across 2379 -- multiple TCP packets or SMB messages). 2380 while ( tdsPacketAvailable ) do 2381 local packetType, messageStatus, packetLength, spid, window 2382 local pos = 1 2383 2384 if ( self._socket ) then 2385 -- If there is existing data in the readBuffer, see if there's 2386 -- enough to read the TDS headers for the next packet. If not, 2387 -- do another read so we have something to work with. 2388 if ( readBuffer:len() < 8 ) then 2389 status, result = self._socket:receive_bytes(8 - readBuffer:len()) 2390 readBuffer = readBuffer .. result 2391 end 2392 elseif ( self._pipe ) then 2393 -- The named pipe takes care of all of its reassembly. We don't 2394 -- have to mess with buffers and repeatedly reading until we get 2395 -- the whole packet. We'll still write to readBuffer, though, so 2396 -- that the common logic can be reused. 2397 status, result, errorDetail = self._pipe:receive() 2398 readBuffer = result 2399 end 2400 2401 if not ( status and readBuffer ) then return false, result, errorDetail end 2402 2403 -- TDS packet validity check: packet at least as long as the TDS header 2404 if ( readBuffer:len() < 8 ) then 2405 stdnse.debug2("%s: Receiving (%s): packet is invalid length", "MSSQL", self._name ) 2406 return false, "Server returned invalid packet" 2407 end 2408 2409 -- read in the TDS headers 2410 packetType, messageStatus, packetLength, pos = string.unpack(">BBI2", readBuffer, pos ) 2411 spid, self._packetId, window, pos = string.unpack(">I2BB", readBuffer, pos ) 2412 2413 -- TDS packet validity check: packet type is Response (0x4) 2414 if ( packetType ~= PacketType.Response ) then 2415 stdnse.debug2("%s: Receiving (%s): Expected type 0x4 (response), but received type 0x%x", 2416 "MSSQL", self._name, packetType ) 2417 return false, "Server returned invalid packet" 2418 end 2419 2420 if ( self._socket ) then 2421 -- If we didn't previously read in enough data to complete this 2422 -- TDS packet, let's do so. 2423 while ( packetLength - readBuffer:len() > 0 ) do 2424 status, result = self._socket:receive() 2425 if not ( status and result ) then return false, result end 2426 readBuffer = readBuffer .. result 2427 end 2428 end 2429 2430 -- We've read in an apparently valid TDS packet 2431 local thisPacketData = readBuffer:sub( pos, packetLength ) 2432 -- Append its data to that of any previous TDS packets 2433 combinedData = combinedData .. thisPacketData 2434 if ( self._socket ) then 2435 -- If we read in data beyond the end of this TDS packet, save it 2436 -- so that we can use it in the next loop. 2437 readBuffer = readBuffer:sub( packetLength + 1 ) 2438 end 2439 2440 -- TDS packet validity check: packet length matches length from header 2441 if ( packetLength ~= (thisPacketData:len() + 8) ) then 2442 stdnse.debug2("%s: Receiving (%s): Header reports length %d, actual length is %d", 2443 "MSSQL", self._name, packetLength, thisPacketData:len() ) 2444 return false, "Server returned invalid packet" 2445 end 2446 2447 -- Check the status flags in the TDS packet to see if the message is 2448 -- continued in another TDS packet. 2449 tdsPacketAvailable = (( messageStatus & TDSStream.MESSAGE_STATUS_FLAGS.EndOfMessage) ~= 2450 TDSStream.MESSAGE_STATUS_FLAGS.EndOfMessage) 2451 end 2452 2453 -- return only the data section ie. without the headers 2454 return status, combinedData 2455 end, 2456 2457} 2458 2459--- Helper class 2460Helper = 2461{ 2462 new = function(self,o) 2463 o = o or {} 2464 setmetatable(o, self) 2465 self.__index = self 2466 return o 2467 end, 2468 2469 --- Establishes a connection to the SQL server 2470 -- 2471 -- @param host table containing host information 2472 -- @param port table containing port information 2473 -- @return status true on success, false on failure 2474 -- @return result containing error message on failure 2475 ConnectEx = function( self, instanceInfo ) 2476 local status, result 2477 self.stream = TDSStream:new() 2478 status, result = self.stream:ConnectEx( instanceInfo ) 2479 if ( not(status) ) then 2480 return false, result 2481 end 2482 2483 return true 2484 end, 2485 2486 --- Establishes a connection to the SQL server 2487 -- 2488 -- @param host table containing host information 2489 -- @param port table containing port information 2490 -- @return status true on success, false on failure 2491 -- @return result containing error message on failure 2492 Connect = function( self, host, port ) 2493 local status, result 2494 self.stream = TDSStream:new() 2495 status, result = self.stream:Connect(host, port) 2496 if ( not(status) ) then 2497 return false, result 2498 end 2499 2500 return true 2501 end, 2502 2503 --- Returns true if discovery has been performed to detect 2504 -- SQL Server instances on the given host 2505 WasDiscoveryPerformed = function( host ) 2506 local mutex = nmap.mutex( "discovery_performed for " .. host.ip ) 2507 mutex( "lock" ) 2508 nmap.registry.mssql = nmap.registry.mssql or {} 2509 nmap.registry.mssql.discovery_performed = nmap.registry.mssql.discovery_performed or {} 2510 2511 local wasPerformed = nmap.registry.mssql.discovery_performed[ host.ip ] or false 2512 mutex( "done" ) 2513 2514 return wasPerformed 2515 end, 2516 2517 --- Adds an instance to the list of instances kept in the Nmap registry for 2518 -- shared use by SQL Server scripts. 2519 -- 2520 -- If the registry already contains the instance, any new information is 2521 -- merged into the existing instance info. This may happen, for example, 2522 -- when an instance is discovered via named pipes, but the same instance has 2523 -- already been discovered via SSRP; this will prevent duplicates, where 2524 -- possible. 2525 AddOrMergeInstance = function( newInstance ) 2526 local instanceExists 2527 2528 nmap.registry.mssql = nmap.registry.mssql or {} 2529 nmap.registry.mssql.instances = nmap.registry.mssql.instances or {} 2530 nmap.registry.mssql.instances[ newInstance.host.ip ] = nmap.registry.mssql.instances[ newInstance.host.ip ] or {} 2531 2532 for _, existingInstance in ipairs( nmap.registry.mssql.instances[ newInstance.host.ip ] ) do 2533 if existingInstance == newInstance then 2534 existingInstance:Merge( newInstance ) 2535 instanceExists = true 2536 break 2537 end 2538 end 2539 2540 if not instanceExists then 2541 table.insert( nmap.registry.mssql.instances[ newInstance.host.ip ], newInstance ) 2542 end 2543 end, 2544 2545 --- Gets a table containing SqlServerInstanceInfo objects discovered on 2546 -- the specified host (and port, if specified). 2547 -- 2548 -- @param host A host table for the target host 2549 -- @param port (Optional) If omitted, all of the instances for the host 2550 -- will be returned. 2551 -- @return A table containing SqlServerInstanceInfo objects, or nil 2552 GetDiscoveredInstances = function( host, port ) 2553 nmap.registry.mssql = nmap.registry.mssql or {} 2554 nmap.registry.mssql.instances = nmap.registry.mssql.instances or {} 2555 nmap.registry.mssql.instances[ host.ip ] = nmap.registry.mssql.instances[ host.ip ] or {} 2556 2557 if ( not port ) then 2558 local instances = nmap.registry.mssql.instances[ host.ip ] 2559 if ( instances and #instances == 0 ) then instances = nil end 2560 return instances 2561 else 2562 for _, instance in ipairs( nmap.registry.mssql.instances[ host.ip ] ) do 2563 if ( instance.port and instance.port.number == port.number and 2564 instance.port.protocol == port.protocol ) then 2565 return { instance } 2566 end 2567 end 2568 2569 return nil 2570 end 2571 end, 2572 2573 --- Attempts to discover SQL Server instances using SSRP to query one or 2574 -- more (if <code>broadcast</code> is used) SQL Server Browser services. 2575 -- 2576 -- Any discovered instances are returned, as well as being stored for use 2577 -- by other scripts (see <code>mssql.Helper.GetDiscoveredInstances()</code>). 2578 -- 2579 -- @param host A host table for the target. 2580 -- @param port (Optional) A port table for the target port. If this is nil, 2581 -- the default SSRP port (UDP 1434) is used. 2582 -- @param broadcast If true, this will be done with an SSRP broadcast, and 2583 -- <code>host</code> should contain the broadcast specification (e.g. 2584 -- ip = "255.255.255.255"). 2585 -- @return (status, result) If status is true, result is a table of 2586 -- tables containing SqlServerInstanceInfo objects. The top-level table 2587 -- is indexed by IP address. If status is false, result is an 2588 -- error message. 2589 DiscoverBySsrp = function( host, port, broadcast ) 2590 2591 if broadcast then 2592 local status, result = SSRP.DiscoverInstances_Broadcast( host, port ) 2593 2594 if not status then 2595 return status, result 2596 else 2597 for ipAddress, host in pairs( result ) do 2598 for _, instance in ipairs( host ) do 2599 Helper.AddOrMergeInstance( instance ) 2600 -- Give some version info back to Nmap 2601 if ( instance.port and instance.version ) then 2602 instance.version:PopulateNmapPortVersion( instance.port ) 2603 --nmap.set_port_version( instance.host, instance.port) 2604 end 2605 end 2606 end 2607 2608 return true, result 2609 end 2610 else 2611 local status, result = SSRP.DiscoverInstances( host, port ) 2612 2613 if not status then 2614 return status, result 2615 else 2616 for _, instance in ipairs( result ) do 2617 Helper.AddOrMergeInstance( instance ) 2618 -- Give some version info back to Nmap 2619 if ( instance.port and instance.version ) then 2620 instance.version:PopulateNmapPortVersion( instance.port ) 2621 nmap.set_port_version( host, instance.port) 2622 end 2623 end 2624 2625 local instances_all = {} 2626 instances_all[ host.ip ] = result 2627 return true, instances_all 2628 end 2629 end 2630 end, 2631 2632 --- Attempts to discover a SQL Server instance listening on the specified 2633 -- port. 2634 -- 2635 -- If an instance is discovered, it is returned, as well as being stored for 2636 -- use by other scripts (see 2637 -- <code>mssql.Helper.GetDiscoveredInstances()</code>). 2638 -- 2639 -- @param host A host table for the target. 2640 -- @param port A port table for the target port. 2641 -- @return (status, result) If status is true, result is a table of 2642 -- SqlServerInstanceInfo objects. If status is false, result is an 2643 -- error message or nil. 2644 DiscoverByTcp = function( host, port ) 2645 local version, instance, status 2646 -- Check to see if we've already discovered an instance on this port 2647 instance = Helper.GetDiscoveredInstances( host, port ) 2648 if ( not instance ) then 2649 instance = SqlServerInstanceInfo:new() 2650 instance.host = host 2651 instance.port = port 2652 2653 status, version = Helper.GetInstanceVersion( instance ) 2654 if ( status ) then 2655 Helper.AddOrMergeInstance( instance ) 2656 -- The point of this wasn't to get the version, just to use the 2657 -- pre-login packet to determine whether there was a SQL Server on 2658 -- the port. However, since we have the version now, we'll store it. 2659 instance.version = version 2660 -- Give some version info back to Nmap 2661 if ( instance.port and instance.version ) then 2662 instance.version:PopulateNmapPortVersion( instance.port ) 2663 nmap.set_port_version( host, instance.port) 2664 end 2665 end 2666 end 2667 2668 return (instance ~= nil), { instance } 2669 end, 2670 2671 --- Attempts to discover SQL Server instances listening on default named 2672 -- pipes. 2673 -- 2674 -- Any discovered instances are returned, as well as being stored for use by 2675 -- other scripts (see <code>mssql.Helper.GetDiscoveredInstances()</code>). 2676 -- 2677 -- @param host A host table for the target. 2678 -- @param port A port table for the port to connect on for SMB 2679 -- @return (status, result) If status is true, result is a table of 2680 -- SqlServerInstanceInfo objects. If status is false, result is an 2681 -- error message or nil. 2682 DiscoverBySmb = function( host, port ) 2683 local defaultPipes = { 2684 "\\sql\\query", 2685 "\\MSSQL$SQLEXPRESS\\sql\\query", 2686 "\\MSSQL$SQLSERVER\\sql\\query", 2687 } 2688 local tdsStream = TDSStream:new() 2689 local status, result, instances_host 2690 2691 for _, pipeSubPath in ipairs( defaultPipes ) do 2692 status, result = tdsStream:ConnectToNamedPipe( host, pipeSubPath, nil ) 2693 2694 if status then 2695 instances_host = {} 2696 local instance = SqlServerInstanceInfo:new() 2697 instance.pipeName = tdsStream:GetNamedPipeName() 2698 tdsStream:Disconnect() 2699 instance.host = host 2700 2701 Helper.AddOrMergeInstance( instance ) 2702 table.insert( instances_host, instance ) 2703 else 2704 stdnse.debug3("DiscoverBySmb \n pipe: %s\n result: %s", pipeSubPath, tostring( result ) ) 2705 end 2706 end 2707 2708 return (instances_host ~= nil), instances_host 2709 end, 2710 2711 --- Attempts to discover SQL Server instances by a variety of means. 2712 -- 2713 -- This function calls the three DiscoverBy functions, which perform the 2714 -- actual discovery. Any discovered instances can be retrieved using 2715 -- <code>mssql.Helper.GetDiscoveredInstances()</code>. 2716 -- 2717 -- @param host Host table as received by the script action function 2718 Discover = function( host ) 2719 nmap.registry.mssql = nmap.registry.mssql or {} 2720 nmap.registry.mssql.discovery_performed = nmap.registry.mssql.discovery_performed or {} 2721 nmap.registry.mssql.discovery_performed[ host.ip ] = false 2722 2723 local mutex = nmap.mutex( "discovery_performed for " .. host.ip ) 2724 mutex( "lock" ) 2725 2726 local sqlDefaultPort = nmap.get_port_state( host, {number = 1433, protocol = "tcp"} ) or {number = 1433, protocol = "tcp"} 2727 local sqlBrowserPort = nmap.get_port_state( host, {number = 1434, protocol = "udp"} ) or {number = 1434, protocol = "udp"} 2728 local smbPort 2729 -- smb.get_port() will return nil if no SMB port was scanned OR if SMB ports were scanned but none was open 2730 local smbPortNumber = smb.get_port( host ) 2731 if ( smbPortNumber ) then 2732 smbPort = nmap.get_port_state( host, {number = smbPortNumber, protocol = "tcp"} ) 2733 -- There's no use in manually setting an SMB port; if no SMB port was 2734 -- scanned and found open, the SMB library won't work 2735 end 2736 -- if the user has specified ports, we'll check those too 2737 local targetInstancePorts = stdnse.get_script_args( "mssql.instance-port" ) 2738 2739 if ( sqlBrowserPort and sqlBrowserPort.state ~= "closed" ) then 2740 Helper.DiscoverBySsrp( host, sqlBrowserPort ) 2741 end 2742 if ( sqlDefaultPort and sqlDefaultPort.state ~= "closed" ) then 2743 Helper.DiscoverByTcp( host, sqlDefaultPort ) 2744 end 2745 if ( smbPort ) then 2746 Helper.DiscoverBySmb( host, smbPort ) 2747 end 2748 if ( targetInstancePorts ) then 2749 if ( type( targetInstancePorts ) == "string" ) then 2750 targetInstancePorts = { targetInstancePorts } 2751 end 2752 for _, portNumber in ipairs( targetInstancePorts ) do 2753 portNumber = tonumber( portNumber ) 2754 Helper.DiscoverByTcp( host, {number = portNumber, protocol = "tcp"} ) 2755 end 2756 end 2757 2758 nmap.registry.mssql.discovery_performed[ host.ip ] = true 2759 mutex( "done" ) 2760 end, 2761 2762 --- Returns all of the credentials available for the target instance, 2763 -- including any set by the <code>mssql.username</code> and <code>mssql.password</code> 2764 -- script arguments. 2765 -- 2766 -- @param instanceInfo A SqlServerInstanceInfo object for the target instance 2767 -- @return A table of usernames mapped to passwords (i.e. <code>creds[ username ] = password</code>) 2768 GetLoginCredentials_All = function( instanceInfo ) 2769 local credentials = instanceInfo.credentials or {} 2770 local credsExist = false 2771 for _, _ in pairs( credentials ) do 2772 credsExist = true 2773 break 2774 end 2775 if ( not credsExist ) then credentials = nil end 2776 2777 if ( stdnse.get_script_args( "mssql.username" ) ) then 2778 credentials = credentials or {} 2779 local usernameArg = stdnse.get_script_args( "mssql.username" ) 2780 local passwordArg = stdnse.get_script_args( "mssql.password" ) or "" 2781 credentials[ usernameArg ] = passwordArg 2782 end 2783 2784 return credentials 2785 end, 2786 2787 --- Returns a username-password set according to the following rules of 2788 -- precedence: 2789 -- 2790 -- * If the <code>mssql.username</code> and <code>mssql.password</code> 2791 -- script arguments were set, their values are used. (If the username 2792 -- argument was specified without the password argument, a blank 2793 -- password is used.) 2794 -- * If the password for the "sa" account has been discovered (e.g. by the 2795 -- <code>ms-sql-empty-password</code> or <code>ms-sql-brute</code> 2796 -- scripts), these credentials are used. 2797 -- * If other credentials have been discovered, the first of these in the 2798 -- table are used. 2799 -- * Otherwise, nil is returned. 2800 -- 2801 -- @param instanceInfo A SqlServerInstanceInfo object for the target instance 2802 -- @return (username, password) 2803 GetLoginCredentials = function( instanceInfo ) 2804 2805 -- First preference goes to any user-specified credentials 2806 local username = stdnse.get_script_args( "mssql.username" ) 2807 local password = stdnse.get_script_args( "mssql.password" ) or "" 2808 2809 -- Otherwise, use any valid credentials that have been discovered (e.g. by ms-sql-brute) 2810 if ( not(username) and instanceInfo.credentials ) then 2811 -- Second preference goes to the "sa" account 2812 if ( instanceInfo.credentials.sa ) then 2813 username = "sa" 2814 password = instanceInfo.credentials.sa 2815 else 2816 -- ok were stuck with some n00b account, just get the first one 2817 for user, pass in pairs( instanceInfo.credentials ) do 2818 username = user 2819 password = pass 2820 break 2821 end 2822 end 2823 end 2824 2825 return username, password 2826 end, 2827 2828 --- Disconnects from the SQL Server 2829 -- 2830 -- @return status true on success, false on failure 2831 -- @return result containing error message on failure 2832 Disconnect = function( self ) 2833 if ( not(self.stream) ) then 2834 return false, "Not connected to server" 2835 end 2836 2837 self.stream:Disconnect() 2838 self.stream = nil 2839 2840 return true 2841 end, 2842 2843 --- Authenticates to SQL Server. 2844 -- 2845 -- If login fails, one of the following error messages will be returned: 2846 -- * "Password is expired" 2847 -- * "Must change password at next logon" 2848 -- * "Account is locked out" 2849 -- * "Login Failed" 2850 -- 2851 -- @param username string containing the username for authentication 2852 -- @param password string containing the password for authentication 2853 -- @param database string containing the database to access 2854 -- @param servername string containing the name or ip of the remote server 2855 -- @return status true on success, false on failure 2856 -- @return result containing error message on failure 2857 -- @return errorDetail nil or a <code>LoginErrorType</code> value, if available 2858 Login = function( self, username, password, database, servername ) 2859 local loginPacket = LoginPacket:new() 2860 local status, result, data, errorDetail, token 2861 local servername = servername or "DUMMY" 2862 local pos = 1 2863 local ntlmAuth = false 2864 2865 if ( not self.stream ) then 2866 return false, "Not connected to server" 2867 end 2868 2869 loginPacket:SetUsername(username) 2870 loginPacket:SetPassword(password) 2871 loginPacket:SetDatabase(database) 2872 loginPacket:SetServer(servername) 2873 2874 local domain = stdnse.get_script_args("mssql.domain") 2875 if (domain) then 2876 if ( not(HAVE_SSL) ) then return false, "mssql: OpenSSL not present" end 2877 ntlmAuth = true 2878 -- if the domain was specified without an argument, set a default domain of "." 2879 if (domain == 1 or domain == true ) then 2880 domain = "." 2881 end 2882 loginPacket:SetDomain(domain) 2883 end 2884 2885 status, result = self.stream:Send( loginPacket:ToString() ) 2886 if ( not(status) ) then 2887 return false, result 2888 end 2889 2890 status, data, errorDetail = self.stream:Receive() 2891 if ( not(status) ) then 2892 -- When logging in via named pipes, SQL Server will sometimes 2893 -- disconnect the pipe if the login attempt failed (this only seems 2894 -- to happen with non-"sa") accounts. At this point, having 2895 -- successfully connected and sent a message, we can be reasonably 2896 -- comfortable that a disconnected pipe indicates a failed login. 2897 if ( errorDetail == "NT_STATUS_PIPE_DISCONNECTED" ) then 2898 return false, "Bad username or password", LoginErrorType.InvalidUsernameOrPassword 2899 end 2900 return false, data 2901 end 2902 2903 if ( ntlmAuth ) then 2904 local pos, nonce = Token.ParseToken( data, pos ) 2905 local authpacket = NTAuthenticationPacket:new( username, password, domain, nonce ) 2906 status, result = self.stream:Send( authpacket:ToString() ) 2907 status, data = self.stream:Receive() 2908 if ( not(status) ) then 2909 return false, data 2910 end 2911 end 2912 2913 while( pos < data:len() ) do 2914 pos, token = Token.ParseToken( data, pos ) 2915 if ( -1 == pos ) then 2916 return false, token 2917 end 2918 2919 if ( token.type == TokenType.ErrorMessage ) then 2920 local errorMessageLookup = { 2921 [LoginErrorType.AccountLockedOut] = "Account is locked out", 2922 [LoginErrorType.NotAssociatedWithTrustedConnection] = "User is not associated with a trusted connection (instance may allow Windows authentication only)", 2923 [LoginErrorType.InvalidUsernameOrPassword] = "Bad username or password", 2924 [LoginErrorType.PasswordExpired] = "Password is expired", 2925 [LoginErrorType.PasswordMustChange] = "Must change password at next logon", 2926 } 2927 local errorMessage = errorMessageLookup[ token.errno ] or string.format( "Login Failed (%s)", tostring(token.errno) ) 2928 2929 return false, errorMessage, token.errno 2930 elseif ( token.type == TokenType.LoginAcknowledgement ) then 2931 return true, "Login Success" 2932 end 2933 end 2934 2935 return false, "Failed to process login response" 2936 end, 2937 2938 --- Authenticates to SQL Server, using the credentials returned by 2939 -- Helper.GetLoginCredentials(). 2940 -- 2941 -- If the login is rejected by the server, the error code will be returned, 2942 -- as a number in the form of a <code>mssql.LoginErrorType</code> (for which 2943 -- error messages can be looked up in <code>mssql.LoginErrorMessage</code>). 2944 -- 2945 -- @param instanceInfo a SqlServerInstanceInfo object for the instance to log into 2946 -- @param database string containing the database to access 2947 -- @param servername string containing the name or ip of the remote server 2948 -- @return status true on success, false on failure 2949 -- @return result containing error code or error message 2950 LoginEx = function( self, instanceInfo, database, servername ) 2951 local servername = servername or instanceInfo.host.ip 2952 local username, password = Helper.GetLoginCredentials( instanceInfo ) 2953 if ( not username ) then 2954 return false, "No login credentials" 2955 end 2956 2957 return self:Login( username, password, database, servername ) 2958 end, 2959 2960 --- Performs a SQL query and parses the response 2961 -- 2962 -- @param query string containing the SQL query 2963 -- @return status true on success, false on failure 2964 -- @return table containing a table of columns for each row 2965 -- or error message on failure 2966 Query = function( self, query ) 2967 2968 local queryPacket = QueryPacket:new() 2969 local status, result, data, token, colinfo, rows 2970 local pos = 1 2971 2972 if ( nil == self.stream ) then 2973 return false, "Not connected to server" 2974 end 2975 2976 queryPacket:SetQuery( query ) 2977 status, result = self.stream:Send( queryPacket:ToString() ) 2978 if ( not(status) ) then 2979 return false, result 2980 end 2981 2982 status, data = self.stream:Receive() 2983 if ( not(status) ) then 2984 return false, data 2985 end 2986 2987 -- Iterate over tokens until we get to a rowtag 2988 while( pos < data:len() ) do 2989 local rowtag = string.unpack("B", data, pos) 2990 2991 if ( rowtag == TokenType.Row ) then 2992 break 2993 end 2994 2995 pos, token = Token.ParseToken( data, pos ) 2996 if ( -1 == pos ) then 2997 return false, token 2998 end 2999 if ( token.type == TokenType.ErrorMessage ) then 3000 return false, token.error 3001 elseif ( token.type == TokenType.TDS7Results ) then 3002 colinfo = token.colinfo 3003 end 3004 end 3005 3006 3007 rows = {} 3008 3009 while(true) do 3010 local rowtag 3011 rowtag, pos = string.unpack("B", data, pos ) 3012 3013 if ( rowtag ~= TokenType.Row ) then 3014 break 3015 end 3016 3017 if ( rowtag == TokenType.Row and colinfo and #colinfo > 0 ) then 3018 local columns = {} 3019 3020 for i=1, #colinfo do 3021 local val 3022 3023 if ( ColumnData.Parse[colinfo[i].type] ) then 3024 if not ( colinfo[i].type == 106 or colinfo[i].type == 108) then 3025 pos, val = ColumnData.Parse[colinfo[i].type](data, pos) 3026 else 3027 -- decimal / numeric types need precision and scale passed. 3028 pos, val = ColumnData.Parse[colinfo[i].type]( colinfo[i].precision, colinfo[i].scale, data, pos) 3029 end 3030 3031 if ( -1 == pos ) then 3032 return false, val 3033 end 3034 table.insert(columns, val) 3035 else 3036 return false, ("unknown datatype=0x%X"):format(colinfo[i].type) 3037 end 3038 end 3039 table.insert(rows, columns) 3040 end 3041 end 3042 3043 result = {} 3044 result.rows = rows 3045 result.colinfo = colinfo 3046 3047 return true, result 3048 end, 3049 3050 --- Attempts to connect to a SQL Server instance listening on a TCP port in 3051 -- order to determine the version of the SSNetLib DLL, which is an 3052 -- authoritative version number for the SQL Server instance itself. 3053 -- 3054 -- @param instanceInfo An instance of SqlServerInstanceInfo 3055 -- @return status true on success, false on failure 3056 -- @return versionInfo an instance of mssql.SqlServerVersionInfo, or nil 3057 GetInstanceVersion = function( instanceInfo ) 3058 3059 if ( not instanceInfo.host or not (instanceInfo:HasNetworkProtocols()) ) then return false, nil end 3060 3061 local status, response, version 3062 local tdsStream = TDSStream:new() 3063 3064 status, response = tdsStream:ConnectEx( instanceInfo ) 3065 3066 if ( not status ) then 3067 stdnse.debug2("%s: Connection to %s failed: %s", "MSSQL", instanceInfo:GetName(), response or "" ) 3068 return false, "Connect failed" 3069 end 3070 3071 local preLoginRequest = PreLoginPacket:new() 3072 preLoginRequest:SetInstanceName( instanceInfo.instanceName ) 3073 3074 tdsStream:SetTimeout( 5000 ) 3075 tdsStream:Send( preLoginRequest:ToBytes() ) 3076 3077 -- read in any response we might get 3078 status, response = tdsStream:Receive() 3079 tdsStream:Disconnect() 3080 3081 if status then 3082 local preLoginResponse 3083 status, preLoginResponse = PreLoginPacket.FromBytes( response ) 3084 if status then 3085 version = preLoginResponse.versionInfo 3086 else 3087 stdnse.debug2("%s: Parsing of pre-login packet from %s failed: %s", 3088 "MSSQL", instanceInfo:GetName(), preLoginResponse or "" ) 3089 return false, "Parsing failed" 3090 end 3091 else 3092 stdnse.debug2("%s: Receive for %s failed: %s", "MSSQL", instanceInfo:GetName(), response or "" ) 3093 return false, "Receive failed" 3094 end 3095 3096 return status, version 3097 end, 3098 3099 --- Gets a table containing SqlServerInstanceInfo objects for the instances 3100 -- that should be run against, based on the script-args (e.g. <code>mssql.instance</code>) 3101 -- 3102 -- @param host Host table as received by the script action function 3103 -- @param port (Optional) Port table as received by the script action function 3104 -- @return status True on success, false on failure 3105 -- @return instances If status is true, this will be a table with one or 3106 -- more SqlServerInstanceInfo objects. If status is false, this will be 3107 -- an error message. 3108 GetTargetInstances = function( host, port ) 3109 if ( port ) then 3110 local status = true 3111 local instance = Helper.GetDiscoveredInstances( host, port ) 3112 3113 if ( not instance ) then 3114 status, instance = Helper.DiscoverByTcp( host, port ) 3115 end 3116 if ( instance ) then 3117 return true, instance 3118 else 3119 return false, "No SQL Server instance detected on this port" 3120 end 3121 else 3122 local targetInstanceNames = stdnse.get_script_args( "mssql.instance-name" ) 3123 local targetInstancePorts = stdnse.get_script_args( "mssql.instance-port" ) 3124 local targetAllInstances = stdnse.get_script_args( "mssql.instance-all" ) 3125 3126 if ( targetInstanceNames and targetInstancePorts ) then 3127 return false, "Connections can be made either by instance name or port." 3128 end 3129 3130 if ( targetAllInstances and ( targetInstanceNames or targetInstancePorts ) ) then 3131 return false, "All instances cannot be specified together with an instance name or port." 3132 end 3133 3134 if ( not (targetInstanceNames or targetInstancePorts or targetAllInstances) ) then 3135 return false, "No instance(s) specified." 3136 end 3137 3138 if ( not Helper.WasDiscoveryPerformed( host ) ) then 3139 stdnse.debug2("%s: Discovery has not been performed prior to GetTargetInstances() call. Performing discovery now.", "MSSQL" ) 3140 Helper.Discover( host ) 3141 end 3142 3143 local instanceList = Helper.GetDiscoveredInstances( host ) 3144 if ( not instanceList ) then 3145 return false, "No instances found on target host" 3146 end 3147 3148 local targetInstances = {} 3149 if ( targetAllInstances ) then 3150 targetInstances = instanceList 3151 else 3152 -- We want an easy way to look up whether an instance's name was 3153 -- in our target list. So, we'll make a table of { instanceName = true, ... } 3154 local temp = {} 3155 if ( targetInstanceNames ) then 3156 if ( type( targetInstanceNames ) == "string" ) then 3157 targetInstanceNames = { targetInstanceNames } 3158 end 3159 for _, instanceName in ipairs( targetInstanceNames ) do 3160 temp[ string.upper( instanceName ) ] = true 3161 end 3162 end 3163 targetInstanceNames = temp 3164 3165 -- Do the same for the target ports 3166 temp = {} 3167 if ( targetInstancePorts ) then 3168 if ( type( targetInstancePorts ) == "string" ) then 3169 targetInstancePorts = { targetInstancePorts } 3170 end 3171 for _, portNumber in ipairs( targetInstancePorts ) do 3172 portNumber = tonumber( portNumber ) 3173 temp[portNumber] = true 3174 end 3175 end 3176 targetInstancePorts = temp 3177 3178 for _, instance in ipairs( instanceList ) do 3179 if ( instance.instanceName and targetInstanceNames[ string.upper( instance.instanceName ) ] ) then 3180 table.insert( targetInstances, instance ) 3181 elseif ( instance.port and targetInstancePorts[ tonumber( instance.port.number ) ] ) then 3182 table.insert( targetInstances, instance ) 3183 end 3184 end 3185 end 3186 3187 if ( #targetInstances > 0 ) then 3188 return true, targetInstances 3189 else 3190 return false, "Specified instance(s) not found on target host" 3191 end 3192 end 3193 end, 3194 3195 --- Queries the SQL Browser service for the DAC port of the specified instance 3196 -- 3197 -- The DAC (Dedicated Admin Connection) port allows DBA's to connect to 3198 -- the database when normal connection attempts fail, for example, when 3199 -- the server is hanging, out of memory or other bad states. 3200 -- 3201 -- @param host Host table as received by the script action function 3202 -- @param instanceName the instance name to probe for a DAC port 3203 -- @return number containing the DAC port on success or nil on failure 3204 DiscoverDACPort = function(host, instanceName) 3205 local socket = nmap.new_socket() 3206 socket:set_timeout(5000) 3207 3208 if ( not(socket:connect(host, 1434, "udp")) ) then 3209 return false, "Failed to connect to sqlbrowser service" 3210 end 3211 3212 if ( not(socket:send(string.pack("c2z", "\x0F\x01", instanceName))) ) then 3213 socket:close() 3214 return false, "Failed to send request to sqlbrowser service" 3215 end 3216 3217 local status, data = socket:receive_buf(match.numbytes(6), true) 3218 if ( not(status) ) then 3219 socket:close() 3220 return nil 3221 end 3222 socket:close() 3223 3224 if ( #data < 6 ) then 3225 return nil 3226 end 3227 return string.unpack("<I2", data, 5) 3228 end, 3229 3230 --- Returns a hostrule for standard SQL Server scripts, which will return 3231 -- true if one or more instances have been targeted with the <code>mssql.instance</code> 3232 -- script argument. 3233 -- 3234 -- However, if a previous script has failed to find any 3235 -- SQL Server instances on the host, the hostrule function will return 3236 -- false to keep further scripts from running unnecessarily on that host. 3237 -- 3238 -- @return A hostrule function (use as <code>hostrule = mssql.GetHostrule_Standard()</code>) 3239 GetHostrule_Standard = function() 3240 return function( host ) 3241 if ( stdnse.get_script_args( {"mssql.instance-all", "mssql.instance-name", "mssql.instance-port"} ) ~= nil ) then 3242 if ( Helper.WasDiscoveryPerformed( host ) ) then 3243 return Helper.GetDiscoveredInstances( host ) ~= nil 3244 else 3245 return true 3246 end 3247 else 3248 return false 3249 end 3250 end 3251 end, 3252 3253 3254 --- Returns a portrule for standard SQL Server scripts 3255 -- 3256 -- The portrule return true if BOTH of the following conditions are met: 3257 -- * The port has been identified as "ms-sql-s" 3258 -- * The <code>mssql.instance</code> script argument has NOT been used 3259 -- 3260 -- @return A portrule function (use as <code>portrule = mssql.GetPortrule_Standard()</code>) 3261 GetPortrule_Standard = function() 3262 return function( host, port ) 3263 return ( shortport.service( "ms-sql-s" )(host, port) and 3264 stdnse.get_script_args( {"mssql.instance-all", "mssql.instance-name", "mssql.instance-port"} ) == nil) 3265 end 3266 end, 3267} 3268 3269 3270Auth = { 3271 3272 --- Encrypts a password using the TDS7 *ultra secure* XOR encryption 3273 -- 3274 -- @param password string containing the password to encrypt 3275 -- @return string containing the encrypted password 3276 TDS7CryptPass = function(password) 3277 local xormask = 0x5a5a 3278 3279 return password:gsub(".", function(i) 3280 local c = string.byte( i ) ~ xormask 3281 local m1= ( c >> 4 ) & 0x0F0F 3282 local m2= ( c << 4 ) & 0xF0F0 3283 return string.pack("<I2", m1 | m2 ) 3284 end) 3285 end, 3286 3287 LmResponse = function( password, nonce ) 3288 3289 if ( not(HAVE_SSL) ) then 3290 stdnse.debug1("ERROR: Nmap is missing OpenSSL") 3291 return 3292 end 3293 3294 password = password .. string.rep('\0', 14 - #password) 3295 3296 password = password:upper() 3297 3298 -- Take the first and second half of the password (note that if it's longer than 14 characters, it's truncated) 3299 local str1 = string.sub(password, 1, 7) 3300 local str2 = string.sub(password, 8, 14) 3301 3302 -- Generate the keys 3303 local key1 = openssl.DES_string_to_key(str1) 3304 local key2 = openssl.DES_string_to_key(str2) 3305 3306 local result = openssl.encrypt("DES", key1, nil, nonce) .. openssl.encrypt("DES", key2, nil, nonce) 3307 3308 result = result .. string.rep('\0', 21 - #result) 3309 3310 str1 = string.sub(result, 1, 7) 3311 str2 = string.sub(result, 8, 14) 3312 local str3 = string.sub(result, 15, 21) 3313 3314 key1 = openssl.DES_string_to_key(str1) 3315 key2 = openssl.DES_string_to_key(str2) 3316 local key3 = openssl.DES_string_to_key(str3) 3317 3318 result = openssl.encrypt("DES", key1, nil, nonce) .. openssl.encrypt("DES", key2, nil, nonce) .. openssl.encrypt("DES", key3, nil, nonce) 3319 return result 3320 end, 3321 3322 NtlmResponse = function( password, nonce ) 3323 local lm_response, ntlm_response, mac_key = smbauth.get_password_response(nil, 3324 nil, 3325 nil, 3326 password, 3327 nil, 3328 "v1", 3329 nonce, 3330 false 3331 ) 3332 return ntlm_response 3333 end, 3334} 3335 3336--- "static" Utility class containing mostly conversion functions 3337Util = 3338{ 3339 --- Takes a table as returned by Query and does some fancy formatting 3340 -- better suitable for <code>stdnse.output_result</code> 3341 -- 3342 -- @param tbl as received by <code>Helper.Query</code> 3343 -- @param with_headers boolean true if output should contain column headers 3344 -- @return table suitable for <code>stdnse.output_result</code> 3345 FormatOutputTable = function ( tbl, with_headers ) 3346 local new_tbl = {} 3347 local col_names = {} 3348 3349 if ( not(tbl) ) then 3350 return 3351 end 3352 3353 if ( with_headers and tbl.rows and #tbl.rows > 0 ) then 3354 local headers 3355 for k, v in pairs( tbl.colinfo ) do 3356 table.insert( col_names, v.text) 3357 end 3358 headers = table.concat(col_names, "\t") 3359 table.insert( new_tbl, headers) 3360 headers = headers:gsub("[^%s]", "=") 3361 table.insert( new_tbl, headers ) 3362 end 3363 3364 for _, v in ipairs( tbl.rows ) do 3365 table.insert( new_tbl, table.concat(v, "\t") ) 3366 end 3367 3368 return new_tbl 3369 end, 3370} 3371 3372return _ENV; 3373