|
| 1 | +local mssql = require "mssql" |
| 2 | +local nmap = require "nmap" |
| 3 | +local smb = require "smb" |
| 4 | +local shortport = require "shortport" |
| 5 | +local stdnse = require "stdnse" |
| 6 | + |
| 7 | +-- -*- mode: lua -*- |
| 8 | +-- vim: set filetype=lua : |
| 9 | + |
| 10 | +description = [[ |
| 11 | +Attempts to determine configuration and version information for Microsoft SQL |
| 12 | +Server instances. |
| 13 | +
|
| 14 | +SQL Server credentials required: No (will not benefit from |
| 15 | +<code>mssql.username</code> & <code>mssql.password</code>). |
| 16 | +Run criteria: |
| 17 | +* Host script: Will always run. |
| 18 | +* Port script: N/A |
| 19 | +
|
| 20 | +NOTE: Unlike previous versions, this script will NOT attempt to log in to SQL |
| 21 | +Server instances. Blank passwords can be checked using the |
| 22 | +<code>ms-sql-empty-password</code> script. E.g.: |
| 23 | +<code>nmap -sn --script ms-sql-empty-password --script-args mssql.instance-all <host></code> |
| 24 | +
|
| 25 | +The script uses two means of getting version information for SQL Server instances: |
| 26 | +* Querying the SQL Server Browser service, which runs by default on UDP port |
| 27 | +1434 on servers that have SQL Server 2000 or later installed. However, this |
| 28 | +service may be disabled without affecting the functionality of the instances. |
| 29 | +Additionally, it provides imprecise version information. |
| 30 | +* Sending a probe to the instance, causing the instance to respond with |
| 31 | +information including the exact version number. This is the same method that |
| 32 | +Nmap uses for service versioning; however, this script can also do the same for |
| 33 | +instances accessible via Windows named pipes, and can target all of the |
| 34 | +instances listed by the SQL Server Browser service. |
| 35 | +
|
| 36 | +In the event that the script can connect to the SQL Server Browser service |
| 37 | +(UDP 1434) but is unable to connect directly to the instance to obtain more |
| 38 | +accurate version information (because ports are blocked or the <code>mssql.scanned-ports-only</code> |
| 39 | +argument has been used), the script will rely only upon the version number |
| 40 | +provided by the SQL Server Browser/Monitor, which has the following limitations: |
| 41 | +* For SQL Server 2000 and SQL Server 7.0 instances, the RTM version number is |
| 42 | +always given, regardless of any service packs or patches installed. |
| 43 | +* For SQL Server 2005 and later, the version number will reflect the service |
| 44 | +pack installed, but the script will not be able to tell whether patches have |
| 45 | +been installed. |
| 46 | +
|
| 47 | +Where possible, the script will determine major version numbers, service pack |
| 48 | +levels and whether patches have been installed. However, in cases where |
| 49 | +particular determinations can not be made, the script will report only what can |
| 50 | +be confirmed. |
| 51 | +
|
| 52 | +NOTE: Communication with instances via named pipes depends on the <code>smb</code> |
| 53 | +library. To communicate with (and possibly to discover) instances via named pipes, |
| 54 | +the host must have at least one SMB port (e.g. TCP 445) that was scanned and |
| 55 | +found to be open. Additionally, named pipe connections may require Windows |
| 56 | +authentication to connect to the Windows host (via SMB) in addition to the |
| 57 | +authentication required to connect to the SQL Server instances itself. See the |
| 58 | +documentation and arguments for the <code>smb</code> library for more information. |
| 59 | +
|
| 60 | +NOTE: By default, the ms-sql-* scripts may attempt to connect to and communicate |
| 61 | +with ports that were not included in the port list for the Nmap scan. This can |
| 62 | +be disabled using the <code>mssql.scanned-ports-only</code> script argument. |
| 63 | +]] |
| 64 | +--- |
| 65 | +-- @usage |
| 66 | +-- nmap -p 445 --script ms-sql-info <host> |
| 67 | +-- nmap -p 1433 --script ms-sql-info --script-args mssql.instance-port=1433 <host> |
| 68 | +-- |
| 69 | +-- @output |
| 70 | +-- | ms-sql-info: |
| 71 | +-- | Windows server name: WINXP |
| 72 | +-- | 192.168.100.128\PROD: |
| 73 | +-- | Instance name: PROD |
| 74 | +-- | Version: |
| 75 | +-- | name: Microsoft SQL Server 2000 SP3 |
| 76 | +-- | number: 8.00.760 |
| 77 | +-- | Product: Microsoft SQL Server 2000 |
| 78 | +-- | Service pack level: SP3 |
| 79 | +-- | Post-SP patches applied: No |
| 80 | +-- | TCP port: 1278 |
| 81 | +-- | Named pipe: \\192.168.100.128\pipe\MSSQL$PROD\sql\query |
| 82 | +-- | Clustered: No |
| 83 | +-- | 192.168.100.128\SQLFIREWALLED: |
| 84 | +-- | Instance name: SQLFIREWALLED |
| 85 | +-- | Version: |
| 86 | +-- | name: Microsoft SQL Server 2008 RTM |
| 87 | +-- | Product: Microsoft SQL Server 2008 |
| 88 | +-- | Service pack level: RTM |
| 89 | +-- | TCP port: 4343 |
| 90 | +-- | Clustered: No |
| 91 | +-- | \\192.168.100.128\pipe\sql\query: |
| 92 | +-- | Version: |
| 93 | +-- | name: Microsoft SQL Server 2005 SP3+ |
| 94 | +-- | number: 9.00.4053 |
| 95 | +-- | Product: Microsoft SQL Server 2005 |
| 96 | +-- | Service pack level: SP3 |
| 97 | +-- | Post-SP patches applied: Yes |
| 98 | +-- |_ Named pipe: \\192.168.100.128\pipe\sql\query |
| 99 | +-- |
| 100 | +-- @xmloutput |
| 101 | +-- <elem key="Windows server name">WINXP</elem> |
| 102 | +-- <table key="192.168.100.128\PROD"> |
| 103 | +-- <elem key="Instance name">PROD</elem> |
| 104 | +-- <table key="Version"> |
| 105 | +-- <elem key="name">Microsoft SQL Server 2000 SP3</elem> |
| 106 | +-- <elem key="number">8.00.760</elem> |
| 107 | +-- <elem key="Product">Microsoft SQL Server 2000</elem> |
| 108 | +-- <elem key="Service pack level">SP3</elem> |
| 109 | +-- <elem key="Post-SP patches applied">No</elem> |
| 110 | +-- </table> |
| 111 | +-- <elem key="TCP port">1278</elem> |
| 112 | +-- <elem key="Named pipe">\\192.168.100.128\pipe\MSSQL$PROD\sql\query</elem> |
| 113 | +-- <elem key="Clustered">No</elem> |
| 114 | +-- </table> |
| 115 | +-- <table key="192.168.100.128\SQLFIREWALLED"> |
| 116 | +-- <elem key="Instance name">SQLFIREWALLED</elem> |
| 117 | +-- <table key="Version"> |
| 118 | +-- <elem key="name">Microsoft SQL Server 2008 RTM</elem> |
| 119 | +-- <elem key="Product">Microsoft SQL Server 2008</elem> |
| 120 | +-- <elem key="Service pack level">RTM</elem> |
| 121 | +-- </table> |
| 122 | +-- <elem key="TCP port">4343</elem> |
| 123 | +-- <elem key="Clustered">No</elem> |
| 124 | +-- </table> |
| 125 | +-- <table key="\\192.168.100.128\pipe\sql\query"> |
| 126 | +-- <table key="Version"> |
| 127 | +-- <elem key="name">Microsoft SQL Server 2005 SP3+</elem> |
| 128 | +-- <elem key="number">9.00.4053</elem> |
| 129 | +-- <elem key="Product">Microsoft SQL Server 2005</elem> |
| 130 | +-- <elem key="Service pack level">SP3</elem> |
| 131 | +-- <elem key="Post-SP patches applied">Yes</elem> |
| 132 | +-- </table> |
| 133 | +-- <elem key="Named pipe">\\192.168.100.128\pipe\sql\query</elem> |
| 134 | +-- </table> |
| 135 | + |
| 136 | +-- rev 1.0 (2007-06-09) |
| 137 | +-- rev 1.1 (2009-12-06 - Added SQL 2008 identification T Sellers) |
| 138 | +-- rev 1.2 (2010-10-03 - Added Broadcast support <patrik@cqure.net>) |
| 139 | +-- rev 1.3 (2010-10-10 - Added prerule and newtargets support <patrik@cqure.net>) |
| 140 | +-- rev 1.4 (2011-01-24 - Revised logic in order to get version data without logging in; |
| 141 | +-- added functionality to interpret version in terms of SP level, etc. |
| 142 | +-- added script arg to prevent script from connecting to ports that |
| 143 | +-- weren't in original Nmap scan <chris3E3@gmail.com>) |
| 144 | +-- rev 1.5 (2011-02-01 - Moved discovery functionality into ms-sql-discover.nse and |
| 145 | +-- broadcast-ms-sql-discovery.nse <chris3E3@gmail.com>) |
| 146 | +-- rev 1.6 (2014-09-04 - Added structured output Daniel Miller) |
| 147 | + |
| 148 | +author = {"Chris Woodbury", "Thomas Buchanan"} |
| 149 | + |
| 150 | +license = "Same as Nmap--See https://nmap.org/book/man-legal.html" |
| 151 | + |
| 152 | +categories = {"default", "discovery", "safe"} |
| 153 | + |
| 154 | +portrule = shortport.port_or_service(1433, "ms-sql-s") |
| 155 | + |
| 156 | + |
| 157 | + |
| 158 | +--- Returns formatted output for the given version data |
| 159 | +local function create_version_output_table( versionInfo ) |
| 160 | + local versionOutput = stdnse.output_table() |
| 161 | + |
| 162 | + versionOutput["name"] = versionInfo:ToString() |
| 163 | + if ( versionInfo.source ~= "SSRP" ) then |
| 164 | + versionOutput["number"] = versionInfo.versionNumber |
| 165 | + end |
| 166 | + versionOutput["Product"] = versionInfo.productName |
| 167 | + versionOutput["Service pack level"] = versionInfo.servicePackLevel |
| 168 | + versionOutput["Post-SP patches applied"] = versionInfo.patched |
| 169 | + |
| 170 | + return versionOutput |
| 171 | +end |
| 172 | + |
| 173 | + |
| 174 | +--- Returns formatted output for the given instance |
| 175 | +local function create_instance_output_table( instance ) |
| 176 | + |
| 177 | + -- if we didn't get anything useful (due to errors or the port not actually |
| 178 | + -- being SQL Server), don't report anything |
| 179 | + if not ( instance.instanceName or instance.version ) then return nil end |
| 180 | + |
| 181 | + local instanceOutput = stdnse.output_table() |
| 182 | + |
| 183 | + instanceOutput["Instance name"] = instance.instanceName |
| 184 | + if instance.version then |
| 185 | + instanceOutput["Version"] = create_version_output_table( instance.version ) |
| 186 | + end |
| 187 | + if instance.port then instanceOutput["TCP port"] = instance.port.number end |
| 188 | + instanceOutput["Named pipe"] = instance.pipeName |
| 189 | + instanceOutput["Clustered"] = instance.isClustered |
| 190 | + |
| 191 | + return instanceOutput |
| 192 | + |
| 193 | +end |
| 194 | + |
| 195 | + |
| 196 | +--- Processes a single instance, attempting to determine its version, etc. |
| 197 | +local function process_instance( instance ) |
| 198 | + |
| 199 | + local foundVersion = false |
| 200 | + local ssnetlibVersion |
| 201 | + |
| 202 | + -- If possible and allowed (see 'mssql.scanned-ports-only' argument), we'll |
| 203 | + -- connect to the instance to get an accurate version number |
| 204 | + if ( instance:HasNetworkProtocols() ) then |
| 205 | + local ssnetlibVersion |
| 206 | + foundVersion, ssnetlibVersion = mssql.Helper.GetInstanceVersion( instance ) |
| 207 | + if ( foundVersion ) then |
| 208 | + instance.version = ssnetlibVersion |
| 209 | + stdnse.debug1("Retrieved SSNetLib version for %s.", instance:GetName() ) |
| 210 | + else |
| 211 | + stdnse.debug1("Could not retrieve SSNetLib version for %s.", instance:GetName() ) |
| 212 | + end |
| 213 | + end |
| 214 | + |
| 215 | + -- If we didn't get a version from SSNetLib, give the user some detail as to why |
| 216 | + if ( not foundVersion ) then |
| 217 | + if ( not instance:HasNetworkProtocols() ) then |
| 218 | + stdnse.debug1("%s has no network protocols enabled.", instance:GetName() ) |
| 219 | + end |
| 220 | + if ( instance.version ) then |
| 221 | + stdnse.debug1("Using version number from SSRP response for %s.", instance:GetName() ) |
| 222 | + else |
| 223 | + stdnse.debug1("Version info could not be retrieved for %s.", instance:GetName() ) |
| 224 | + end |
| 225 | + end |
| 226 | + |
| 227 | + -- Give some version info back to Nmap |
| 228 | + if ( instance.port and instance.version ) then |
| 229 | + instance.version:PopulateNmapPortVersion( instance.port ) |
| 230 | + nmap.set_port_version( instance.host, instance.port) |
| 231 | + end |
| 232 | + |
| 233 | +end |
| 234 | + |
| 235 | + |
| 236 | +action = function( host ) |
| 237 | + local scriptOutput = stdnse.output_table() |
| 238 | + |
| 239 | + local status, instanceList = mssql.Helper.GetTargetInstances( host ) |
| 240 | + -- if no instances were targeted, then display info on all |
| 241 | + if ( not status ) then |
| 242 | + if ( not mssql.Helper.WasDiscoveryPerformed( host ) ) then |
| 243 | + mssql.Helper.Discover( host ) |
| 244 | + end |
| 245 | + instanceList = mssql.Helper.GetDiscoveredInstances( host ) |
| 246 | + end |
| 247 | + |
| 248 | + |
| 249 | + if ( not instanceList ) then |
| 250 | + return stdnse.format_output( false, instanceList or "" ) |
| 251 | + else |
| 252 | + for _, instance in ipairs( instanceList ) do |
| 253 | + if instance.serverName then |
| 254 | + scriptOutput["Windows server name"] = instance.serverName |
| 255 | + break |
| 256 | + end |
| 257 | + end |
| 258 | + for _, instance in pairs( instanceList ) do |
| 259 | + process_instance( instance ) |
| 260 | + scriptOutput[instance:GetName()] = create_instance_output_table( instance ) |
| 261 | + end |
| 262 | + end |
| 263 | + |
| 264 | + return scriptOutput |
| 265 | +end |
| 266 | + |
0 commit comments