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