1
0
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:
patrik
2012-01-02 11:27:06 +00:00
parent 3491fdc1fa
commit 4118ee064b
5 changed files with 371 additions and 0 deletions

View File

@@ -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
View 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
View 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
View 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

View File

@@ -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", } }