From 475393bc2cc6382a43ec7ce0ce55c3f5f789ca69 Mon Sep 17 00:00:00 2001 From: fyodor Date: Sat, 11 Aug 2007 04:58:05 +0000 Subject: [PATCH] merge soc07 r5049:5063 - added string split/join methods; Bruteforce telnet script; fixed a few typos; updated to escape some common url constructs; refactored shorport library; Added a family of string buffer functions to nselib as concatenation is not efficient; Updated a couple of scripts to use string buffers; resolved a couple of naming conflicts --- nselib/shortport.lua | 94 ++++++++-------- nselib/stdnse.lua | 81 ++++++++++++++ nselib/url.lua | 4 + scripts/MSSQLm.nse | 71 ++++++------ scripts/bruteTelnet.nse | 219 ++++++++++++++++++++++++++++++++++++++ scripts/script.db | 1 + scripts/showHTMLTitle.nse | 12 +-- 7 files changed, 389 insertions(+), 93 deletions(-) create mode 100644 scripts/bruteTelnet.nse diff --git a/nselib/shortport.lua b/nselib/shortport.lua index 9c8a9b0ba..57d1133f3 100644 --- a/nselib/shortport.lua +++ b/nselib/shortport.lua @@ -1,62 +1,58 @@ -module(...) +module('shortport', package.seeall) -protorule = function(service, proto, state) - return function(host,port) - state = state or "open" - proto = proto or "tcp" - if port.service==service - and port.protocol == proto - and port.state == state - then - return true; - else - return false; - end - end -end +portnumber = function(port, _proto, _state) + local port_table; + local state = _state or "open" + local proto = _proto or "tcp" -portnumber = function(number, proto, state) - return function(host,port) - state = state or "open" - proto = proto or "tcp" - if port.number==number - and port.protocol == proto - and port.state ==state - then - return true; - else - return false; - end - end -end + if(type(port) == "number") then + port_table = {port} + elseif(type(port) == "table") then + port_table = port + end -port_in_list = function(proto, ...) - local list={...} - return function(host,port) - if not port.protocol==proto - then - return false - end - for _, v in ipairs(list) do - if port.number == v then - return true + return function(host, port) + if(port.protocol == proto and port.state == state) then + for _, _port in ipairs(port_table) do + if(port.number == _port) then + return true + end end end + return false end end -port_or_service = function(number, service, proto, state) +service = function(service, _proto, _state) + local service_table; + local state = _state or "open" + local proto = _proto or "tcp" + + if(type(service) == "string") then + service_table = {service} + elseif(type(service) == "table") then + service_table = service + end + 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 + if(port.protocol == proto and port.state == state) then + for _, service in ipairs(service_table) do + if(port.service == service) then + return true + end + end end + + return false + end +end + +port_or_service = function(port, _service, proto, state) + local port_checker = portnumber(port, proto, state) + local service_checker = service(_service, proto, state) + + return function(host, port) + return port_checker(host, port) or service_checker(host, port) end end diff --git a/nselib/stdnse.lua b/nselib/stdnse.lua index c7b909300..3a5d13c3d 100644 --- a/nselib/stdnse.lua +++ b/nselib/stdnse.lua @@ -9,8 +9,89 @@ print_debug = function(...) nmap.print_debug_unformatted(verbosity, string.format(unpack(arg, start))); end +-- Concat the contents of the parameter list, +-- separated by the string delimiter (just like in perl) +-- example: strjoin(", ", {"Anna", "Bob", "Charlie", "Dolores"}) +function strjoin(delimiter, list) + local len = getn(list) + if len == 0 then + return "" + end + local string = list[1] + for i = 2, len do + string = string .. delimiter .. list[i] + end + return string +end + +-- Split text into a list consisting of the strings in text, +-- separated by strings matching delimiter (which may be a pattern). +-- example: strsplit(",%s*", "Anna, Bob, Charlie,Dolores") +function strsplit(delimiter, text) + local list = {} + local pos = 1 + + if strfind("", delimiter, 1) then -- this would result in endless loops + error("delimiter matches empty string!") + end + + while 1 do + local first, last = strfind(text, delimiter, pos) + if first then -- found? + tinsert(list, strsub(text, pos, first-1)) + pos = last+1 + else + tinsert(list, strsub(text, pos)) + break + end + end + + return list +end + +-- String buffer functions. Concatenation is not efficient in +-- lua as strings are immutable. If a large amount of '..' +-- operations are needed a string buffer should be used instead + +--[[ + local buf = strbuf.new() + strbuf.add(buf, 'string') ; strbuf.add(buf, 'data') + + print(buf) -- default seperator is a new line + print(strbuf.dump(buf)) -- no seperator + print(strbuf.dump(buf, ' ')) -- seperated by spaces + strbuf.clear(buf) +--]] + +strbuf_dump = table.concat + +function strbuf_new() + local sbuf = {} + sbuf.mt = {} + setmetatable(sbuf, sbuf.mt) + sbuf.mt.__tostring = function(s) return strbuf_dump(s, '\n') end + return sbuf +end + +function strbuf_add(sbuf, s) + if not (type(s) == 'string') or + not (type(sbuf) == 'table') then + return nil + end + table.insert(sbuf, s) + return table.getn(sbuf) +end + +function strbuf_clear(sbuf) + for i, v in pairs(sbuf) do + sbuf[i] = nil + end +end + +-- pseudo namespace for string buffers +strbuf = { new=strbuf_new, add=strbuf_add, dump=strbuf_dump, clear=strbuf_clear } -- Generic buffer implementation using lexical closures -- diff --git a/nselib/url.lua b/nselib/url.lua index e5b7ae999..8ce85a1f0 100644 --- a/nselib/url.lua +++ b/nselib/url.lua @@ -315,6 +315,10 @@ function parse_query(query) local parsed = {} local pos = 0 + query = string.gsub(query, "&", "&") + query = string.gsub(query, "<", "<") + query = string.gsub(query, ">", ">") + function ginsert(qstr) local first, last = string.find(qstr, "=") if first then diff --git a/scripts/MSSQLm.nse b/scripts/MSSQLm.nse index 86ed17b51..d8a1c9f82 100644 --- a/scripts/MSSQLm.nse +++ b/scripts/MSSQLm.nse @@ -11,6 +11,8 @@ license = "See nmaps COPYING for licence" categories = {"discovery", "intrusive"} +require('stdnse') + portrule = function(host, port) if ( port.number == 1433 @@ -30,6 +32,7 @@ action = function(host, port) -- create the socket used for our connection local socket = nmap.new_socket() + local sb_add = stdnse.strbuf.add -- set a reasonable timeout value socket:set_timeout(5000) @@ -46,24 +49,24 @@ action = function(host, port) local get_real_version = function(dst, dstPort) local outcome - local payload + local payload = stdnse.strbuf.new() 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" + sb_add(payload, "\018\001\000\047\000\000\001\000\000\000") + sb_add(payload, "\026\000\006\001\000\032\000\001\002\000") + sb_add(payload, "\033\000\001\003\000\034\000\004\004\000") + sb_add(payload, "\038\000\001\255\009\000\011\226\000\000") + sb_add(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)) + try(socket:send(stdnse.strbuf.dump(payload))) -- read in any response we might get stat, resp = socket:receive_bytes(1) @@ -74,25 +77,26 @@ action = function(host, port) -- 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) + local query = stdnse.strbuf.new() + sb_add(query, "\016\001\000\128\000\000\001\000") -- TDS packet header + sb_add(query, "\120\000\000\000\002\000\009\114") -- Login packet header = length, version + sb_add(query, "\000\000\000\000\000\000\000\007") -- Login packet header continued = size, client version + sb_add(query, "\140\018\000\000\000\000\000\000") -- Login packet header continued = Client PID, Connection ID + sb_add(query, "\224\003\000\000\104\001\000\000") -- Login packet header continued = Option Flags 1 & 2, status flag, reserved flag, timezone + sb_add(query, "\009\004\000\000\094\000\004\000") -- Login packet (Collation), then start offsets & lengths (client name, client length) + sb_add(query, "\102\000\002\000\000\000\000\000") -- Login packet, offsets & lengths = username offset, username length, password offset, password length + sb_add(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 + sb_add(query, "\000\000\000\000\114\000\003\000") -- Login packet, offsets & lengths = unknown offset, unknown length, library name offset, library name length + sb_add(query, "\120\000\000\000\120\000\000\000") -- Login packet, offsets & lengths = locale offset, locale length, database name offset, database name length + sb_add(query, "\000\000\000\000\000\000\000\000") -- Login packet, MAC address + padding + sb_add(query, "\000\000\000\000\000\000\000\000") -- Login packet, padding + sb_add(query, "\000\000\000\000\000\000\078\000") -- Login packet, padding + start of client name (N) + sb_add(query, "\077\000\065\000\080\000\115\000") -- Login packet = rest of client name (MAP) + username (s) + sb_add(query, "\097\000\078\000\077\000\065\000") -- Login packet = username (a), app name (NMA) + sb_add(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)) + try(socket:send(stdnse.strbuf.dump(query))) -- read in any response we might get stat, resp = socket:receive_bytes(1) @@ -102,19 +106,20 @@ action = function(host, port) if string.match(resp, "S\000Q\000L\000") then outcome = "\n sa user appears to have blank password" + stdnse.strbuf.clear(query) -- 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" + sb_add(query, "\001\001\000\044\000\000\001\000") -- TDS Query packet + sb_add(query, "\083\000\069\000\076\000\069\000") -- SELE + sb_add(query, "\067\000\084\000\032\000\064\000") -- CT @ + sb_add(query, "\064\000\086\000\069\000\082\000") -- @VER + sb_add(query, "\083\000\073\000\079\000\078\000") -- SION + sb_add(query, "\013\000\010\000") -- send the packet down the wire - try(socket:send(query)) + try(socket:send(stdnse.strbuf.dump(query))) - -- read in any response we might get - stat, resp = socket:receive_bytes(1) + -- 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", "") diff --git a/scripts/bruteTelnet.nse b/scripts/bruteTelnet.nse new file mode 100644 index 000000000..b206b0185 --- /dev/null +++ b/scripts/bruteTelnet.nse @@ -0,0 +1,219 @@ +id='bruteforce' +author = 'Eddie Bell ' +description='brute force telnet login credientials' +license = 'See nmaps COPYING for licence' +categories = {'intrusive'} + +require('shortport') +require('stdnse') + +local soc +local catch = function() soc.close() end +local try = nmap.new_try(catch) + +portrule = shortport.port_or_service(23, 'telnet') + +local escape_cred = function(cred) + if cred == '' then + return '' + else + return cred + end +end + +--[[ +Returns a function which returns the next user/pass pair each time +it is called. When no more pairs are available nil is returned. + +There are plenty more possible pairs but we need to find +a compromise between speed and coverage +--]] + +local new_auth_iter = function() + local userpass = { + -- guest + {'guest', ''}, {'guest', 'guest'}, {'guest', 'visitor'}, + + -- root + {'root', ''}, {'root', 'root'}, + {'root', 'pass'}, {'root', 'password'}, + + -- admin + {'admin', ''}, {'admin', 'admin'}, + {'admin', 'pass'}, {'admin', 'password'}, + + -- adminstrator + {'adminstrator', ''}, {'adminstrator', 'adminstrator'}, + {'adminstrator', 'password'}, {'adminstrator', 'pass'}, + + -- others + {'visitor', ''}, {'netman', 'netman'}, {'Admin', 'Admin'}, + {'manager', 'manager'}, {'security', 'security'}, + {'username', 'password'}, {'user', 'pass'}, + + -- sentinel + {nil, nil} + } + + local i = 1 + return function(direction) + if not userpass[i][1] then + return + end + + i = i + 1 + stdnse.print_debug(3, id .. " " .. + userpass[i-1][1] .. ":" .. escape_cred(userpass[i-1][2])) + return userpass[i-1][1], userpass[i-1][2] + end +end + +--[[ +Go through telnet's option palaver so we can get to the login prompt. +We just deny every options the server asks us about. +--]] + +local negotiate_options = function(result) + local index, x, opttype, opt, retbuf + + index = 0 + retbuf = stdnse.strbuf.new() + + while true do + + -- 255 is IAC (Interpret As Command) + index, x = string.find(result, '\255', index) + + if not index then + break + end + + opttype = string.byte(result, index+1) + opt = string.byte(result, index+2) + + -- don't want it! won't do it! + if opttype == 251 or opttype == 252 then + opttype = 254 + elseif opttype == 253 or opttype == 254 then + opttype = 252 + end + + stdnse.strbuf.add(retbuf, string.char(255)) + stdnse.strbuf.add(retbuf, string.char(opttype)) + stdnse.strbuf.add(retbuf, string.char(opt)) + index = index + 1 + end + soc:send(stdnse.strbuf.dump(retbuf)) +end + +--[[ +A semi-state-machine that takes action based on output from the +server. Through pattern matching, it tries to deem if a user/pass +pair is valid. Telnet does not have a way of telling the client +if it was authenticated....so we have to make an educated guess +--]] + +local brute_line = function(line, user, pass, usent) + + if (line:find 'incorrect' or line:find 'failed' or line:find 'denied' or + line:find 'invalid' or line:find 'bad') and usent then + usent = false + return 2, nil, usent + + elseif (line:find '[/>%%%$#]+' or line:find "last login%s*:" or + line:find '%u:\\') and not + (line:find 'username%s*:' and line:find 'login%s*:') and + usent then + return 1, escape_cred(user) .. ' - ' .. escape_cred(pass) .. '\n', usent + + elseif line:find 'username%s*:' or line:find 'login%s*:' then + try(soc:send(user .. '\r\0')) + usent = true + + elseif line:find 'password%s*:' or line:find 'passcode%s*:' then + -- fix, add 'password only' support + if not usent then return 1, nil, usent end + try(soc:send(pass .. '\r\0')) + end + + return 0, nil, usent +end + +--[[ +Splits the input into lines and passes it to brute_line() +so it can try to login with and + +return value: + (1, user:pass) - valid pair + (2, nil) - invalid pair + (3, nil) - disconnected and invalid pair + (4, nil) - disconnected and didn't send pair +--]] + +local brute_cred = function(user, pass) + local status, ret, value, usent, results + + usent = false ; ret = 0 + + while true do + status, results = soc:receive_lines(1) + + -- remote host disconnected + if not status then + if usent then return 3 + else return 4 + end + end + + if (string.byte(results, 1) == 255) then + negotiate_options(results) + end + + results = string.lower(results) + + for line in results:gmatch '[^\r\n]+' do + ret, value, usent = brute_line(line, user, pass, usent) + if (ret > 0) then + return ret, value + end + end + end + return 1, "error -> this should never happen" +end + +action = function(host, port) + local pair, status, auth_iter + local user, pass, count, rbuf + + pair = nil ; status = 3 ; count = 0 + auth_iter = new_auth_iter(); + + soc = nmap.new_socket() + soc:set_timeout(4000) + + -- continually try user/pass pairs (reconnecting, if we have to) + -- until we find a valid one or we run out of pairs + while not (status == 1) do + + if status == 2 or status == 3 then + user, pass = auth_iter() + end + + -- make sure we don't get stuck in a loop + if status == 4 then + count = count + 1 + if count > 3 then return nil end + else count = 0 end + + -- no more users left + if not user then break end + + if status == 3 or status == 4 then + try(soc:connect(host.ip, port.number, port.protocol)) + end + + status, pair = brute_cred(user, pass) + end + soc:close() + return pair +end diff --git a/scripts/script.db b/scripts/script.db index 7bdbc693e..c1a83f498 100644 --- a/scripts/script.db +++ b/scripts/script.db @@ -32,3 +32,4 @@ Entry{ category = "discovery", filename = "SMTPcommands.nse" } Entry{ category = "intrusive", filename = "SMTPcommands.nse" } Entry{ category = "malware", filename = "kibuvDetection.nse" } Entry{ category = "discovery", filename = "ircServerInfo.nse" } +Entry{ category = "intrusive", filename = "bruteTelnet.nse" } diff --git a/scripts/showHTMLTitle.nse b/scripts/showHTMLTitle.nse index d9eb34b05..24ca0f09b 100644 --- a/scripts/showHTMLTitle.nse +++ b/scripts/showHTMLTitle.nse @@ -14,17 +14,7 @@ categories = {"demo", "safe"} require "shortport" require "stdnse" -portrule = function(host, port) - if ( port.service=='http' - or port.service=='https' ) - and port.protocol == 'tcp' - and port.state == 'open' - then - return true; - else - return false; - end -end +portrule = shortport.service({'http', 'https'}) action = function(host, port) local url, socket, request, result, status, s, title, protocol