diff --git a/CHANGELOG b/CHANGELOG
index 849b11dca..8215c12a4 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,9 @@
#Nmap Changelog ($Id$); -*-text-*-
+o [NSE] memcached-info can now gather information from the UDP memcached
+ service in addition to the TCP service. The UDP service is frequently used as
+ a DDoS reflector and amplifier. [Daniel Miller]
+
o [NSE][GH#1129] Changed url.absolute() behavior with respect to dot and
dot-dot path segments to comply with RFC 3986, section 5.2. [nnposter]
diff --git a/scripts/memcached-info.nse b/scripts/memcached-info.nse
index 11ede3a6f..9bacc11ae 100644
--- a/scripts/memcached-info.nse
+++ b/scripts/memcached-info.nse
@@ -1,8 +1,10 @@
local nmap = require "nmap"
local match = require "match"
+local math = require "math"
local shortport = require "shortport"
local stdnse = require "stdnse"
-local tab = require "tab"
+local string = require "string"
+local table = require "table"
description = [[
Retrieves information (including system architecture, process ID, and
@@ -14,29 +16,40 @@ server time) from distributed memory object caching system memcached.
-- nmap -p 11211 --script memcached-info
--
-- @output
--- 11211/tcp open unknown
+-- 11211/udp open unknown
-- | memcached-info:
--- | Process ID 18568
--- | Uptime 6950 seconds
--- | Server time Sat Dec 31 14:16:10 2011
--- | Architecture 64 bit
--- | Used CPU (user) 0.172010
--- | Used CPU (system) 0.200012
--- | Current connections 10
--- | Total connections 78
--- | Maximum connections 1024
--- | TCP Port 11211
--- | UDP Port 11211
--- |_ Authentication no
+-- | Process ID: 18568
+-- | Uptime: 6950 seconds
+-- | Server time: Sat Dec 31 14:16:10 2011
+-- | Architecture: 64 bit
+-- | Used CPU (user): 0.172010
+-- | Used CPU (system): 0.200012
+-- | Current connections: 10
+-- | Total connections: 78
+-- | Maximum connections: 1024
+-- | TCP Port: 11211
+-- | UDP Port: 11211
+-- |_ Authentication: no
--
+-- @xmloutput
+-- 17307
+-- 10662 seconds
+-- 2018-03-01T16:46:59
+-- 64 bit
+-- 0.212809
+-- 0.157151
+-- 5
+-- 11
+-- 1024
+-- 11211
+-- 11211
+-- no
author = "Patrik Karlsson"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"discovery", "safe"}
-
--- currently, we only support the TCP, text based protocol
-portrule = shortport.port_or_service(11211, "memcached", "tcp")
+portrule = shortport.port_or_service(11211, "memcached", {"tcp", "udp"})
local filter = {
@@ -70,61 +83,96 @@ local function mergetab(tab1, tab2)
return tab1
end
-local function recvResponse(socket)
- local kvs = {}
- repeat
- 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"
+local Comm = {
+ new = function(self, host, port, options)
+ local o = { host = host, port = port, options = options or {}}
+ self.protocol = port.protocol
+ self.req_id = math.random(0,0xfff)
+ setmetatable(o, self)
+ self.__index = self
+ return o
+ end,
+ connect = function(self)
+ self.socket = nmap.new_socket(self.protocol)
+ self.socket:set_timeout(self.options.timeout or stdnse.get_timeout(self.host))
+ return self.socket:connect(self.host, self.port)
+ end,
+ exchange = function(self, data)
+ local req_id = self.req_id
+ self.req_id = req_id + 1
+ if self.protocol == "udp" then
+ data = string.pack(">I2 I2 I2 I2",
+ req_id, -- request ID
+ 0, -- sequence number
+ 1, -- number of datagrams
+ 0 -- reserved, must be 0
+ ) .. data
end
- local k,v = response:match("^STAT ([^%s]*) (.*)$")
- if ( k and v ) then
- kvs[k] = v
+ local status = self.socket:send(data)
+ if not status then
+ return false, "Failed to send request to server"
+ end
+ if self.protocol == "udp" then
+ local msgs = {}
+ local dgrams = 0
+ repeat
+ local status, response = self.socket:receive_bytes(8)
+ if not status then return false, "Failed to receive entire response" end
+ local resp_id, seq, ndgrams, pos = string.unpack(">I2 I2 I2 xx", response)
+ if resp_id == req_id then
+ dgrams = ndgrams
+ msgs[seq+1] = string.sub(response, pos)
+ end
+ until #msgs >= dgrams
+ return true, table.concat(msgs)
end
- until ( "END" == response or "ERROR" == response )
- return true, kvs
+ -- pattern matches ERR or ERROR at the beginning of a string or after a newline
+ return self.socket:receive_buf(match.pattern_limit("%f[^\n\0]E[NR][DR]O?R?\r\n", 2048), true)
+ end,
+}
+
+local function parseResponse(response, expected)
+ local kvs = {}
+ for k, v in response:gmatch(("%%f[^\n\0]%s ([^%%s]*) (.-)\r\n"):format(expected)) do
+ stdnse.debug1("k=%s, v=%s", k, v)
+ kvs[k] = v
+ end
+ return kvs
end
action = function(host, port)
- local socket = nmap.new_socket()
- socket:set_timeout(10000)
- local status = socket:connect(host, port)
+ local client = Comm:new(host, port)
+ local status = client:connect()
if ( not(status) ) then
return fail("Failed to connect to server")
end
- status = socket:send("stats\r\n")
+ local status, response = client:exchange("stats\r\n")
if ( not(status) ) then
- return fail("Failed to send request to server")
+ return fail(("Failed to send request to server: %s"):format(response))
end
- local status, kvs = recvResponse(socket)
- if( not(status) ) then
- return fail(kvs)
- end
+ local kvs = parseResponse(response, "STAT")
- status = socket:send("stats settings\r\n")
+ local status, response = client:exchange("stats settings\r\n")
if ( not(status) ) then
- return fail("Failed to send request to server")
+ return fail(("Failed to send request to server: %s"):format(response))
end
- local status, kvs2 = recvResponse(socket)
- if( not(status) ) then
- return fail(kvs2)
- end
+ local kvs2 = parseResponse(response, "STAT")
kvs = mergetab(kvs, kvs2)
- local result = tab.new(2)
+ local result = stdnse.output_table()
for _, item in ipairs(order) do
if ( kvs[item] ) then
local name = filter[item].name
local val = ( filter[item].func and filter[item].func(kvs[item]) or kvs[item] )
- tab.addrow(result, name, val)
+ result[name] = val
end
end
- return stdnse.format_output(true, tab.dump(result))
+ return result
end