diff --git a/CHANGELOG b/CHANGELOG index 78364e498..d9cc2484d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,11 @@ # Nmap Changelog ($Id$); -*-text-*- +o [NSE] Fixed several potential hangs in NSE scripts that used + receive_buf(pattern), which will not return if the service continues to send + data that does not match pattern. A new function in match.lua, pattern_limit, + is introduced to limit the number of bytes consumed while searching for the + pattern. [Daniel Miller, Jacek Wielemborek] + o [NSE] The HTTP response object has a new member, fragment, which contains a partially received body (if any) when the overall request fails to complete. [nnposter] diff --git a/nselib/imap.lua b/nselib/imap.lua index d5dcd3823..9e786a684 100644 --- a/nselib/imap.lua +++ b/nselib/imap.lua @@ -27,6 +27,7 @@ local base64 = require "base64" local comm = require "comm" +local match = require "match" local sasl = require "sasl" local stdnse = require "stdnse" local table = require "table" @@ -61,7 +62,7 @@ IMAP = { receive = function(self) local data = "" repeat - local status, tmp = self.socket:receive_buf("\r\n", false) + local status, tmp = self.socket:receive_buf(match.pattern_limit("\r\n", 1024), false) if( not(status) ) then return false, tmp end data = data .. tmp until( tmp:match(("^A%04d"):format(self.counter - 1)) or tmp:match("^%+")) diff --git a/nselib/match.lua b/nselib/match.lua index b24922857..a514cf8da 100644 --- a/nselib/match.lua +++ b/nselib/match.lua @@ -6,6 +6,7 @@ -- @copyright Same as Nmap--See https://nmap.org/book/man-legal.html local stdnse = require "stdnse" +local find = (require "string").find _ENV = stdnse.module("match", stdnse.seeall) --various functions for use with NSE's nsock:receive_buf - function @@ -38,5 +39,24 @@ numbytes = function(num) end end +--- Search for a pattern within a set number of bytes +-- +-- This function behaves just like passing a pattern to receive_buf, but it +-- will only receive a predefined number of bytes before returning the buffer. +-- @param pattern The pattern to search for +-- @param within The number of bytes to consume +-- @usage sock:receive_buf(match.pattern_limit("\r\n", 80), true) +pattern_limit = function (pattern, within) + local n = within + return function(buf) + local left, right = find(buf, pattern) + if left then + return left, right + elseif #buf >= n then + return n, n + end + return nil + end +end return _ENV; diff --git a/nselib/nmap.luadoc b/nselib/nmap.luadoc index c6d8b009f..3dd204eeb 100644 --- a/nselib/nmap.luadoc +++ b/nselib/nmap.luadoc @@ -615,6 +615,12 @@ function receive_bytes(n) -- matching against regular expressions or byte counts. These functions are -- suitable as arguments to receive_buf. -- +-- NOTE: If a pattern is used, receive_buf will continue to receive data until +-- the pattern matches or there is a timeout. If the service never stops +-- sending non-matching data, receive_buf will never return. Using +-- match.pattern_limit can avoid this by imposing a limit on how +-- many bytes to read before returning the entire non-matching buffer. +-- -- The second argument to receive_buf is a Boolean value -- controlling whether the delimiting string is returned along with the -- received data (true) or discarded (false). diff --git a/nselib/pop3.lua b/nselib/pop3.lua index 83bdb6ab7..b92148ae1 100644 --- a/nselib/pop3.lua +++ b/nselib/pop3.lua @@ -5,6 +5,7 @@ local base64 = require "base64" local comm = require "comm" +local match = require "match" local stdnse = require "stdnse" local string = require "string" local table = require "table" @@ -163,7 +164,7 @@ function capabilities(host, port) return nil, "Failed to send" end - status, line = socket:receive_buf("%.", false) + status, line = socket:receive_buf(match.pattern_limit("%.", 2048), false) if( not(status) ) then return nil, "Failed to receive" end diff --git a/nselib/redis.lua b/nselib/redis.lua index 3c588185a..b88d5e2ef 100644 --- a/nselib/redis.lua +++ b/nselib/redis.lua @@ -49,7 +49,7 @@ Response = { end, receive = function(self) - local status, data = self.socket:receive_buf("\r\n", false) + local status, data = self.socket:receive_buf(match.pattern_limit("\r\n", 2048), false) if ( not(status) ) then return false, "Failed to receive data from server" end @@ -83,7 +83,7 @@ Response = { return false, "Failed to receive data from server" end -- move past the terminal CRLF - local status, crlf = self.socket:receive_buf("\r\n", false) + local status, crlf = self.socket:receive_buf(match.pattern_limit("\r\n", 2048), false) return true, { data = data, type = Response.Type.BULK } end @@ -95,12 +95,12 @@ Response = { for i=1, count do -- peel of the length - local status = self.socket:receive_buf("\r\n", false) + local status = self.socket:receive_buf(match.pattern_limit("\r\n", 2048), false) if( not(status) ) then return false, "Failed to receive data from server" end - status, data = self.socket:receive_buf("\r\n", false) + status, data = self.socket:receive_buf(match.pattern_limit("\r\n", 2048), false) if( not(status) ) then return false, "Failed to receive data from server" end diff --git a/nselib/rsync.lua b/nselib/rsync.lua index c83971249..ac06c27ab 100644 --- a/nselib/rsync.lua +++ b/nselib/rsync.lua @@ -39,7 +39,7 @@ Helper = { if ( not(status) ) then return false, err end - local status, data = self.socket:receive_buf("\n", false) + local status, data = self.socket:receive_buf(match.pattern_limit("\n", 2048), false) if( not(status) ) then return false, err end @@ -119,7 +119,7 @@ Helper = { local modules = {} while(true) do - status, data = self.socket:receive_buf("\n", false) + status, data = self.socket:receive_buf(match.pattern_limit("\n", 2048), false) if (not(status)) then return false, data end diff --git a/nselib/vnc.lua b/nselib/vnc.lua index a48bbb006..1df546809 100644 --- a/nselib/vnc.lua +++ b/nselib/vnc.lua @@ -175,7 +175,7 @@ VNC = { -- @return status, true on success, false on failure -- @return error string containing error message if status is false handshake = function(self) - local status, data = self.socket:receive_buf("[\r\n]+", true) + local status, data = self.socket:receive_buf(match.pattern_limit("[\r\n]+", 16), true) if not status or not string.match(data, "^RFB %d%d%d%.%d%d%d[\r\n]") then stdnse.debug1("ERROR: Not a VNC port. Banner: %s", data) return false, "Not a VNC port." diff --git a/nselib/xmpp.lua b/nselib/xmpp.lua index a12f78a20..2ec1fb075 100644 --- a/nselib/xmpp.lua +++ b/nselib/xmpp.lua @@ -32,6 +32,7 @@ -- CRAM-MD5 and LOGIN local base64 = require "base64" +local match = require "match" local nmap = require "nmap" local sasl = require "sasl" local stdnse = require "stdnse" @@ -94,7 +95,7 @@ TagProcessor = { if ( tag.finish ) then return true end local newtag repeat - local status, data = socket:receive_buf(">", true) + local status, data = socket:receive_buf(match.pattern_limit(">", 2048), true) if ( not(status) ) then return false, ("ERROR: Failed to process %s tag"):format(tag.name) end @@ -105,7 +106,7 @@ TagProcessor = { end, ["challenge"] = function(socket, tag) - local status, data = socket:receive_buf(">", true) + local status, data = socket:receive_buf(match.pattern_limit(">", 2048), true) if ( not(status) ) then return false, "ERROR: Failed to read challenge tag" end local tag = XML.parse_tag(data) @@ -174,7 +175,7 @@ XMPP = { receive_tag = function(self, tag, close) local result repeat - local status, data = self.socket:receive_buf(">", true) + local status, data = self.socket:receive_buf(match.pattern_limit(">", 2048), true) if ( not(status) ) then return false, data end result = XML.parse_tag(data) until( ( not(tag) and (close == nil or result.finish == close ) ) or diff --git a/scripts/dict-info.nse b/scripts/dict-info.nse index b03b47318..5dfbb40f9 100644 --- a/scripts/dict-info.nse +++ b/scripts/dict-info.nse @@ -1,4 +1,5 @@ local nmap = require "nmap" +local match = require "match" local shortport = require "shortport" local stdnse = require "stdnse" local table = require "table" @@ -58,7 +59,7 @@ action = function(host, port) local srvinfo repeat - local status, data = socket:receive_buf("\r\n", false) + local status, data = socket:receive_buf(match.pattern_limit("\r\n", 2048), false) if ( not(status) ) then return fail("Failed to read response from server") elseif ( data:match("^5") ) then diff --git a/scripts/distcc-cve2004-2687.nse b/scripts/distcc-cve2004-2687.nse index 747aeee61..525bc740c 100644 --- a/scripts/distcc-cve2004-2687.nse +++ b/scripts/distcc-cve2004-2687.nse @@ -1,4 +1,5 @@ local nmap = require "nmap" +local match = require "match" local shortport = require "shortport" local stdnse = require "stdnse" local vulns = require "vulns" @@ -95,7 +96,8 @@ earlier. The vulnerability is the consequence of weak service configuration. end end - local status, data = socket:receive_buf("DOTO00000000", false) + -- Command could have lots of output, need to cut it off somewhere. 4096 should be enough. + local status, data = socket:receive_buf(match.pattern_limit("DOTO00000000", 4096), false) if ( status ) then local output = data:match("SOUT%w%w%w%w%w%w%w%w(.*)") diff --git a/scripts/ftp-anon.nse b/scripts/ftp-anon.nse index c1de3971d..7896bd659 100644 --- a/scripts/ftp-anon.nse +++ b/scripts/ftp-anon.nse @@ -1,4 +1,5 @@ local ftp = require "ftp" +local match = require "match" local nmap = require "nmap" local shortport = require "shortport" local stdnse = require "stdnse" @@ -84,7 +85,7 @@ local function list(socket, target, max_lines) local listing = {} while not max_lines or #listing < max_lines do - local status, data = list_socket:receive_buf("\r?\n", false) + local status, data = list_socket:receive_buf(match.pattern_limit("\r?\n", 2048), false) if (not status and data == "EOF") or data == "" then break end diff --git a/scripts/gkrellm-info.nse b/scripts/gkrellm-info.nse index 8a6b1fe50..93c02eea6 100644 --- a/scripts/gkrellm-info.nse +++ b/scripts/gkrellm-info.nse @@ -1,4 +1,5 @@ local math = require "math" +local match = require "match" local nmap = require "nmap" local shortport = require "shortport" local stdnse = require "stdnse" @@ -140,11 +141,11 @@ action = function(host, port) end -- If there's an error we get a response back, and only then - local status, data = socket:receive_buf("\n", false) + local status, data = socket:receive_buf(match.pattern_limit("\n", 2048), false) if( status and data ~= "" ) then return fail("An unknown error occurred, aborting ...") elseif ( status ) then - status, data = socket:receive_buf("\n", false) + status, data = socket:receive_buf(match.pattern_limit("\n", 2048), false) if ( status ) then return fail(data) else @@ -157,7 +158,7 @@ action = function(host, port) end local tags = {} - local status, tag = socket:receive_buf("\n", false) + local status, tag = socket:receive_buf(match.pattern_limit("\n", 2048), false) while(true) do if ( not(status) ) then break @@ -175,7 +176,7 @@ action = function(host, port) while(true) do local data - status, data = socket:receive_buf("\n", false) + status, data = socket:receive_buf(match.pattern_limit("\n", 2048), false) if ( not(status) ) then break end diff --git a/scripts/gpsd-info.nse b/scripts/gpsd-info.nse index 20e85fa03..c7fc63075 100644 --- a/scripts/gpsd-info.nse +++ b/scripts/gpsd-info.nse @@ -1,4 +1,5 @@ local gps = require "gps" +local match = require "match" local nmap = require "nmap" local os = require "os" local shortport = require "shortport" @@ -79,7 +80,7 @@ action = function(host, port) repeat local entry - status, line = socket:receive_buf("\r\n", false) + status, line = socket:receive_buf(match.pattern_limit("\r\n", 2048), false) if ( status ) then status, entry = gps.NMEA.parse(line) if ( status ) then diff --git a/scripts/icap-info.nse b/scripts/icap-info.nse index cdbfe87dd..501171602 100644 --- a/scripts/icap-info.nse +++ b/scripts/icap-info.nse @@ -1,4 +1,5 @@ local nmap = require "nmap" +local match = require "match" local shortport = require "shortport" local stdnse = require "stdnse" local table = require "table" @@ -97,7 +98,7 @@ action = function(host, port) return fail("Failed to send request to server") end - local status, resp = socket:receive_buf("\r\n\r\n", false) + local status, resp = socket:receive_buf(match.pattern_limit("\r\n\r\n", 2048), false) if ( not(status) ) then return fail("Failed to receive response from server") end diff --git a/scripts/irc-brute.nse b/scripts/irc-brute.nse index ed320decc..5405b89a9 100644 --- a/scripts/irc-brute.nse +++ b/scripts/irc-brute.nse @@ -1,6 +1,7 @@ local brute = require "brute" local comm = require "comm" local creds = require "creds" +local match = require "match" local shortport = require "shortport" local stdnse = require "stdnse" @@ -69,7 +70,7 @@ Driver = { end repeat - local status, response = self.socket:receive_buf("\r?\n", false) + local status, response = self.socket:receive_buf(match.pattern_limit("\r?\n", 2048), false) -- we check for the RPL_WELCOME message, if we don't see it, -- we failed to authenticate if ( status and response:match("^:.-%s(%d*)%s") == "001" ) then @@ -96,7 +97,7 @@ local function needsPassword(host, port) local err, code repeat - local status, response = s:receive_buf("\r?\n", false) + local status, response = s:receive_buf(match.pattern_limit("\r?\n", 2048), false) if ( status ) then code = tonumber(response:match("^:.-%s(%d*)%s")) -- break after first code diff --git a/scripts/memcached-info.nse b/scripts/memcached-info.nse index 8c9dda4e9..11ede3a6f 100644 --- a/scripts/memcached-info.nse +++ b/scripts/memcached-info.nse @@ -1,4 +1,5 @@ local nmap = require "nmap" +local match = require "match" local shortport = require "shortport" local stdnse = require "stdnse" local tab = require "tab" @@ -72,7 +73,7 @@ end local function recvResponse(socket) local kvs = {} repeat - local status, response = socket:receive_buf("\r\n", false) + local status, response = socket:receive_buf(match.pattern_limit("\r\n", 2048), false) if ( not(status) ) then return false, "Failed to receive response from server" end diff --git a/scripts/metasploit-xmlrpc-brute.nse b/scripts/metasploit-xmlrpc-brute.nse index 331dd38b5..4e0b8adaf 100644 --- a/scripts/metasploit-xmlrpc-brute.nse +++ b/scripts/metasploit-xmlrpc-brute.nse @@ -1,6 +1,7 @@ local brute = require "brute" local comm = require "comm" local creds = require "creds" +local match = require "match" local nmap = require "nmap" local shortport = require "shortport" local stdnse = require "stdnse" @@ -62,7 +63,7 @@ Driver = -- Create a buffer and receive the first line local response - status, response = self.socket:receive_buf("\r?\n", false) + status, response = self.socket:receive_buf(match.pattern_limit("\r?\n", 2048), false) if (response == nil or string.match(response,"faultStringauthentication error")) then stdnse.debug2("Bad login: %s/%s", username, password) diff --git a/scripts/mmouse-brute.nse b/scripts/mmouse-brute.nse index 9dc254637..0d83ee62a 100644 --- a/scripts/mmouse-brute.nse +++ b/scripts/mmouse-brute.nse @@ -1,5 +1,6 @@ local brute = require "brute" local creds = require "creds" +local match = require "match" local nmap = require "nmap" local shortport = require "shortport" local stdnse = require "stdnse" @@ -66,7 +67,7 @@ Driver = { return false, err end - local status, data = self.socket:receive_buf("\04", true) + local status, data = self.socket:receive_buf(match.pattern_limit("\04", 2048), true) if (data:match("^CONNECTED\30([^\30]*)") == "NO" ) then return false, brute.Error:new( "Incorrect password" ) diff --git a/scripts/mmouse-exec.nse b/scripts/mmouse-exec.nse index 5e3e088c6..d45339a80 100644 --- a/scripts/mmouse-exec.nse +++ b/scripts/mmouse-exec.nse @@ -1,4 +1,5 @@ local creds = require "creds" +local match = require "match" local nmap = require "nmap" local shortport = require "shortport" local stdnse = require "stdnse" @@ -52,7 +53,7 @@ portrule = shortport.port_or_service(51010, "mmouse", "tcp") local function receiveData(socket, cmd) local status, data = "" repeat - status, data = socket:receive_buf("\04", true) + status, data = socket:receive_buf(match.pattern_limit("\04", 2048), true) if ( not(status) ) then return false, "Failed to receive data from server" end diff --git a/scripts/nessus-brute.nse b/scripts/nessus-brute.nse index 11a6fbeca..2c4a5a919 100644 --- a/scripts/nessus-brute.nse +++ b/scripts/nessus-brute.nse @@ -1,5 +1,6 @@ local brute = require "brute" local creds = require "creds" +local match = require "match" local nmap = require "nmap" local shortport = require "shortport" @@ -65,7 +66,7 @@ Driver = end local line - status, line = self.socket:receive_buf("\r?\n", false) + status, line = self.socket:receive_buf(match.pattern_limit("\r?\n", 2048), false) if ( not(status) or line ~= "< NTP/1.2 >" ) then local err = brute.Error:new( "The server failed to respond to handshake" ) err:setAbort( true ) diff --git a/scripts/openvas-otp-brute.nse b/scripts/openvas-otp-brute.nse index d3c3e77ba..0e194211a 100644 --- a/scripts/openvas-otp-brute.nse +++ b/scripts/openvas-otp-brute.nse @@ -1,5 +1,6 @@ local brute = require "brute" local creds = require "creds" +local match = require "match" local nmap = require "nmap" local shortport = require "shortport" local stdnse = require "stdnse" @@ -59,7 +60,7 @@ Driver = end local response - status, response = self.socket:receive_buf("\r?\n", false) + status, response = self.socket:receive_buf(match.pattern_limit("\r?\n", 2048), false) if ( not(status) or response ~= "< OTP/1.0 >" ) then local err = brute.Error:new( "Bad handshake from server: "..response ) err:setAbort(true) @@ -82,7 +83,7 @@ Driver = -- Create a buffer and receive the first line local line - status, line = self.socket:receive_buf("\r?\n", false) + status, line = self.socket:receive_buf(match.pattern_limit("\r?\n", 2048), false) if (line == nil or string.match(line,"Bad login")) then stdnse.debug2("Bad login: %s/%s", username, password) diff --git a/scripts/servicetags.nse b/scripts/servicetags.nse index 63efb53c3..35188ab27 100644 --- a/scripts/servicetags.nse +++ b/scripts/servicetags.nse @@ -1,4 +1,5 @@ local nmap = require "nmap" +local match = require "match" local os = require "os" local shortport = require "shortport" local stdnse = require "stdnse" @@ -210,7 +211,7 @@ function get_agent(host, port, output) socket:close() return nil, err end - status, response = socket:receive_buf("", true) + status, response = socket:receive_buf(match.pattern_limit("", 2048), true) if not status then socket:close() return nil, response @@ -242,7 +243,7 @@ function get_svctag_list(host, port) socket:close() return nil, err end - status, response = socket:receive_buf("", true) + status, response = socket:receive_buf(match.pattern_limit("", 2048), true) if not status then socket:close() return nil, response @@ -272,7 +273,7 @@ function get_svctag(host, port, svctag) socket:close() return nil, err end - status, response = socket:receive_buf("", true) + status, response = socket:receive_buf(match.pattern_limit("", 2048), true) if not status then socket:close() return nil, response diff --git a/scripts/telnet-brute.nse b/scripts/telnet-brute.nse index 8b2ec2f1b..8de02aaaf 100644 --- a/scripts/telnet-brute.nse +++ b/scripts/telnet-brute.nse @@ -1,6 +1,7 @@ local comm = require "comm" local coroutine = require "coroutine" local creds = require "creds" +local match = require "match" local nmap = require "nmap" local shortport = require "shortport" local stdnse = require "stdnse" @@ -258,7 +259,7 @@ end Connection.methods.get_line = function (self) if self.buffer:len() == 0 then -- refill the buffer - local status, data = self.socket:receive_buf("[\r\n:>%%%$#\255].*", true) + local status, data = self.socket:receive_buf(match.pattern_limit("[\r\n:>%%%$#\255].*", 2048), true) if not status then -- connection error self.error = data diff --git a/scripts/vmauthd-brute.nse b/scripts/vmauthd-brute.nse index 8c91983d7..8d1c42fb5 100644 --- a/scripts/vmauthd-brute.nse +++ b/scripts/vmauthd-brute.nse @@ -1,5 +1,6 @@ local brute = require "brute" local creds = require "creds" +local match = require "match" local nmap = require "nmap" local shortport = require "shortport" local stdnse = require "stdnse" @@ -45,7 +46,7 @@ Driver = { end, login = function(self, username, password) - local status, line = self.socket:receive_buf("\r\n", false) + local status, line = self.socket:receive_buf(match.pattern_limit("\r\n", 2048), false) if ( line:match("^220 VMware Authentication Daemon.*SSL Required") ) then self.socket:reconnect_ssl() end @@ -57,7 +58,7 @@ Driver = { return false, err end - local status, response = self.socket:receive_buf("\r\n", false) + local status, response = self.socket:receive_buf(match.pattern_limit("\r\n", 2048), false) if ( not(status) or not(response:match("^331") ) ) then local err = brute.Error:new( "Received unexpected response from server" ) err:setRetry( true ) @@ -70,7 +71,7 @@ Driver = { err:setRetry( true ) return false, err end - status, response = self.socket:receive_buf("\r\n", false) + status, response = self.socket:receive_buf(match.pattern_limit("\r\n", 2048), false) if ( response:match("^230") ) then return true, creds.Account:new(username, password, creds.State.VALID) @@ -93,7 +94,7 @@ local function checkAuthd(host, port) return false, "Failed to connect to server" end - local status, line = socket:receive_buf("\r\n", false) + local status, line = socket:receive_buf(match.pattern_limit("\r\n", 2048), false) socket:close() if ( not(status) ) then return false, "Failed to receive response from server" diff --git a/scripts/xmpp-info.nse b/scripts/xmpp-info.nse index 7019c6c52..dc7e3d16e 100644 --- a/scripts/xmpp-info.nse +++ b/scripts/xmpp-info.nse @@ -1,3 +1,4 @@ +local match = require "match" local nmap = require "nmap" local shortport = require "shortport" local stdnse = require "stdnse" @@ -201,7 +202,7 @@ local id_database = { } local receive_tag = function(conn) - local status, data = conn:receive_buf(">", true) + local status, data = conn:receive_buf(match.pattern_limit(">", 256), true) if data then stdnse.debug2("%s", data) end return status and xmpp.XML.parse_tag(data) end