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/nfs-ls.nse | 470 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 470 insertions(+) create mode 100644 scripts/nfs-ls.nse (limited to 'scripts/nfs-ls.nse') diff --git a/scripts/nfs-ls.nse b/scripts/nfs-ls.nse new file mode 100644 index 0000000..d6185a0 --- /dev/null +++ b/scripts/nfs-ls.nse @@ -0,0 +1,470 @@ +local rpc = require "rpc" +local shortport = require "shortport" +local stdnse = require "stdnse" +local string = require "string" +local ls = require "ls" +local table = require "table" +local nmap = require "nmap" + +description = [[ +Attempts to get useful information about files from NFS exports. +The output is intended to resemble the output of ls. + +The script starts by enumerating and mounting the remote NFS exports. After +that it performs an NFS GETATTR procedure call for each mounted point +in order to get its ACLs. +For each mounted directory the script will try to list its file entries +with their attributes. + +Since the file attributes shown in the results are the result of +GETATTR, READDIRPLUS, and similar procedures, the attributes +are the attributes of the local filesystem. + +These access permissions are shown only with NFSv3: +* Read: Read data from file or read a directory. +* Lookup: Look up a name in a directory + (no meaning for non-directory objects). +* Modify: Rewrite existing file data or modify existing + directory entries. +* Extend: Write new data or add directory entries. +* Delete: Delete an existing directory entry. +* Execute: Execute file (no meaning for a directory). + +Recursive listing is not implemented. +]] + +--- +-- @usage +-- nmap -p 111 --script=nfs-ls +-- nmap -sV --script=nfs-ls +-- +-- @args nfs-ls.time Specifies which one of the last mac times to use in +-- the files attributes output. Possible values are: +-- * m: last modification time (mtime) +-- * a: last access time (atime) +-- * c: last change time (ctime) +-- The default value is m (mtime). +-- @args nfs.version The NFS protocol version to use +-- +-- @output +-- PORT STATE SERVICE +-- 111/tcp open rpcbind +-- | nfs-ls: +-- | Volume /mnt/nfs/files +-- | access: Read Lookup NoModify NoExtend NoDelete NoExecute +-- | PERMISSION UID GID SIZE MODIFICATION TIME FILENAME +-- | drwxr-xr-x 1000 100 4096 2010-06-17 12:28 /mnt/nfs/files +-- | drwxr--r-- 1000 1002 4096 2010-05-14 12:58 sources +-- | -rw------- 1000 1002 23606 2010-06-17 12:28 notes +-- | +-- | Volume /home/storage/backup +-- | access: Read Lookup Modify Extend Delete NoExecute +-- | PERMISSION UID GID SIZE MODIFICATION TIME FILENAME +-- | drwxr-xr-x 1000 100 4096 2010-06-11 22:31 /home/storage/backup +-- | -rw-r--r-- 1000 1002 0 2010-06-10 08:34 filetest +-- | drwx------ 1000 100 16384 2010-02-05 17:05 lost+found +-- | -rw-r--r-- 0 0 5 2010-06-10 11:32 rootfile +-- | lrwxrwxrwx 1000 1002 8 2010-06-10 08:34 symlink +-- |_ +-- +-- @xmloutput +-- +--
+-- /mnt/nfs/files +--
+--
+-- drwxr-xr-x +-- 1000 +-- 100 +-- 4096 +-- 2010-06-11 22:31 +-- /mnt/nfs/files +--
+-- +-- -rw-r--r-- +-- 1000 +-- 1002 +-- 0 +-- 2010-06-10 08:34 +-- filetest +--
+-- +-- drwx------ +-- 0 +-- 0 +-- 16384 +-- 2010-02-05 17:05 +-- lost+found +--
+-- +-- -rw-r--r-- +-- 0 +-- 0 +-- 5 +-- 2010-06-10 11:32 +-- rootfile +--
+-- +-- lrwxrwxrwx +-- 1000 +-- 1002 +-- 8 +-- 2010-06-10 08:34 +-- symlink +--
+-- +-- +-- access: Read Lookup NoModify NoExtend NoDelete NoExecute +--
+-- +-- +-- +-- 5 +-- 20493 +--
+ +-- Created 05/28/2010 - v0.1 - combined nfs-dirlist and nfs-acls scripts +-- Revised 06/04/2010 - v0.2 - make NFS exports listing with their acls +-- default action. +-- Revised 06/07/2010 - v0.3 - added mactimes output. +-- Revised 06/10/2010 - v0.4 - use the new library functions and list +-- entries with their attributes. +-- Revised 06/11/2010 - v0.5 - make the mtime the default time to show. +-- Revised 06/12/2010 - v0.6 - reworked the output to use the tab +-- library. +-- Revised 06/27/2010 - v0.7 - added NFSv3 ACCESS support. +-- Revised 06/28/2010 - v0.8 - added NFSv2 support. +-- + +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_attributes(nfs, mount, attr) + local file = {} + + if attr.mode then + file.type = rpc.Util.FtypeToChar(attr.mode) + file.mode = rpc.Util.FpermToString(attr.mode) + file.uid = tostring(attr.uid) + file.gid = tostring(attr.gid) + if nfs.human then + file.size = rpc.Util.SizeToHuman(attr.size) + else + file.size = tostring(attr.size) + end + file.time = rpc.Util.TimeToString(attr[nfs.time].seconds) + else + file.type = '?' + file.mode = '?????????' + file.uid = '?' + file.gid = '?' + file.size = '?' + file.time = '?' + end + file.filename = mount + + return file +end + +local function table_dirlist(nfs, mount, dirlist) + local ret, files, attrs = {}, {}, {} + local idx = 1 + + for _, v in ipairs(dirlist) do + if ((0 < nfs.maxfiles) and (#files >= nfs.maxfiles)) then + break + end + + if v.attributes then + table.insert(files, v.name) + attrs[files[idx]] = table_attributes(nfs, v.name, v.attributes) + idx = idx + 1 + else + stdnse.debug1("ERROR attributes: %s", v.name) + end + end + + table.sort(files) + for _, v in pairs(files) do + table.insert(ret, attrs[v]) + end + + return ret +end + +-- Unmount the NFS file system and close the connections +local function unmount_nfs(mount, mnt_obj, nfs_obj) + rpc.Helper.NfsClose(nfs_obj) + rpc.Helper.UnmountPath(mnt_obj, mount) +end + +local function nfs_ls(nfs, mount, output) + local dirs, attr, acs = {}, {}, {} + local nfsobj = rpc.NFS:new() + local mnt_comm, nfs_comm, fhandle + + mnt_comm, fhandle = procedures.MountPath(nfs.host, mount) + if mnt_comm == nil then + ls.report_error(output, fhandle) + return false + end + + local nfs_comm, status = procedures.NfsOpen(nfs.host) + if nfs_comm == nil then + rpc.Helper.UnmountPath(mnt_comm, mount) + ls.report_error(output, status) + return false + end + + -- check if NFS and Mount versions are compatible + -- RPC library will check if the Mount and NFS versions are supported + if (nfs_comm.version == 1) then + unmount_nfs(mount, mnt_comm, nfs_comm) + ls.report_error(output, + string.format("NFS v%d not supported", nfs_comm.version)) + return false + elseif ((nfs_comm.version == 2 and mnt_comm.version > 2) or + (nfs_comm.version == 3 and mnt_comm.version ~= 3)) then + unmount_nfs(mount, mnt_comm, nfs_comm) + ls.report_error(output, + string.format("versions mismatch, NFS v%d - Mount v%d", + nfs_comm.version, mnt_comm.version)) + return false + end + + status, attr = nfsobj:GetAttr(nfs_comm, fhandle) + if not status then + unmount_nfs(mount, mnt_comm, nfs_comm) + ls.report_error(output, attr) + return status + end + + if nfs_comm.version == 3 then + status, acs = nfsobj:Access(nfs_comm, fhandle, 0x0000003F) + if status then + acs.str = rpc.Util.format_access(acs.mask, nfs_comm.version) + ls.report_info(output, string.format("access: %s", acs.str)) + end + + status, dirs = nfsobj:ReadDirPlus(nfs_comm, fhandle) + if status then + for _,v in ipairs(table_dirlist(nfs, mount, dirs.entries)) do + ls.add_file(output, {v.type .. v.mode, v.uid, v.gid, v.size, + v.time, v.filename}) + end + end + elseif nfs_comm.version == 2 then + status, dirs = nfsobj:ReadDir(nfs_comm, fhandle) + if status then + local lookup = {} + for _, v in ipairs(dirs.entries) do + if ((0 < nfs.maxfiles) and (#lookup >= nfs.maxfiles)) then + break + end + + local f = {} + status, f = nfsobj:LookUp(nfs_comm, fhandle, v.name) + f.name = v.name + table.insert(lookup, f) + end + + for _, v in ipairs(table_dirlist(nfs, mount, lookup)) do + ls.add_file(output, {v.type .. v.mode, v.uid, v.gid, v.size, + v.time, v.filename}) + end + end + end + + unmount_nfs(mount, mnt_comm, nfs_comm) + return status +end + +local mainaction = function(host) + local results, mounts, status = {}, {} + local nfs_info = + { + host = host, + --recurs = tonumber(nmap.registry.args['nfs-ls.recurs']) or 1, + } + local output = ls.new_listing() + + nfs_info.version, nfs_info.time = stdnse.get_script_args('nfs.version', + 'nfs-ls.time') + nfs_info.maxfiles = ls.config('maxfiles') + nfs_info.human = ls.config('human') + + if nfs_info.time == "a" or nfs_info.time == "A" then + nfs_info.time = "atime" + elseif nfs_info.time == "c" or nfs_info.time == "C" then + nfs_info.time = "ctime" + else + nfs_info.time = "mtime" + end + + status, mounts = procedures.ShowMounts(nfs_info.host) + if not status or mounts == nil then + if mounts then + return stdnse.format_output(false, mounts) + else + return stdnse.format_output(false, "Mount error") + end + end + + for _, v in ipairs(mounts) do + local err + ls.new_vol(output, v.name, true) + status = nfs_ls(nfs_info, v.name, output) + ls.end_vol(output) + end + + return ls.end_listing(output) +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 -- cgit v1.2.3