summaryrefslogtreecommitdiffstats
path: root/scripts/nbd-info.nse
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/nbd-info.nse')
-rw-r--r--scripts/nbd-info.nse197
1 files changed, 197 insertions, 0 deletions
diff --git a/scripts/nbd-info.nse b/scripts/nbd-info.nse
new file mode 100644
index 0000000..ba233d0
--- /dev/null
+++ b/scripts/nbd-info.nse
@@ -0,0 +1,197 @@
+local nbd = require "nbd"
+local shortport = require "shortport"
+local stdnse = require "stdnse"
+local table = require "table"
+local tableaux = require "tableaux"
+
+description = [[
+Displays protocol and block device information from NBD servers.
+
+The Network Block Device protocol is used to publish block devices
+over TCP. This script connects to an NBD server and attempts to pull
+down a list of exported block devices and their details
+
+For additional information:
+* https://github.com/NetworkBlockDevice/nbd/blob/master/doc/proto.md
+]]
+
+---
+-- @usage nmap -p 10809 --script nbd-info <target>
+--
+-- @output
+-- PORT STATE SERVICE REASON
+-- 10809/tcp open nbd syn-ack
+-- | nbd-info:
+-- | Protocol:
+-- | Negotiation: fixed newstyle
+-- | SSL/TLS Wrapped: false
+-- | Exported Block Devices:
+-- | foo:
+-- | Size: 1048576 bytes
+-- | Transmission Flags:
+-- | SEND_FLUSH
+-- | READ_ONLY
+-- | SEND_FUA
+-- | bar:
+-- | Size: 1048576 bytes
+-- | Transmission Flags:
+-- | READ_ONLY
+-- |_ ROTATIONAL
+--
+-- @args nbd-info.export_names Either a single name, or a table of
+-- names to about which to request information from the server.
+
+author = "Mak Kolybabi <mak@kolybabi.com>"
+license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
+categories = {"discovery", "intrusive"}
+
+portrule = shortport.version_port_or_service(10809, "nbd", "tcp")
+
+local enumerate_options = function(comm)
+ -- Run the LIST command and store the responses.
+ local req = comm:build_opt_req("LIST")
+ if not req then
+ return
+ end
+
+ local status, err = comm:send(req)
+ if not status then
+ stdnse.debug1("Failed to send option request: %s", err)
+ return nil
+ end
+
+ while true do
+ local rep = comm:receive_opt_rep()
+ if not rep or rep.rtype_name ~= "SERVER" then
+ break
+ end
+
+ comm.exports[rep.export_name] = {}
+ end
+end
+
+local newstyle_connection = function(comm, args)
+ local names = {}
+
+ for _, name in ipairs(args.export_name) do
+ table.insert(names, name)
+ end
+
+ for name, _ in pairs(comm.exports) do
+ table.insert(names, name)
+ end
+
+ for i, name in ipairs(names) do
+ if i ~= 1 then
+ local status = comm:reconnect()
+ if not status then
+ return
+ end
+ end
+
+ comm:attach(name)
+ end
+end
+
+local function parse_args()
+ local args = {}
+
+ local arg = stdnse.get_script_args(SCRIPT_NAME .. ".export-names")
+ if not arg then
+ -- An empty string for an export name indicates to the server that
+ -- we wish to attach to the default export.
+ arg = {}
+ elseif type(arg) ~= 'table' then
+ arg = {arg}
+ end
+ args.export_name = arg
+
+ return args
+end
+
+action = function(host, port)
+ local args = parse_args()
+
+ local comm = nbd.Comm:new(host, port)
+
+ local status = comm:connect(args)
+ if not status then
+ return nil
+ end
+
+ -- If the service supports an unrecognized negotiation, or the
+ -- oldstyle negotiation, there's no more information to be had.
+ if comm.protocol.negotiation == "unrecognized" or comm.protocol.negotiation == "oldstyle" then
+ -- Nothing to do.
+ comm:close()
+
+ -- If the service supports the (non-fixed) newstyle negotiation,
+ -- which should be very rare, we can only send a single option. That
+ -- option is the name of the export to which we'd like to attach.
+ elseif comm.protocol.negotiation == "newstyle" then
+ newstyle_connection(comm, args)
+
+ -- If the service supports the fixed newstyle negotiation, then we
+ -- can perform option haggling to wring additional information from
+ -- it.
+ elseif comm.protocol.negotiation == "fixed newstyle" then
+ enumerate_options(comm)
+ newstyle_connection(comm, args)
+
+ -- Otherwise, we've got a mismatch between the library and this script.
+ else
+ assert(false, "NBD library supports more negotiation styles than this script.")
+ end
+
+ -- Master output table.
+ local output = stdnse.output_table()
+
+ -- Format protocol information.
+ local protocol = stdnse.output_table()
+ if comm.protocol.negotiation == "oldstyle" and comm.exports["(default)"] then
+ if comm.exports["(default)"].hflags & nbd.NBD.handshake_flags.FIXED_NEWSTYLE then
+ protocol["Fixed Newstyle Negotiation"] = "Supported by service, but not on this port."
+ end
+ end
+ protocol["Negotiation"] = comm.protocol.negotiation
+ protocol["SSL/TLS Wrapped"] = comm.protocol.ssl_tls
+
+ output["Protocol"] = protocol
+
+ -- Format exported block device information.
+ local exports = stdnse.output_table()
+ local no_shares = true
+ local names = tableaux.keys(comm.exports)
+ -- keep exports in stable order
+ table.sort(names)
+ for _, name in ipairs(names) do
+ local info = comm.exports[name]
+ local exp = {}
+ if type(info.size) == "number" then
+ exp["Size"] = info.size .. " bytes"
+ end
+
+ if type(info.tflags) == "table" then
+ local keys = {}
+ for k, _ in pairs(info.tflags) do
+ if k ~= "HAS_FLAGS" then
+ table.insert(keys, k)
+ end
+ end
+ -- sort by bitfield flag value
+ table.sort(keys, function(a, b)
+ return nbd.NBD.transmission_flags[a] < nbd.NBD.transmission_flags[b]
+ end)
+ exp["Transmission Flags"] = keys
+ end
+
+ no_shares = false
+ exports[name] = exp
+ end
+
+ if not no_shares then
+ output["Exported Block Devices"] = exports
+ end
+
+ return output
+end