Nmap Development mailing list archives
[NSE Script] Microsoft SQL Server detection
From: Thomas Buchanan <tbuchanan () thecompassgrp net>
Date: Tue, 12 Jun 2007 10:54:05 -0500
Here's a script that tries to dig a little deeper than the MS SQL service detection probe. It reports some of the same information as the service detection, but then takes that information and does a little more with it.
Here's an example of the version detection (-sV) output for a SQL Server 2000 system:
PORT STATE SERVICE VERSION1434/udp open ms-sql-m Microsoft SQL Server 8.00.194 (ServerName: NT4SERVER-SP2; TCPPort: 1433)
Here's the output from the script that I worked up, run against that same server:
PORT STATE SERVICE 1434/udp open ms-sql-m | MS SQL: Discovered Microsoft SQL Server 2000 | Server name: NT4SERVER-SP2 | Server version: 8.00.194 | Instance name: MSSQLSERVER | TCP Port: 1433 | sa user appears to have blank password | Microsoft SQL Server 2000 - 8.00.534 (Intel X86) |_ Enterprise Edition on Windows NT 4.0 (Build 1381: Service Pack 6) Some of the features of the script: - Checks for blank 'sa' password - Handles more than one instance of SQL Server running on the same system - Makes TCP connections to the port listed for each instance- Knows a little about specific versions of SQL Server, such as service pack levels for SQL Server 2005
PORT STATE SERVICE 1434/udp open ms-sql-m | MS SQL: Discovered Microsoft SQL Server 2000 | Server name: WINXP | Server version: 8.00.194 | Instance name: MSSQLSERVER | TCP Port: 1433 | Could not retrieve actual version information | Instance name: MSDE | TCP Port: 1336 | sa user appears to have blank password | Microsoft SQL Server 2000 - 8.00.760 (Intel X86) |_ Desktop Engine on Windows NT 5.1 (Build 2600: Service Pack 2) PORT STATE SERVICE 1434/udp open ms-sql-m | MS SQL: Discovered Microsoft SQL Server 2005 Express Edition | Server name: WINXP1 | Server version: 9.00.3042.00 (SP2) | Instance name: SQLEXPRESS | TCP Port: 3061 |_ Could not retrieve actual version informationI've only been able to test this script against SQL Server 2000 and 2005. If anybody can test this against SQL Server 6.5 or 7, and let me know what happens, I'd be very interested.
This script might cause some problems with nmap output under certain circumstances. I've only observed the issue from one specific system, but if you notice anything, please report it.
Hope somebody finds this useful. Enjoy, Thomas
-- Microsoft SQL Server information gathering script
-- rev 1.0 (2007-06-09)
id = "MS SQL"
description = "Attempts to extract information from Microsoft SQL Server"
author = "Thomas Buchanan <tbuchanan () thecompassgrp net>"
license = "See nmaps COPYING for licence"
categories = {"discovery", "intrusive"}
portrule = function(host, port)
if
( port.number == 1433
or port.number == 1434 )
and port.protocol == "udp"
-- if we only run against open or open|filtered ports anyway, leave next two lines commented
-- and ( port.state == "open"
-- or port.state == "open|filtered" )
then
return true
else
return false
end
end
action = function(host, port)
-- create the socket used for our connection
local socket = nmap.new_socket()
-- set a reasonable timeout value
socket:set_timeout(5000)
-- do some exception handling / cleanup
local catch = function()
socket:close()
end
local try = nmap.new_try(catch)
-- try to login to MS SQL network service, and obtain the real version information
-- MS SQL 2000 does not report the correct version in the data sent in response to UDP probe (see below)
local get_real_version = function(dst, dstPort)
local outcome
local payload
local stat, resp
-- build a TDS packet - type 0x12
-- copied from packet capture of osql connection
payload = "\018\001\000\047\000\000\001\000\000\000"
payload = payload .. "\026\000\006\001\000\032\000\001\002\000"
payload = payload .. "\033\000\001\003\000\034\000\004\004\000"
payload = payload .. "\038\000\001\255\009\000\011\226\000\000"
payload = payload .. "\000\000\120\023\000\000\000"
socket = nmap.new_socket()
-- connect to the server using the tcpPort captured from the UDP probe
try(socket:connect(dst, dstPort, "tcp"))
try(socket:send(payload))
-- read in any response we might get
stat, resp = socket:receive_bytes(1)
if string.match(resp, "^\004") then
-- build a login packet to send to SQL server
-- username = sa, blank password
-- for information about packet structure, see http://www.freetds.org/tds.html
local query = "\016\001\000\128\000\000\001\000" -- TDS packet header
query = query .. "\120\000\000\000\002\000\009\114" -- Login packet header = length, version
query = query .. "\000\000\000\000\000\000\000\007" -- Login packet header continued = size, client version
query = query .. "\140\018\000\000\000\000\000\000" -- Login packet header continued = Client PID,
Connection ID
query = query .. "\224\003\000\000\104\001\000\000" -- Login packet header continued = Option Flags 1 & 2,
status flag, reserved flag, timezone
query = query .. "\009\004\000\000\094\000\004\000" -- Login packet (Collation), then start offsets &
lengths (client name, client length)
query = query .. "\102\000\002\000\000\000\000\000" -- Login packet, offsets & lengths = username offset,
username length, password offset, password length
query = query .. "\106\000\004\000\114\000\000\000" -- Login packet, offsets & lengths = app name offset,
app name length, server name offset, server name length
query = query .. "\000\000\000\000\114\000\003\000" -- Login packet, offsets & lengths = unknown offset,
unknown length, library name offset, library name length
query = query .. "\120\000\000\000\120\000\000\000" -- Login packet, offsets & lengths = locale offset,
locale length, database name offset, database name length
query = query .. "\000\000\000\000\000\000\000\000" -- Login packet, MAC address + padding
query = query .. "\000\000\000\000\000\000\000\000" -- Login packet, padding
query = query .. "\000\000\000\000\000\000\078\000" -- Login packet, padding + start of client name (N)
query = query .. "\077\000\065\000\080\000\115\000" -- Login packet = rest of client name (MAP) + username
(s)
query = query .. "\097\000\078\000\077\000\065\000" -- Login packet = username (a), app name (NMA)
query = query .. "\080\000\078\000\083\000\069\000" -- Login packet = app name (P), library name (NSE)
-- send the packet down the wire
try(socket:send(query))
-- read in any response we might get
stat, resp = socket:receive_bytes(1)
-- successful response to login packet should contain the string "SQL Server"
-- however, the string is UCS2 encoded, so we have to add the \000 characters
if string.match(resp, "S\000Q\000L\000") then
outcome = "\n sa user appears to have blank password"
-- since we have a successful login, send a query that will tell us what version the server is really
running
query = "\001\001\000\044\000\000\001\000" -- TDS Query packet
query = query .. "\083\000\069\000\076\000\069\000" -- SELE
query = query .. "\067\000\084\000\032\000\064\000" -- CT @
query = query .. "\064\000\086\000\069\000\082\000" -- @VER
query = query .. "\083\000\073\000\079\000\078\000" -- SION
query = query .. "\013\000\010\000"
-- send the packet down the wire
try(socket:send(query))
-- read in any response we might get
stat, resp = socket:receive_bytes(1)
-- strip out the embedded \000 characters
local banner = string.gsub(resp, "%z", "")
outcome = outcome .. "\n " .. string.match(banner, "(Microsoft.-)\n")
outcome = outcome .. "\n" .. string.match(banner, "\n.-\n.-\n(.-Build.-)\n")
end
try(socket:close())
end -- if string.match(response, "^\004")
if outcome == nil then
outcome = "\n Could not retrieve actual version information"
end
return outcome
end -- get_real_version(dst, dstPort)
-- connect to the potential SQL server
try(socket:connect(host.ip, port.number, "udp"))
-- send a magic packet
-- details here: http://www.codeproject.com/cs/database/locate_sql_servers.asp
try(socket:send("\002"))
local status
local response
-- read in any response we might get
status, response = socket:receive_bytes(1)
try(socket:close())
if (not status) then
return
end
if (response == "TIMEOUT") then
return
end
-- since we got something back, the port is definitely open
nmap.set_port_state(host, port, "open")
local result
-- create a lua table to hold some information
local serverInfo = {}
-- do some pattern matching to exract certain key elements from the response
-- the data comes back as a long semicolon separated list
-- A single server can have multiple instances, which are separated by a double semicolon
-- cycle through each instance
local count = 1
for instance in string.gmatch(response, "(.-;;)") do
result = instance
serverInfo[count] = {}
serverInfo[count].name = string.match(instance, "ServerName;(.-);")
serverInfo[count].instanceName = string.match(instance, "InstanceName;(.-);")
serverInfo[count].clustered = string.match(instance, "IsClustered;(.-);")
serverInfo[count].version = string.match(instance, "Version;(.-);")
serverInfo[count].tcpPort = string.match(instance, ";tcp;(.-);")
serverInfo[count].namedPipe = string.match(instance, ";np;(.-);")
count = count + 1
end
-- do some heuristics on the version to see if we can match the major releases
if string.match(serverInfo[1].version, "^6%.0") then
result = "Discovered Microsoft SQL Server 6.0"
elseif string.match(serverInfo[1].version, "^6%.5") then
result = "Discovered Microsoft SQL Server 6.5"
elseif string.match(serverInfo[1].version, "^7%.0") then
result = "Discovered Microsoft SQL Server 7.0"
elseif string.match(serverInfo[1].version, "^8%.0") then
result = "Discovered Microsoft SQL Server 2000"
elseif string.match(serverInfo[1].version, "^9%.0") then
-- The Express Edition of MS SQL Server 2005 has a default instance name of SQLEXPRESS
for _,instance in ipairs(serverInfo) do
if string.match(instance.instanceName, "SQLEXPRESS") then
result = "Discovered Microsoft SQL Server 2005 Express Edition"
end
end
if result == nil then
result = "Discovered Microsoft SQL Server 2005"
end
else
result = "Discovered Microsoft SQL Server"
end
if serverInfo[1].name ~= nil then
result = result .. "\n Server name: " .. serverInfo[1].name
end
if serverInfo[1].version ~= nil then
result = result .. "\n Server version: " .. serverInfo[1].version
-- Check for some well known release versions of SQL Server 2005
-- for more info, see http://support.microsoft.com/kb/321185
if string.match(serverInfo[1].version, "9.00.3042") then
result = result .. " (SP2)"
elseif string.match(serverInfo[1].version, "9.00.3043") then
result = result .. " (SP2)"
elseif string.match(serverInfo[1].version, "9.00.2047") then
result = result .. " (SP1)"
elseif string.match(serverInfo[1].version, "9.00.1399") then
result = result .. " (RTM)"
end
end
for _,instance in ipairs(serverInfo) do
if instance.instanceName ~= nil then
result = result .. "\n Instance name: " .. instance.instanceName
end
if instance.tcpPort ~= nil then
result = result .. "\n TCP Port: " .. instance.tcpPort
result = result .. get_real_version(host.ip, instance.tcpPort)
end
end
return result
end
_______________________________________________ Sent through the nmap-dev mailing list http://cgi.insecure.org/mailman/listinfo/nmap-dev Archived at http://SecLists.Org
Current thread:
- [NSE Script] Microsoft SQL Server detection Thomas Buchanan (Jun 12)
- Re: [NSE Script] Microsoft SQL Server detection Diman Todorov (Jun 12)
- RE: [NSE Script] Microsoft SQL Server detection Thomas Buchanan (Jun 12)
- Re: [NSE Script] Microsoft SQL Server detection Diman Todorov (Jun 13)
- RE: [NSE Script] Microsoft SQL Server detection Thomas Buchanan (Jun 12)
- Re: [NSE Script] Microsoft SQL Server detection Diman Todorov (Jun 12)
