diff options
Diffstat (limited to '')
-rw-r--r-- | scripts/nfs-statfs.nse | 359 |
1 files changed, 359 insertions, 0 deletions
diff --git a/scripts/nfs-statfs.nse b/scripts/nfs-statfs.nse new file mode 100644 index 0000000..2086978 --- /dev/null +++ b/scripts/nfs-statfs.nse @@ -0,0 +1,359 @@ +local rpc = require "rpc" +local shortport = require "shortport" +local stdnse = require "stdnse" +local string = require "string" +local tab = require "tab" +local table = require "table" +local nmap = require "nmap" + +description = [[ +Retrieves disk space statistics and information from a remote NFS share. +The output is intended to resemble the output of <code>df</code>. + +The script will provide pathconf information of the remote NFS if +the version used is NFSv3. +]] + +--- +-- @usage +-- nmap -p 111 --script=nfs-statfs <target> +-- nmap -sV --script=nfs-statfs <target> +-- @output +-- PORT STATE SERVICE +-- | nfs-statfs: +-- | Filesystem 1K-blocks Used Available Use% Blocksize +-- | /mnt/nfs/files 5542276 2732012 2528728 52% 4096 +-- |_ /mnt/nfs/opensource 5534416 620640 4632644 12% 4096 +-- +-- @args nfs-statfs.human If set to <code>1</code> or <code>true</code>, +-- shows file sizes in a human readable format with suffixes like +-- <code>KB</code> and <code>MB</code>. + +-- Version 0.3 + +-- Created 01/25/2010 - v0.1 - created by Patrik Karlsson <patrik@cqure.net> +-- Revised 02/22/2010 - v0.2 - adapted to support new RPC library +-- Revised 03/13/2010 - v0.3 - converted host to port rule +-- Revised 06/28/2010 - v0.4 - added NFSv3 support and doc + + +author = {"Patrik Karlsson", "Djalal Harouni"} +license = "Same as Nmap--See https://nmap.org/book/man-legal.html" +categories = {"discovery", "safe"} +dependencies = {"rpc-grind"} + + +portrule = shortport.port_or_service(111, "rpcbind", {"tcp", "udp"} ) + +hostrule = function(host) + local mountport, nfsport + if host.registry.nfs then + mountport = host.registry.nfs.mountport + nfsport = host.registry.nfs.nfsport + else + host.registry.nfs = {} + end + for _,proto in ipairs({"tcp","udp"}) do + local port = nmap.get_ports(host, nil, proto, "open") + while port do + if port.version then + if port.service == "mountd" then + mountport = port + elseif port.service == "nfs" then + nfsport = port + end + end + if mountport and nfsport then break end + port = nmap.get_ports(host, port, proto, "open") + end + if mountport and nfsport then break end + end + -- Run when nfs and mount ports were scanned and their versions numbers known + if not (nfsport and (host.registry.nfs.nfsver or nfsport.version.version)) then + return false + end + if not (mountport and (host.registry.nfs.mountver or mountport.version.version)) then + return false + end + if host.registry.nfs.nfsver == nil then + local low, high = string.match(nfsport.version.version, "(%d)%-(%d)") + if high == nil then + high = tonumber(nfsport.version.version) + if high == 4 then + return false --Can't support version 4 + else + host.registry.nfs.nfsver = high + end + else + if high == "4" then + host.registry.nfs.nfsver = 3 + else + host.registry.nfs.nfsver = tonumber(low) + end + end + end + if host.registry.nfs.mountver == nil then + local low, high = string.match(mountport.version.version, "(%d)%-(%d)") + if high == nil then + host.registry.nfs.mountver = tonumber(mountport.version.version) + else + host.registry.nfs.mountver = tonumber(high) + end + end + host.registry.nfs.mountport = mountport + host.registry.nfs.nfsport = nfsport + return (mountport and nfsport) +end + +local procedures = { } + +local function table_fsstat(nfs, mount, stats) + local fs, err = rpc.Util.calc_fsstat_table(stats, nfs.version, nfs.human) + if fs == nil then + return false, err + end + fs.filesystem = string.format("%s", mount) + return true, fs +end + +local function table_fsinfo(nfs, fsinfo) + local ret = {} + local fs, err = rpc.Util.calc_fsinfo_table(fsinfo, nfs.version, nfs.human) + if fs == nil then + return false, err + end + + ret.maxfilesize = fs.maxfilesize + return true, ret +end + +local function table_pathconf(nfs, pconf) + local ret = {} + local fs, err = rpc.Util.calc_pathconf_table(pconf, nfs.version) + if fs == nil then + return false, err + end + + ret.linkmax = fs.linkmax + return true, ret +end + +local function report(nfs, tables) + local outtab, tab_size, tab_avail + local tab_filesys, tab_used, tab_use, + tab_bs, tab_maxfs, tab_linkmax = "Filesystem", + "Used", "Use%", "Blocksize", "Maxfilesize", "Maxlink" + + if nfs.human then + tab_size = "Size" + tab_avail = "Avail" + else + tab_size = "1K-blocks" + tab_avail = "Available" + end + + if nfs.version == 2 then + outtab = tab.new() + tab.addrow(outtab, tab_filesys, tab_size, tab_used, + tab_avail, tab_use, tab_bs) + for _, t in ipairs(tables) do + tab.addrow(outtab, t.filesystem, t.size, + t.used, t.available, t.use, t.bsize) + end + elseif nfs.version == 3 then + outtab = tab.new() + tab.addrow(outtab, tab_filesys, tab_size, tab_used, + tab_avail, tab_use, tab_maxfs, tab_linkmax) + for _, t in ipairs(tables) do + tab.addrow(outtab, t.filesystem, t.size, t.used, + t.available, t.use, t.maxfilesize, t.linkmax) + end + end + + return tab.dump(outtab) +end + +local function nfs_filesystem_info(nfs, mount, filesystem) + local results, res, status = {}, {} + local nfsobj = rpc.NFS:new() + local mnt_comm, nfs_comm, fhandle + + mnt_comm, fhandle = procedures.MountPath(nfs.host, mount) + if mnt_comm == nil then + return false, fhandle + end + + local nfs_comm, status = procedures.NfsOpen(nfs.host) + if nfs_comm == nil then + rpc.Helper.UnmountPath(mnt_comm, mount) + return false, status + end + + nfs.version = nfs_comm.version + + -- use simple check since NFSv1 is not used anymore, and NFSv4 not supported + if (nfs_comm.version <= 2 and mnt_comm.version > 2) then + rpc.Helper.UnmountPath(mnt_comm, mount) + return false, string.format("versions mismatch, nfs v%d - mount v%d", + nfs_comm.version, mnt_comm.version) + end + + if nfs_comm.version < 3 then + status, res = nfsobj:StatFs(nfs_comm, fhandle) + elseif nfs_comm.version == 3 then + status, res = nfsobj:FsStat(nfs_comm, fhandle) + end + + if status then + status, res = table_fsstat(nfs, mount, res) + if status then + for k, v in pairs(res) do + results[k] = v + end + end + + if nfs_comm.version == 3 then + status, res = nfsobj:FsInfo(nfs_comm, fhandle) + if status then + status, res = table_fsinfo(nfs, res) + if status then + for k, v in pairs(res) do + results[k] = v + end + end + end + + status, res = nfsobj:PathConf(nfs_comm, fhandle) + if status then + status, res = table_pathconf(nfs, res) + if status then + for k, v in pairs(res) do + results[k] = v + end + end + end + + end + end + + rpc.Helper.NfsClose(nfs_comm) + rpc.Helper.UnmountPath(mnt_comm, mount) + if (not(status)) then + return status, res + end + + table.insert(filesystem, results) + return true, nil +end + +mainaction = function(host) + local fs_info, mounts, status = {}, {}, {} + local nfs_info = + { + host = host, + } + nfs_info.human = stdnse.get_script_args('nfs-statfs.human') + + status, mounts = procedures.ShowMounts( host ) + if (not(status)) then + return stdnse.format_output(false, mounts) + end + + if #mounts < 1 then + stdnse.debug1("No NFS mounts available") + return nil + end + + for _, v in ipairs(mounts) do + local err + status, err = nfs_filesystem_info(nfs_info, v.name, fs_info) + if (not(status)) then + return stdnse.format_output(false, + string.format("%s: %s", v.name, err)) + end + end + + return stdnse.format_output(true, report(nfs_info, fs_info)) +end + +hostaction = function(host) + procedures = { + ShowMounts = function(ahost) + local mnt_comm, status, result, mounts + local mnt = rpc.Mount:new() + mnt_comm = rpc.Comm:new('mountd', host.registry.nfs.mountver) + status, result = mnt_comm:Connect(ahost, host.registry.nfs.mountport) + if ( not(status) ) then + stdnse.debug1("ShowMounts: %s", result) + return false, result + end + status, mounts = mnt:Export(mnt_comm) + mnt_comm:Disconnect() + if ( not(status) ) then + stdnse.debug1("ShowMounts: %s", mounts) + end + return status, mounts + end, + + MountPath = function(ahost, path) + local fhandle, status, err + local mountd, mnt_comm + local mnt = rpc.Mount:new() + + mnt_comm = rpc.Comm:new("mountd", host.registry.nfs.mountver) + + status, err = mnt_comm:Connect(host, host.registry.nfs.mountport) + if not status then + stdnse.debug1("MountPath: %s", err) + return nil, err + end + + status, fhandle = mnt:Mount(mnt_comm, path) + if not status then + mnt_comm:Disconnect() + stdnse.debug1("MountPath: %s", fhandle) + return nil, fhandle + end + + return mnt_comm, fhandle + end, + + NfsOpen = function(ahost) + local nfs_comm, status, err + + nfs_comm = rpc.Comm:new('nfs', host.registry.nfs.nfsver) + status, err = nfs_comm:Connect(host, host.registry.nfs.nfsport) + if not status then + stdnse.debug1("NfsOpen: %s", err) + return nil, err + end + + return nfs_comm, nil + end, + } + return mainaction(host) +end + +portaction = function(host, port) + procedures = { + ShowMounts = function(ahost) + return rpc.Helper.ShowMounts(ahost, port) + end, + MountPath = function(ahost, path) + return rpc.Helper.MountPath(ahost, port, path) + end, + NfsOpen = function(ahost) + return rpc.Helper.NfsOpen(ahost, port) + end, + } + return mainaction(host) +end + +local ActionsTable = { + -- portrule: use rpcbind service + portrule = portaction, + -- hostrule: Talk to services directly + hostrule = hostaction +} + +action = function(...) return ActionsTable[SCRIPT_TYPE](...) end |