home *** CD-ROM | disk | FTP | other *** search
/ Hackers Magazine 57 / CdHackersMagazineNr57.iso / Software / Networking / nmap-5.00-setup.exe / scripts / ms-sql-info.nse < prev    next >
Text File  |  2009-07-06  |  9KB  |  230 lines

  1. description = [[
  2. Attempts to extract information from Microsoft SQL Server instances.
  3. ]]
  4. -- rev 1.0 (2007-06-09)
  5.  
  6. author = "Thomas Buchanan <tbuchanan@thecompassgrp.net>"
  7.  
  8. license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
  9.  
  10. categories = {"default", "discovery", "intrusive"}
  11.  
  12. require('stdnse')
  13. require "shortport"
  14. require("strbuf")
  15.  
  16. portrule = shortport.portnumber({1433, 1434}, "udp", {"open", "open|filtered"})
  17.  
  18. action = function(host, port)
  19.  
  20.     -- create the socket used for our connection
  21.     local socket = nmap.new_socket()
  22.     
  23.     -- set a reasonable timeout value
  24.     socket:set_timeout(5000)
  25.     
  26.     -- do some exception handling / cleanup
  27.     local catch = function()
  28.         socket:close()
  29.     end
  30.     
  31.     local try = nmap.new_try(catch)
  32.     
  33.     -- try to login to MS SQL network service, and obtain the real version information
  34.     -- MS SQL 2000 does not report the correct version in the data sent in response to UDP probe (see below)
  35.     local get_real_version = function(dst, dstPort)
  36.       
  37.       local outcome
  38.       local payload =  strbuf.new()
  39.       
  40.       local stat, resp
  41.       
  42.       -- build a TDS packet - type 0x12
  43.       -- copied from packet capture of osql connection
  44.       payload = payload .. "\018\001\000\047\000\000\001\000\000\000"
  45.       payload = payload .. "\026\000\006\001\000\032\000\001\002\000"
  46.       payload = payload .. "\033\000\001\003\000\034\000\004\004\000"
  47.       payload = payload .. "\038\000\001\255\009\000\011\226\000\000"
  48.       payload = payload .. "\000\000\120\023\000\000\000"
  49.       
  50.       socket = nmap.new_socket()
  51.       
  52.       -- connect to the server using the tcpPort captured from the UDP probe
  53.       try(socket:connect(dst, dstPort, "tcp"))
  54.       
  55.       try(socket:send(strbuf.dump(payload)))
  56.       
  57.       -- read in any response we might get
  58.       stat, resp = socket:receive_bytes(1)
  59.       
  60.       if string.match(resp, "^\004") then
  61.         
  62.         -- build a login packet to send to SQL server
  63.         -- username = sa, blank password
  64.         -- for information about packet structure, see http://www.freetds.org/tds.html
  65.         
  66.         local query = strbuf.new()
  67.         query = query .. "\016\001\000\128\000\000\001\000" -- TDS packet header
  68.         query = query .. "\120\000\000\000\002\000\009\114" -- Login packet header = length, version
  69.         query = query .. "\000\000\000\000\000\000\000\007" -- Login packet header continued = size, client version
  70.         query = query .. "\140\018\000\000\000\000\000\000" -- Login packet header continued = Client PID, Connection ID
  71.         query = query .. "\224\003\000\000\104\001\000\000" -- Login packet header continued = Option Flags 1 & 2, status flag, reserved flag, timezone
  72.         query = query .. "\009\004\000\000\094\000\004\000" -- Login packet (Collation), then start offsets & lengths (client name, client length)
  73.         query = query .. "\102\000\002\000\000\000\000\000" -- Login packet, offsets & lengths = username offset, username length, password offset, password length
  74.         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
  75.         query = query .. "\000\000\000\000\114\000\003\000" -- Login packet, offsets & lengths = unknown offset, unknown length, library name offset, library name length
  76.         query = query .. "\120\000\000\000\120\000\000\000" -- Login packet, offsets & lengths = locale offset, locale length, database name offset, database name length
  77.         query = query .. "\000\000\000\000\000\000\000\000" -- Login packet, MAC address + padding
  78.         query = query .. "\000\000\000\000\000\000\000\000" -- Login packet, padding
  79.         query = query .. "\000\000\000\000\000\000\078\000" -- Login packet, padding + start of client name (N)
  80.         query = query .. "\077\000\065\000\080\000\115\000" -- Login packet = rest of client name (MAP) + username (s)
  81.         query = query .. "\097\000\078\000\077\000\065\000" -- Login packet = username (a), app name (NMA)
  82.         query = query .. "\080\000\078\000\083\000\069\000" -- Login packet = app name (P), library name (NSE)
  83.         
  84.         -- send the packet down the wire
  85.         try(socket:send(strbuf.dump(query)))
  86.         
  87.         -- read in any response we might get
  88.         stat, resp = socket:receive_bytes(1)
  89.       
  90.         -- successful response to login packet should contain the string "SQL Server"
  91.         -- however, the string is UCS2 encoded, so we have to add the \000 characters
  92.         if string.match(resp, "S\000Q\000L\000") then
  93.           outcome = "\n    sa user appears to have blank password"
  94.           
  95.           strbuf.clear(query)
  96.           -- since we have a successful login, send a query that will tell us what version the server is really running
  97.           query = query .. "\001\001\000\044\000\000\001\000" -- TDS Query packet
  98.           query = query .. "\083\000\069\000\076\000\069\000" -- SELE
  99.           query = query .. "\067\000\084\000\032\000\064\000" -- CT @
  100.           query = query .. "\064\000\086\000\069\000\082\000" -- @VER
  101.           query = query .. "\083\000\073\000\079\000\078\000" -- SION
  102.           query = query .. "\013\000\010\000"
  103.           
  104.           -- send the packet down the wire
  105.           try(socket:send(strbuf.dump(query)))
  106.           
  107.           -- read in any response we might get
  108.           stat, resp = socket:receive_bytes(1)
  109.           
  110.           -- strip out the embedded \000 characters
  111.           local banner = string.gsub(resp, "%z", "")
  112.           outcome = outcome .. "\n     " .. string.match(banner, "(Microsoft.-)\n")
  113.           outcome = outcome .. "\n" .. string.match(banner, "\n.-\n.-\n(.-Build.-)\n")
  114.         end
  115.         
  116.         try(socket:close())
  117.         
  118.       end -- if string.match(response, "^\004")
  119.       
  120.       if outcome == nil then
  121.         outcome = "\n    Could not retrieve actual version information"
  122.       end
  123.       
  124.       return outcome
  125.     end -- get_real_version(dst, dstPort)
  126.     
  127.     -- connect to the potential SQL server
  128.     try(socket:connect(host.ip, port.number, "udp"))
  129.     
  130.     -- send a magic packet
  131.     -- details here:  http://www.codeproject.com/cs/database/locate_sql_servers.asp
  132.     try(socket:send("\002"))
  133.     
  134.     local status
  135.     local response
  136.     
  137.     -- read in any response we might get
  138.     status, response = socket:receive_bytes(1)
  139.     
  140.     try(socket:close())
  141.  
  142.     if (not status) then
  143.         return
  144.     end
  145.  
  146.     if (response == "TIMEOUT") then
  147.         return
  148.     end
  149.     
  150.     -- since we got something back, the port is definitely open
  151.     nmap.set_port_state(host, port, "open")
  152.     
  153.     local result
  154.     
  155.     -- create a lua table to hold some information
  156.     local serverInfo = {}
  157.             
  158.     -- do some pattern matching to exract certain key elements from the response
  159.     -- the data comes back as a long semicolon separated list
  160.     
  161.     -- A single server can have multiple instances, which are separated by a double semicolon
  162.     -- cycle through each instance
  163.     local count = 1
  164.     for instance in string.gmatch(response, "(.-;;)") do
  165.       result = instance
  166.       serverInfo[count] = {}
  167.       serverInfo[count].name = string.match(instance, "ServerName;(.-);")
  168.       serverInfo[count].instanceName = string.match(instance, "InstanceName;(.-);")
  169.       serverInfo[count].clustered = string.match(instance, "IsClustered;(.-);")
  170.       serverInfo[count].version = string.match(instance, "Version;(.-);")
  171.       serverInfo[count].tcpPort = string.match(instance, ";tcp;(.-);")
  172.       serverInfo[count].namedPipe = string.match(instance, ";np;(.-);")
  173.       count = count + 1
  174.     end
  175.     
  176.     -- do some heuristics on the version to see if we can match the major releases
  177.     if string.match(serverInfo[1].version, "^6%.0") then
  178.       result = "Discovered Microsoft SQL Server 6.0"
  179.     elseif string.match(serverInfo[1].version, "^6%.5") then
  180.       result = "Discovered Microsoft SQL Server 6.5"
  181.     elseif string.match(serverInfo[1].version, "^7%.0") then
  182.       result = "Discovered Microsoft SQL Server 7.0"
  183.     elseif string.match(serverInfo[1].version, "^8%.0") then
  184.       result = "Discovered Microsoft SQL Server 2000"
  185.     elseif string.match(serverInfo[1].version, "^9%.0") then
  186.       -- The Express Edition of MS SQL Server 2005 has a default instance name of SQLEXPRESS
  187.       for _,instance in ipairs(serverInfo) do
  188.         if string.match(instance.instanceName, "SQLEXPRESS") then
  189.           result = "Discovered Microsoft SQL Server 2005 Express Edition"
  190.         end
  191.       end
  192.       if result == nil then
  193.         result = "Discovered Microsoft SQL Server 2005"
  194.       end
  195.     else
  196.       result = "Discovered Microsoft SQL Server"
  197.     end
  198.     if serverInfo[1].name ~= nil then
  199.       result = result .. "\n  Server name: " .. serverInfo[1].name
  200.     end
  201.     if serverInfo[1].version ~= nil then
  202.       result = result .. "\n  Server version: " .. serverInfo[1].version
  203.       -- Check for some well known release versions of SQL Server 2005
  204.       --  for more info, see http://support.microsoft.com/kb/321185
  205.       if string.match(serverInfo[1].version, "9.00.3042") then
  206.         result = result .. " (SP2)"
  207.       elseif string.match(serverInfo[1].version, "9.00.3043") then
  208.         result = result .. " (SP2)"
  209.       elseif string.match(serverInfo[1].version, "9.00.2047") then
  210.         result = result .. " (SP1)"
  211.       elseif string.match(serverInfo[1].version, "9.00.1399") then
  212.         result = result .. " (RTM)"
  213.       end
  214.     end
  215.     for _,instance in ipairs(serverInfo) do
  216.       if instance.instanceName ~= nil then
  217.         result = result .. "\n  Instance name: " .. instance.instanceName
  218.       end
  219.       if instance.tcpPort ~= nil then
  220.         result = result .. "\n  TCP Port: " .. instance.tcpPort
  221.         result = result .. get_real_version(host.ip, instance.tcpPort)
  222.       end
  223.     end
  224.     
  225.     return result
  226.     
  227. end
  228.  
  229.  
  230.