From 7b551b590978a891c1fc0934a7cd44f0e0150326 Mon Sep 17 00:00:00 2001 From: dmiller Date: Thu, 30 May 2024 17:57:53 +0000 Subject: [PATCH] Fix #2852: add TLS support to redis.lua, better detection with -sV --- nmap-service-probes | 8 ++-- nselib/redis.lua | 91 +++++++++++++++++++++++++++++++++++++++------ 2 files changed, 85 insertions(+), 14 deletions(-) diff --git a/nmap-service-probes b/nmap-service-probes index 8435c7b1e..762f52259 100644 --- a/nmap-service-probes +++ b/nmap-service-probes @@ -13688,7 +13688,7 @@ softmatch ftp m|^220[\s-].*ftp[^\r]*\r\n214[\s-]|i # TLSv1-only servers, based on a failed handshake alert. Probe TCP SSLSessionReq q|\x16\x03\0\0S\x01\0\0O\x03\0?G\xd7\xf7\xba,\xee\xea\xb2`~\xf3\0\xfd\x82{\xb9\xd5\x96\xc8w\x9b\xe6\xc4\xdb<=\xdbo\xef\x10n\0\0(\0\x16\0\x13\0\x0a\0f\0\x05\0\x04\0e\0d\0c\0b\0a\0`\0\x15\0\x12\0\x09\0\x14\0\x11\0\x08\0\x06\0\x03\x01\0| rarity 1 -ports 261,271,322,324,443,444,448,465,548,563,585,636,684,853,989,990,992-995,1241,1311,1443,2000,2221,2252,2376,2443,3443,4433,4443,4444,4911,5061,5443,5550,5868,5986,6251,6443,6679,6697,7000,7210,7272,7443,8009,8181,8194,8443,8531,8883,9001,9443,10443,14443,15002,44443,60443 +ports 261,271,322,324,443,444,448,465,548,563,585,636,684,853,989,990,992-995,1241,1311,1443,2000,2221,2252,2376,2443,3443,4433,4443,4444,4911,5061,5443,5550,5868,5986,6251,6380,6443,6679,6697,7000,7210,7272,7443,8009,8181,8194,8443,8531,8883,9001,9443,10443,14443,15002,16379,44443,60443 fallback GetRequest # Unknown service on Vingtor-Stentofon IP intercom echoes only up to the first \n, so softmatching until we know more. @@ -16304,8 +16304,10 @@ match pc-duo-gw m|^.........(.*)\0|s p/Vector PC-Duo Gateway Server/ i/Servernam # Redis key-value store Probe TCP redis-server q|*1\r\n$4\r\ninfo\r\n| rarity 8 -ports 6379 -match redis m|-ERR operation not permitted\r\n|s p/Redis key-value store/ cpe:/a:redislabs:redis/ +ports 6379,6380,16379 +sslports 6380,16379 +match redis m|^-ERR operation not permitted\r\n| p/Redis key-value store/ cpe:/a:redislabs:redis/ +match redis m|^-NOAUTH Authentication required.\r\n| p/Redis key-value store/ cpe:/a:redislabs:redis/ match redis m|^\$\d+\r\n(?:#[^\r\n]*\r\n)*redis_version:([.\d]+)\r\n|s p/Redis key-value store/ v/$1/ cpe:/a:redislabs:redis:$1/ ##############################NEXT PROBE############################## diff --git a/nselib/redis.lua b/nselib/redis.lua index fab919059..39d852875 100644 --- a/nselib/redis.lua +++ b/nselib/redis.lua @@ -6,6 +6,7 @@ local match = require "match" local nmap = require "nmap" local stdnse = require "stdnse" local table = require "table" +local comm = require "comm" _ENV = stdnse.module("redis", stdnse.seeall) Request = { @@ -30,6 +31,63 @@ Request = { } +local socket_wrapper = { + new = function(self, socket, init) + local o = { + socket = socket, + init = init, + pos = init and 1 or nil, + } + setmetatable (o,self) + self.__index = self + return o + end, + + getline = function(self) + if self.pos then + local oldpos = self.pos + local first, last = self.init:find("\r\n", oldpos) + if first then + stdnse.debug1("getline: found line: %s", self.init:sub(oldpos, first-1)) + self.pos = last < #self.init and (last + 1) or nil + return true, self.init:sub(oldpos, first-1) + else + stdnse.debug1("getline: no line found: %s", self.init:sub(oldpos)) + self.pos = nil + local status, more = self.socket:receive_buf(match.pattern_limit("\r\n", 2048), false) + if not status then + return status, more + end + return true, self.init:sub(oldpos) .. more + end + end + return self.socket:receive_buf(match.pattern_limit("\r\n", 2048), false) + end, + + getbytes = function(self, len) + if self.pos then + local remains = #self.init - self.pos + 1 + stdnse.debug1("getbytes(%d), remains=%d", len, remains) + if remains == len then + self.pos = nil + return true, self.init:sub(-len) + elseif remains > len then + local part = self.init:sub(self.pos, self.pos + len - 1) + self.pos = self.pos + len + return true, part + else + local part = self.init:sub(self.pos) + self.pos = nil + local status, more = self.socket:receive_buf(match.numbytes(len - #part), false) + if not status then + return status, more + end + return true, part .. more + end + end + return self.socket:receive_buf(match.numbytes(len), true) + end, +} Response = { @@ -48,8 +106,10 @@ Response = { return o end, - receive = function(self) - local status, data = self.socket:receive_buf(match.pattern_limit("\r\n", 2048), false) + receive = function(self, init) + stdnse.debug1("Response.receive(%d)", #(init or "")) + local sock = socket_wrapper:new(self.socket, init) + local status, data = sock:getline() if ( not(status) ) then return false, "Failed to receive data from server" end @@ -78,12 +138,12 @@ Response = { local len = tonumber(data:match("^%$(%d*)")) -- we should only have a single line, so we can just peel of the length - status, data = self.socket:receive_buf(match.numbytes(len), true) + status, data = sock:getbytes(len) if( not(status) ) then return false, "Failed to receive data from server" end -- move past the terminal CRLF - local status, crlf = self.socket:receive_buf(match.pattern_limit("\r\n", 2048), false) + local status, crlf = sock:getline() return true, { data = data, type = Response.Type.BULK } end @@ -95,12 +155,12 @@ Response = { for i=1, count do -- peel of the length - local status = self.socket:receive_buf(match.pattern_limit("\r\n", 2048), false) + local status = sock:getline() if( not(status) ) then return false, "Failed to receive data from server" end - status, data = self.socket:receive_buf(match.pattern_limit("\r\n", 2048), false) + status, data = sock:getline() if( not(status) ) then return false, "Failed to receive data from server" end @@ -125,18 +185,27 @@ Helper = { return o end, - connect = function(self, socket) - self.socket = socket or nmap.new_socket() - return self.socket:connect(self.host, self.port) + connect = function(self) + return true + end, + + do_send = function(self, payload) + local response + if not self.socket then + self.socket, response = comm.tryssl(self.host, self.port, payload) + return not not self.socket, response + else + return self.socket:send(payload) + end end, reqCmd = function(self, cmd, ...) local req = Request:new(cmd, ...) - local status, err = self.socket:send(tostring(req)) + local status, err_or_response = self:do_send(tostring(req)) if (not(status)) then return false, "Failed to send command to server" end - return Response:new(self.socket):receive() + return Response:new(self.socket):receive(err_or_response) end, close = function(self)