mirror of
https://github.com/nmap/nmap.git
synced 2025-12-16 04:39:03 +00:00
o [NSE] Added the script redis-info that lists version and statistic information
gathered from the Redis network key-value store. [Patrik] o [NSE] Added the script redis-brute that performs brute force password guessing against the Redis network key-value store. [Patrik]
This commit is contained in:
@@ -1,5 +1,11 @@
|
|||||||
# Nmap Changelog ($Id$); -*-text-*-
|
# Nmap Changelog ($Id$); -*-text-*-
|
||||||
|
|
||||||
|
o [NSE] Added the script redis-info that lists version and statistic information
|
||||||
|
gathered from the Redis network key-value store. [Patrik]
|
||||||
|
|
||||||
|
o [NSE] Added the script redis-brute that performs brute force password
|
||||||
|
guessing against the Redis network key-value store. [Patrik]
|
||||||
|
|
||||||
o [NSE] Added the script http-proxy-brute that performs brute force password
|
o [NSE] Added the script http-proxy-brute that performs brute force password
|
||||||
guessing against HTTP proxy servers. [Patrik]
|
guessing against HTTP proxy servers. [Patrik]
|
||||||
|
|
||||||
|
|||||||
142
nselib/redis.lua
Normal file
142
nselib/redis.lua
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
module(... or "redis", package.seeall)
|
||||||
|
|
||||||
|
local match = require 'match'
|
||||||
|
|
||||||
|
|
||||||
|
Request = {
|
||||||
|
|
||||||
|
new = function(self, cmd, ...)
|
||||||
|
local o = { cmd = cmd, args = {...} }
|
||||||
|
setmetatable (o,self)
|
||||||
|
self.__index = self
|
||||||
|
return o
|
||||||
|
end,
|
||||||
|
|
||||||
|
__tostring = function(self)
|
||||||
|
local output = ("*%s\r\n$%d\r\n%s\r\n"):format(#self.args + 1, #self.cmd, self.cmd)
|
||||||
|
|
||||||
|
for _, arg in ipairs(self.args) do
|
||||||
|
arg = tostring(arg)
|
||||||
|
output = output .. ("$%s\r\n%s\r\n"):format(#arg, arg)
|
||||||
|
end
|
||||||
|
|
||||||
|
return output
|
||||||
|
end
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Response = {
|
||||||
|
|
||||||
|
Type = {
|
||||||
|
STATUS = 0,
|
||||||
|
ERROR = 1,
|
||||||
|
INTEGER = 2,
|
||||||
|
BULK = 3,
|
||||||
|
MULTIBULK = 4,
|
||||||
|
},
|
||||||
|
|
||||||
|
new = function(self, socket)
|
||||||
|
local o = { socket = socket }
|
||||||
|
setmetatable (o,self)
|
||||||
|
self.__index = self
|
||||||
|
return o
|
||||||
|
end,
|
||||||
|
|
||||||
|
receive = function(self)
|
||||||
|
local status, data = self.socket:receive_buf("\r\n")
|
||||||
|
if ( not(status) ) then
|
||||||
|
return false, "Failed to receive data from server"
|
||||||
|
end
|
||||||
|
|
||||||
|
-- if we have a status, integer or error message
|
||||||
|
if ( data:match("^[%-%+%:]") ) then
|
||||||
|
local response = { data = data }
|
||||||
|
local t = data:match("^([-+:])")
|
||||||
|
if ( t == "-" ) then
|
||||||
|
response.type = Response.Type.ERROR
|
||||||
|
elseif ( t == "+" ) then
|
||||||
|
response.type = Response.Type.STATUS
|
||||||
|
elseif ( t == ":" ) then
|
||||||
|
response.type = Response.Type.INTEGER
|
||||||
|
end
|
||||||
|
|
||||||
|
return true, response
|
||||||
|
end
|
||||||
|
|
||||||
|
-- process bulk reply
|
||||||
|
if ( data:match("^%$") ) then
|
||||||
|
-- non existing key
|
||||||
|
if ( data == "$-1" ) then
|
||||||
|
return true, nil
|
||||||
|
end
|
||||||
|
|
||||||
|
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))
|
||||||
|
if( not(status) ) then
|
||||||
|
return false, "Failed to receive data from server"
|
||||||
|
end
|
||||||
|
|
||||||
|
return true, { data = data, type = Response.Type.BULK }
|
||||||
|
end
|
||||||
|
|
||||||
|
-- process multi-bulk reply
|
||||||
|
if ( data:match("^%*%d*") ) then
|
||||||
|
local count = data:match("^%*(%d*)")
|
||||||
|
local results = {}
|
||||||
|
|
||||||
|
for i=1, count do
|
||||||
|
-- peel of the length
|
||||||
|
local status = self.socket:receive_buf("\r\n")
|
||||||
|
if( not(status) ) then
|
||||||
|
return false, "Failed to receive data from server"
|
||||||
|
end
|
||||||
|
|
||||||
|
status, data = self.socket:receive_buf("\r\n")
|
||||||
|
if( not(status) ) then
|
||||||
|
return false, "Failed to receive data from server"
|
||||||
|
end
|
||||||
|
table.insert(results, data)
|
||||||
|
end
|
||||||
|
return true, { data = results, type = MULTIBULK }
|
||||||
|
end
|
||||||
|
|
||||||
|
return false, "Unsupported response"
|
||||||
|
end,
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Helper = {
|
||||||
|
|
||||||
|
new = function(self, host, port)
|
||||||
|
local o = { host = host, port = port }
|
||||||
|
setmetatable (o,self)
|
||||||
|
self.__index = self
|
||||||
|
return o
|
||||||
|
end,
|
||||||
|
|
||||||
|
connect = function(self)
|
||||||
|
self.socket = nmap.new_socket()
|
||||||
|
return self.socket:connect(self.host, self.port)
|
||||||
|
end,
|
||||||
|
|
||||||
|
reqCmd = function(self, cmd, ...)
|
||||||
|
local req = Request:new(cmd, ...)
|
||||||
|
local status, err = self.socket:send(tostring(req))
|
||||||
|
if (not(status)) then
|
||||||
|
return false, "Failed to send command to server"
|
||||||
|
end
|
||||||
|
return Response:new(self.socket):receive()
|
||||||
|
end,
|
||||||
|
|
||||||
|
close = function(self)
|
||||||
|
return self.socket:close()
|
||||||
|
end
|
||||||
|
|
||||||
|
}
|
||||||
108
scripts/redis-brute.nse
Normal file
108
scripts/redis-brute.nse
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
description = [[
|
||||||
|
Performs brute force passwords guessing against a Redis key-value store
|
||||||
|
]]
|
||||||
|
|
||||||
|
---
|
||||||
|
-- @usage
|
||||||
|
-- nmap -p 6379 <ip> --script redis-brute
|
||||||
|
--
|
||||||
|
-- @output
|
||||||
|
-- PORT STATE SERVICE
|
||||||
|
-- 6379/tcp open unknown
|
||||||
|
-- | redis-brute:
|
||||||
|
-- | Accounts
|
||||||
|
-- | toledo - Valid credentials
|
||||||
|
-- | Statistics
|
||||||
|
-- |_ Performed 5000 guesses in 3 seconds, average tps: 1666
|
||||||
|
--
|
||||||
|
--
|
||||||
|
|
||||||
|
author = "Patrik Karlsson"
|
||||||
|
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
|
||||||
|
categories = {"intrusive", "brute"}
|
||||||
|
|
||||||
|
require 'brute'
|
||||||
|
require 'redis'
|
||||||
|
require 'shortport'
|
||||||
|
|
||||||
|
portrule = shortport.port_or_service(6379, "redis-server")
|
||||||
|
|
||||||
|
local function fail(err) return ("\n ERROR: %s"):format(err) end
|
||||||
|
|
||||||
|
Driver = {
|
||||||
|
|
||||||
|
new = function(self, host, port)
|
||||||
|
local o = { host = host, port = port }
|
||||||
|
setmetatable(o, self)
|
||||||
|
self.__index = self
|
||||||
|
return o
|
||||||
|
end,
|
||||||
|
|
||||||
|
connect = function( self )
|
||||||
|
self.helper = redis.Helper:new(self.host, self.port)
|
||||||
|
return self.helper:connect()
|
||||||
|
end,
|
||||||
|
|
||||||
|
login = function( self, username, password )
|
||||||
|
local status, response = self.helper:reqCmd("AUTH", password)
|
||||||
|
|
||||||
|
-- some error occured, attempt to retry
|
||||||
|
if ( status and response.type == redis.Response.Type.ERROR and
|
||||||
|
"-ERR invalid password" == response.data ) then
|
||||||
|
return false, brute.Error:new( "Incorrect password" )
|
||||||
|
elseif ( status and response.type == redis.Response.Type.STATUS and
|
||||||
|
"+OK" ) then
|
||||||
|
return true, brute.Account:new( "", password, creds.State.VALID)
|
||||||
|
else
|
||||||
|
local err = brute.Error:new( err )
|
||||||
|
err:setRetry( true )
|
||||||
|
return false, err
|
||||||
|
end
|
||||||
|
|
||||||
|
end,
|
||||||
|
|
||||||
|
disconnect = function(self)
|
||||||
|
return self.helper:close()
|
||||||
|
end,
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
local function checkRedis(host, port)
|
||||||
|
|
||||||
|
local helper = redis.Helper:new(host, port)
|
||||||
|
local status = helper:connect()
|
||||||
|
if( not(status) ) then
|
||||||
|
return false, "Failed to connect to server"
|
||||||
|
end
|
||||||
|
|
||||||
|
local status, response = helper:reqCmd("INFO")
|
||||||
|
if ( not(status) ) then
|
||||||
|
return false, "Failed to request INFO command"
|
||||||
|
end
|
||||||
|
|
||||||
|
if ( redis.Response.Type.ERROR == response.type ) then
|
||||||
|
if ( "-ERR operation not permitted" == response.data ) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return false, "Server does not require authentication"
|
||||||
|
end
|
||||||
|
|
||||||
|
action = function(host, port)
|
||||||
|
|
||||||
|
local status, err = checkRedis(host, port)
|
||||||
|
if ( not(status) ) then
|
||||||
|
return fail(err)
|
||||||
|
end
|
||||||
|
|
||||||
|
local engine = brute.Engine:new(Driver, host, port )
|
||||||
|
|
||||||
|
engine.options.script_name = SCRIPT_NAME
|
||||||
|
engine.options.firstonly = true
|
||||||
|
engine.options:setOption( "passonly", true )
|
||||||
|
|
||||||
|
status, result = engine:start()
|
||||||
|
return result
|
||||||
|
end
|
||||||
113
scripts/redis-info.nse
Normal file
113
scripts/redis-info.nse
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
description = [[
|
||||||
|
Gets information from a Redis key-value store
|
||||||
|
]]
|
||||||
|
|
||||||
|
---
|
||||||
|
-- @usage
|
||||||
|
-- nmap -p 6379 <ip> --script redis-info
|
||||||
|
--
|
||||||
|
-- @output
|
||||||
|
-- PORT STATE SERVICE
|
||||||
|
-- 6379/tcp open unknown
|
||||||
|
-- | redis-info:
|
||||||
|
-- | Version 2.2.11
|
||||||
|
-- | Architecture 64 bits
|
||||||
|
-- | Process ID 17821
|
||||||
|
-- | Used CPU (sys) 2.37
|
||||||
|
-- | Used CPU (user) 1.02
|
||||||
|
-- | Connected clients 1
|
||||||
|
-- | Connected slaves 0
|
||||||
|
-- | Used memory 780.16K
|
||||||
|
-- |_ Role master
|
||||||
|
--
|
||||||
|
|
||||||
|
author = "Patrik Karlsson"
|
||||||
|
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
|
||||||
|
categories = {"discovery", "safe"}
|
||||||
|
dependencies = {"redis-brute"}
|
||||||
|
|
||||||
|
require 'creds'
|
||||||
|
require 'redis'
|
||||||
|
require 'shortport'
|
||||||
|
require 'tab'
|
||||||
|
|
||||||
|
portrule = shortport.port_or_service(6379, "redis-server")
|
||||||
|
|
||||||
|
local function fail(err) return ("\n ERROR: %s"):format(err) end
|
||||||
|
|
||||||
|
local filter = {
|
||||||
|
|
||||||
|
["redis_version"] = { name = "Version" },
|
||||||
|
["arch_bits"] = { name = "Architecture", func = function(v) return ("%s bits"):format(v) end },
|
||||||
|
["process_id"] = { name = "Process ID"},
|
||||||
|
["uptime"] = { name = "Uptime", func = function(v) return ("%s seconds"):format(v) end },
|
||||||
|
["used_cpu_sys"]= { name = "Used CPU (sys)"},
|
||||||
|
["used_cpu_user"] = { name = "Used CPU (user)"},
|
||||||
|
["connected_clients"] = { name = "Connected clients"},
|
||||||
|
["connected_slaves"] = { name = "Connected slaves"},
|
||||||
|
["used_memory_human"] = { name = "Used memory"},
|
||||||
|
["role"] = { name = "Role"}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
local order = {
|
||||||
|
"redis_version", "arch_bits", "process_id", "used_cpu_sys",
|
||||||
|
"used_cpu_user", "connected_clients", "connected_slaves",
|
||||||
|
"used_memory_human", "role"
|
||||||
|
}
|
||||||
|
|
||||||
|
action = function(host, port)
|
||||||
|
|
||||||
|
local helper = redis.Helper:new(host, port)
|
||||||
|
local status = helper:connect()
|
||||||
|
if( not(status) ) then
|
||||||
|
return fail("Failed to connect to server")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- do we have a service password
|
||||||
|
local c = creds.Credentials:new(creds.ALL_DATA, host, port)
|
||||||
|
local cred = c:getCredentials(creds.State.VALID + creds.State.PARAM)()
|
||||||
|
|
||||||
|
if ( cred and cred.pass ) then
|
||||||
|
local status, response = helper:reqCmd("AUTH", cred.pass)
|
||||||
|
if ( not(status) ) then
|
||||||
|
helper:close()
|
||||||
|
return fail(response)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local status, response = helper:reqCmd("INFO")
|
||||||
|
if ( not(status) ) then
|
||||||
|
helper:close()
|
||||||
|
return fail(response)
|
||||||
|
end
|
||||||
|
helper:close()
|
||||||
|
|
||||||
|
if ( redis.Response.Type.ERROR == response.type ) then
|
||||||
|
if ( "-ERR operation not permitted" == response.data ) then
|
||||||
|
return fail("Authentication required")
|
||||||
|
end
|
||||||
|
return fail(response.data)
|
||||||
|
end
|
||||||
|
|
||||||
|
local restab = stdnse.strsplit("\r\n", response.data)
|
||||||
|
if ( not(restab) or 0 == #restab ) then
|
||||||
|
return fail("Failed to parse response from server")
|
||||||
|
end
|
||||||
|
|
||||||
|
local kvs = {}
|
||||||
|
for _, item in ipairs(restab) do
|
||||||
|
local k, v = item:match("^([^:]*):(.*)$")
|
||||||
|
kvs[k] = v
|
||||||
|
end
|
||||||
|
|
||||||
|
local result = tab.new(2)
|
||||||
|
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)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return stdnse.format_output(true, tab.dump(result))
|
||||||
|
end
|
||||||
@@ -218,6 +218,8 @@ Entry { filename = "qscan.nse", categories = { "discovery", "safe", } }
|
|||||||
Entry { filename = "quake3-info.nse", categories = { "default", "discovery", "safe", "version", } }
|
Entry { filename = "quake3-info.nse", categories = { "default", "discovery", "safe", "version", } }
|
||||||
Entry { filename = "quake3-master-getservers.nse", categories = { "default", "discovery", "safe", } }
|
Entry { filename = "quake3-master-getservers.nse", categories = { "default", "discovery", "safe", } }
|
||||||
Entry { filename = "realvnc-auth-bypass.nse", categories = { "auth", "default", "safe", } }
|
Entry { filename = "realvnc-auth-bypass.nse", categories = { "auth", "default", "safe", } }
|
||||||
|
Entry { filename = "redis-brute.nse", categories = { "brute", "intrusive", } }
|
||||||
|
Entry { filename = "redis-info.nse", categories = { "discovery", "safe", } }
|
||||||
Entry { filename = "resolveall.nse", categories = { "discovery", "safe", } }
|
Entry { filename = "resolveall.nse", categories = { "discovery", "safe", } }
|
||||||
Entry { filename = "reverse-index.nse", categories = { "safe", } }
|
Entry { filename = "reverse-index.nse", categories = { "safe", } }
|
||||||
Entry { filename = "rexec-brute.nse", categories = { "brute", "intrusive", } }
|
Entry { filename = "rexec-brute.nse", categories = { "brute", "intrusive", } }
|
||||||
|
|||||||
Reference in New Issue
Block a user