mirror of
https://github.com/nmap/nmap.git
synced 2025-12-19 22:19:02 +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-*-
|
||||
|
||||
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
|
||||
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-master-getservers.nse", categories = { "default", "discovery", "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 = "reverse-index.nse", categories = { "safe", } }
|
||||
Entry { filename = "rexec-brute.nse", categories = { "brute", "intrusive", } }
|
||||
|
||||
Reference in New Issue
Block a user