diff --git a/nselib/shortport.lua b/nselib/shortport.lua index 4edeaf7e2..3cf65656d 100644 --- a/nselib/shortport.lua +++ b/nselib/shortport.lua @@ -40,15 +40,17 @@ port_in_list = function(porttab, proto, ...) return false end -port_or_service = function(porttab, number, service, proto, state) - state= state or "open" - proto = proto or "tcp" - if (porttab.number==number or porttab.service==service) - and porttab.protocol==proto - and porttab.state == state - then - return true - else - return false +port_or_service = function(number, service, proto, state) + return function(host, port) + state = state or "open" + proto = proto or "tcp" + if (port.number==number or port.service==service) + and port.protocol==proto + and port.state == state + then + return true + else + return false + end end end diff --git a/scripts/MSSQLm.nse b/scripts/MSSQLm.nse new file mode 100644 index 000000000..86ed17b51 --- /dev/null +++ b/scripts/MSSQLm.nse @@ -0,0 +1,238 @@ +-- 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 " + +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 + + diff --git a/scripts/SMTPcommands.nse b/scripts/SMTPcommands.nse new file mode 100644 index 000000000..63b256684 --- /dev/null +++ b/scripts/SMTPcommands.nse @@ -0,0 +1,68 @@ +-- SMTP supported commands gathering script +-- Version History +-- 1.0.0.0 - 2007-06-12 + +-- Cribbed heavily from Thomas Buchanan's SQL version detection +-- script and from Arturo 'Buanzo' Busleiman's SMTP open relay +-- detector script. + +id = "SMTP" +description = "Attempts to use EHLO to gather the Extended commands an SMTP server supports." +author = "Jason DePriest " +license = "See nmaps COPYING for licence" +categories = {"discovery", "intrusive"} + +portrule = function(host, port) + if (port.number == 25 + or port.number == 587 + or port.number == 465 + or port.service == "smtp") + and port.state == "open" + and port.protocol == "tcp" + then + return true + else + return false + end +end + +action = function(host, port) + + local socket = nmap.new_socket() + socket:set_timeout(5000) + + local result + local commands + local mailservername + local status = true + + local catch = function() + socket:close() + end + + local try = nmap.new_try(catch) + + try(socket:connect(host.ip, port.number, port.protocol)) + result = try(socket:receive_lines(1)) + + -- ASCII for "EHLO example.org\n" + -- for some reason it wouldn't reply unless I did it like this + local query = "\069\072\076\079\032\101\120\097" + query = query .. "\109\112\108\101\046\111\114\103" + query = query .. "\013\010" + try(socket:send(query)) + result = try(socket:receive_lines(1)) + + if not string.match(result, "^250") then + socket.close() +-- TODO: use print_debug instead + return "EHLO with errors or timeout. Enable --script-trace to see what is happening." + end + + result = string.gsub(result, "\050\053\048\032\079\075\013\010", "") -- 250 OK (needed to have the \r\n in there) + result = string.gsub(result, "250%-", "") -- 250- + result = "Responded to EHLO command\n" .. result + + return result + +end diff --git a/scripts/SNMPsysdesr.nse b/scripts/SNMPsysdesr.nse new file mode 100644 index 000000000..42b1f1a21 --- /dev/null +++ b/scripts/SNMPsysdesr.nse @@ -0,0 +1,137 @@ +-- SNMP system detection script +-- rev 0.4 (6-11-2007) + +id = "SNMPv1" + +description = "Attempts to extract system information from SNMP service" + +author = "Thomas Buchanan " + +license = "See nmaps COPYING for licence" + +categories = {"discovery", "safe"} + +portrule = function(host, port) + if + port.number == 161 + 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) + + -- connect to the potential SNMP system + try(socket:connect(host.ip, port.number, "udp")) + + local payload + + -- build a SNMP v1 packet + -- copied from packet capture of snmpget exchange + -- get value: 1.3.6.1.2.1.1.1.0 (SNMPv2-MIB::sysDescr.0) + payload = "\048\039\002\001\000\004\006" .. "public" -- community string = public + payload = payload .. "\160\026\002\002\111\012\002\001" + payload = payload .. "\000\002\001\000\048\014\048\012" + payload = payload .. "\006\008\043\006\001\002\001\001" + payload = payload .. "\001\000\005\000" + + try(socket:send(payload)) + + local status + local response + + -- read in any response we might get + status, response = socket:receive_bytes(1) + + 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 + result = string.match(response, "\001\001%z\004.(.*)") + + -- build a SNMP v1 packet + -- copied from packet capture of snmpget exchange + -- get value: 1.3.6.1.2.1.1.3.0 (SNMPv2-MIB::sysUpTime.0) + payload = "\048\039\002\001\000\004\006" .. "public" -- community string = public + payload = payload .. "\160\026\002\002\101\040\002\001" + payload = payload .. "\000\002\001\000\048\014\048\012" + payload = payload .. "\006\008\043\006\001\002\001\001" + payload = payload .. "\003\000\005\000" + + try(socket:send(payload)) + + -- read in any response we might get + status, response = socket:receive_bytes(1) + + if (not status) then + return result + end + + if (response == "TIMEOUT") then + return result + end + + try(socket:close()) + + if string.find(response, "\006\001\002\001\001\003") == nil then + return result + end + + local length,uptime,s1,s2,s3,s4 + + length = string.len(response) + + s1,s2,s3,s4 = string.byte(response, length - 3, length) + + uptime = s1*(2^24) + s2*(2^16) + s3*(2^8) + s4 + + local days, hours, minutes, seconds, htime, mtime, stime + days = math.floor(uptime / 8640000) + htime = math.fmod(uptime, 8640000) + hours = math.floor(htime / 360000) + mtime = math.fmod(htime, 360000) + minutes = math.floor(mtime / 6000) + stime = math.fmod(mtime, 6000) + seconds = stime / 100 + + local dayLabel + + if days == 1 then + dayLabel = " day, " + else + dayLabel = " days, " + end + + result = result .. "\n System uptime: " .. days .. dayLabel .. hours .. ":" .. minutes .. ":" .. seconds + result = result .. " (" .. tostring(uptime) .. " timeticks)" + + return result +end + diff --git a/scripts/showHTMLTitle.nse b/scripts/showHTMLTitle.nse index 8819cf849..149d179bf 100644 --- a/scripts/showHTMLTitle.nse +++ b/scripts/showHTMLTitle.nse @@ -13,9 +13,11 @@ categories = {"demo", "safe"} require "shortport" -portrule = function(host, port) - return shortport.port_or_service(port, 80, "http") -end +portrule = shortport.port_or_service(80, "http") + +--portrule = function(host, port) +-- return shortport.port_or_service(port, 80, "http") +--end action = function(host, port) local url, socket, request, result, status, s, title