From 0d47952611198ef6b1163f366dc03922d20b1475 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 17 Apr 2024 09:42:04 +0200 Subject: Adding upstream version 7.94+git20230807.3be01efb1+dfsg. Signed-off-by: Daniel Baumann --- scripts/redis-info.nse | 250 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 250 insertions(+) create mode 100644 scripts/redis-info.nse (limited to 'scripts/redis-info.nse') diff --git a/scripts/redis-info.nse b/scripts/redis-info.nse new file mode 100644 index 0000000..fcfd4b7 --- /dev/null +++ b/scripts/redis-info.nse @@ -0,0 +1,250 @@ +local creds = require "creds" +local redis = require "redis" +local nmap = require "nmap" +local shortport = require "shortport" +local stdnse = require "stdnse" +local string = require "string" +local stringaux = require "stringaux" +local table = require "table" +local tableaux = require "tableaux" +local ipOps = require "ipOps" + +description = [[ +Retrieves information (such as version number and architecture) from a Redis key-value store. +]] + +--- +-- @usage +-- nmap -p 6379 --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 +-- | Bind addresses: +-- | 192.168.121.101 +-- | Active channels: +-- | testChannel +-- | bidChannel +-- | Client connections: +-- | 192.168.171.101 +-- |_ 72.14.177.105 +-- +-- + +author = {"Patrik Karlsson", "Vasiliy Kulikov"} +license = "Same as Nmap--See https://nmap.org/book/man-legal.html" +categories = {"discovery", "safe"} +dependencies = {"redis-brute"} + + +portrule = shortport.port_or_service(6379, "redis") + +local function fail(err) return stdnse.format_output(false, err) end + +local function cb_parse_version(host, port, val) + port.version.version = val + port.version.cpe = port.version.cpe or {} + table.insert(port.version.cpe, 'cpe:/a:redis:redis:' .. val) + nmap.set_port_version(host, port) + return val +end + +local function cb_parse_architecture(host, port, val) + val = ("%s bits"):format(val) + port.version.extrainfo = val + nmap.set_port_version(host, port) + return val +end + +local filter = { + + ["redis_version"] = { name = "Version", func = cb_parse_version }, + ["os"] = { name = "Operating System" }, + ["arch_bits"] = { name = "Architecture", func = cb_parse_architecture }, + ["process_id"] = { name = "Process ID"}, + ["uptime"] = { name = "Uptime", func = function(h, p, 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", "os", "arch_bits", "process_id", "used_cpu_sys", + "used_cpu_user", "connected_clients", "connected_slaves", + "used_memory_human", "role" +} + +local extras = { + { + -- https://redis.io/commands/config-get/ + "Bind addresses", {"CONFIG", "GET", "bind"}, function (data) + if data[1] ~= "bind" or not data[2] then + return nil + end + local restab = stringaux.strsplit(" ", data[2]) + for i, ip in ipairs(restab) do + if ip == '' then restab[i] = '0.0.0.0' end + end + return restab + end + }, + { + -- https://redis.io/commands/pubsub-channels/ + "Active channels", {"PUBSUB", "CHANNELS"}, function (data) + local channels = {} + local omitted = 0 + local limit = nmap.verbosity() <= 1 and 20 or false + for _, channel in ipairs(data) do + if limit and #channels >= limit then + omitted = omitted + 1 + else + table.insert(channels, channel) + end + end + + if omitted > 0 then + table.insert(channels, ("(omitted %s item(s), use verbose mode -v to show them)"):format(omitted)) + end + return #channels > 0 and channels or nil + end + }, + { + -- https://redis.io/commands/client-list/ + "Client connections", {"CLIENT", "LIST"}, function(data) + if not data then + stdnse.debug1("Failed to parse response from server") + return nil + end + + local client_ips = {} + for conn in data:gmatch("[^\n]+") do + local ip = conn:match("%f[^%s\0]addr=%[?([%x:.]+)%]?:%d+%f[%s\0]") + if ip then + local binip = ipOps.ip_to_str(ip) + if binip then + -- prepending length sorts IPv4 and IPv6 separately + client_ips[string.pack("s1", binip)] = binip + end + end + end + + local out = {} + local keys = tableaux.keys(client_ips) + table.sort(keys) + for _, packed in ipairs(keys) do + table.insert(out, ipOps.str_to_ip(client_ips[packed])) + end + return #out > 0 and out or nil + end + }, + { + -- https://redis.io/commands/cluster-nodes/ + "Cluster nodes", {"CLUSTER", "NODES"}, function(data) + if not data then + stdnse.debug1("Failed to parse response from server") + return nil + end + + local out = {} + for node in data:gmatch("[^\n]+") do + local ipport, flags = node:match("^%x+%s+([%x.:%[%]]+)@?%d*%s+(%S+)") + if ipport then + table.insert(out, ("%s (%s)"):format(ipport, flags)) + else + stdnse.debug1("Unable to parse cluster node info") + end + end + return #out > 0 and out or nil + end + }, +} + +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 + + if ( redis.Response.Type.ERROR == response.type ) then + if ( "-ERR operation not permitted" == response.data ) or + ( "-NOAUTH Authentication required." == response.data ) then + return fail("Authentication required") + end + return fail(response.data) + end + + local restab = stringaux.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("^([^:]*):(.*)$") + if k ~= nil then + kvs[k] = v + end + end + + local result = stdnse.output_table() + for _, item in ipairs(order) do + if kvs[item] then + local name = filter[item].name + local val + + if filter[item].func then + val = filter[item].func(host, port, kvs[item]) + else + val = kvs[item] + end + result[name] = val + end + end + + for i=1, #extras do + local name = extras[i][1] + local cmd = extras[i][2] + local process = extras[i][3] + + local status, response = helper:reqCmd(table.unpack(cmd)) + if status and redis.Response.Type.ERROR ~= response.type then + result[name] = process(response.data) + end + end + helper:close() + return result +end -- cgit v1.2.3