diff --git a/CHANGELOG b/CHANGELOG index d1fe6de4c..e7e97e2de 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,8 @@ # Nmap Changelog ($Id$); -*-text-*- +o [NSE] Added AUTH_UNIX support to the rpc library and nfs scripts. + [Daniel Miller] + o [Zenmap] Fixed a crash in the profile editor that would happen when the nmap binary couldn't be found. [David Fifield] diff --git a/nselib/rpc.lua b/nselib/rpc.lua index 5d3ab40fb..932b5232b 100644 --- a/nselib/rpc.lua +++ b/nselib/rpc.lua @@ -164,9 +164,25 @@ Comm = { end if ( port.protocol == "tcp" ) then socket = nmap.new_socket() - status, err = socket:connect(host, port) + if nmap.is_privileged() then + -- Try to bind to a reserved port + for resvport = 600, 1024, 1 do + status, err = socket:bind(nil, 1000) + if status then + status, err = socket:connect(host, port) + if status then break end + end + end + end else socket = nmap.new_socket("udp") + if nmap.is_privileged() then + -- Try to bind to a reserved port + for resvport = 600, 1024, 1 do + status, err = socket:bind(nil, 1000) + if status then break end + end + end end if (not(status)) then return status, string.format("%s connect error: %s", @@ -273,14 +289,30 @@ Comm = { end if not auth then return false, "Comm.CreateHeader: No authentication specified" - elseif auth.type ~= Portmap.AuthType.NULL then - return false, "Comm.CreateHeader: invalid authentication type specified" end packet = bin.pack( ">IIIIII", xid, Portmap.MessageType.CALL, RPC_VERSION, self.program_id, self.version, procedure ) if auth.type == Portmap.AuthType.NULL then packet = packet .. bin.pack( "IIII", 0, 0, 0, 0 ) + elseif auth.type == Portmap.AuthType.UNIX then + packet = packet .. Util.marshall_int32(auth.type) + local blob = Util.marshall_int32(nmap.clock()) --time + blob = blob .. Util.marshall_vopaque(auth.hostname or 'localhost') + blob = blob .. Util.marshall_int32(auth.uid or 0) + blob = blob .. Util.marshall_int32(auth.gid or 0) + if auth.gids then --len prefix gid list + blob = blob .. Util.marshall_int32(#auth.gids) + for _,gid in ipairs(auth.gids) do + blob = blob .. Util.marshall_int32(gid) + end + else + blob = blob .. Util.marshall_int32(0) + end + packet = packet .. Util.marshall_vopaque(blob) + packet = packet .. bin.pack( "II", 0, 0 ) --AUTH_NULL verf + else + return false, "Comm.CreateHeader: invalid authentication type specified" end return true, packet end, @@ -450,7 +482,8 @@ Portmap = -- TODO: add more Authentication Protocols AuthType = { - NULL = 0 + NULL = 0, + UNIX = 1, }, -- TODO: complete Authentication stats and error messages @@ -828,7 +861,7 @@ Mount = { end packet = comm:EncodePacket(nil, Mount.Procedure.EXPORT, - { type=Portmap.AuthType.NULL }, nil ) + { type=Portmap.AuthType.UNIX }, nil ) if (not(comm:SendPacket( packet ))) then return false, "Mount.Export: Failed to send data" end @@ -965,7 +998,7 @@ Mount = { data = Util.marshall_vopaque(path) - packet = comm:EncodePacket( nil, Mount.Procedure.MOUNT, { type=Portmap.AuthType.NULL }, data ) + packet = comm:EncodePacket( nil, Mount.Procedure.MOUNT, { type=Portmap.AuthType.UNIX }, data ) if (not(comm:SendPacket(packet))) then return false, "Mount: Failed to send data" end @@ -1055,7 +1088,7 @@ Mount = { data = Util.marshall_vopaque(path) - packet = comm:EncodePacket( nil, Mount.Procedure.UMNT, { type=Portmap.AuthType.NULL }, data ) + packet = comm:EncodePacket( nil, Mount.Procedure.UMNT, { type=Portmap.AuthType.UNIX }, data ) if (not(comm:SendPacket(packet))) then return false, "Unmount: Failed to send data" end @@ -1493,7 +1526,7 @@ NFS = { data = bin.pack("A>I>I", file_handle, cookie, count) end packet = comm:EncodePacket( nil, NFS.Procedure[comm.version].READDIR, - { type=Portmap.AuthType.NULL }, data ) + { type=Portmap.AuthType.UNIX }, data ) if(not(comm:SendPacket( packet ))) then return false, "ReadDir: Failed to send data" end @@ -1613,7 +1646,7 @@ NFS = { data = Util.marshall_opaque(dir_handle) .. Util.marshall_vopaque(file) packet = comm:EncodePacket(nil, NFS.Procedure[comm.version].LOOKUP, - {type=Portmap.AuthType.NULL}, data) + {type=Portmap.AuthType.UNIX}, data) if(not(comm:SendPacket(packet))) then return false, "LookUp: Failed to send data" end @@ -1790,7 +1823,7 @@ NFS = { opaque_data, dircount, maxcount) packet = comm:EncodePacket(nil, NFS.Procedure[comm.version].READDIRPLUS, - {type = Portmap.AuthType.NULL }, data) + {type = Portmap.AuthType.UNIX }, data) if (not(comm:SendPacket(packet))) then return false, "ReadDirPlus: Failed to send data" @@ -1869,7 +1902,7 @@ NFS = { data = bin.pack("A", file_handle) packet = comm:EncodePacket(nil, NFS.Procedure[comm.version].FSSTAT, - {type = Portmap.AuthType.NULL}, data) + {type = Portmap.AuthType.UNIX}, data) if (not(comm:SendPacket(packet))) then return false, "FsStat: Failed to send data" @@ -1951,7 +1984,7 @@ NFS = { data = Util.marshall_opaque(file_handle) packet = comm:EncodePacket(nil, NFS.Procedure[comm.version].FSINFO, - {type = Portmap.AuthType.NULL}, data) + {type = Portmap.AuthType.UNIX}, data) if (not(comm:SendPacket(packet))) then return false, "FsInfo: Failed to send data" @@ -2030,7 +2063,7 @@ NFS = { data = Util.marshall_opaque(file_handle) packet = comm:EncodePacket(nil, NFS.Procedure[comm.version].PATHCONF, - {type = Portmap.AuthType.NULL}, data) + {type = Portmap.AuthType.UNIX}, data) if (not(comm:SendPacket(packet))) then return false, "PathConf: Failed to send data" @@ -2107,7 +2140,7 @@ NFS = { data = Util.marshall_opaque(file_handle) .. Util.marshall_uint32(access) packet = comm:EncodePacket(nil, NFS.Procedure[comm.version].ACCESS, - {type = Portmap.AuthType.NULL}, data) + {type = Portmap.AuthType.UNIX}, data) if (not(comm:SendPacket(packet))) then return false, "Access: Failed to send data" @@ -2155,7 +2188,7 @@ NFS = { end data = Util.marshall_opaque(file_handle) - packet = comm:EncodePacket( nil, NFS.Procedure[comm.version].STATFS, { type=Portmap.AuthType.NULL }, data ) + packet = comm:EncodePacket( nil, NFS.Procedure[comm.version].STATFS, { type=Portmap.AuthType.UNIX }, data ) if (not(comm:SendPacket( packet ))) then return false, "StatFS: Failed to send data" end @@ -2235,7 +2268,7 @@ NFS = { local data, packet, status, attribs, pos, header data = Util.marshall_opaque(file_handle) - packet = comm:EncodePacket( nil, NFS.Procedure[comm.version].GETATTR, { type=Portmap.AuthType.NULL }, data ) + packet = comm:EncodePacket( nil, NFS.Procedure[comm.version].GETATTR, { type=Portmap.AuthType.UNIX }, data ) if(not(comm:SendPacket(packet))) then return false, "GetAttr: Failed to send data" end diff --git a/scripts/nfs-ls.nse b/scripts/nfs-ls.nse index 402dfd243..5833b9101 100644 --- a/scripts/nfs-ls.nse +++ b/scripts/nfs-ls.nse @@ -4,6 +4,7 @@ local stdnse = require "stdnse" local string = require "string" local tab = require "tab" local table = require "table" +local nmap = require "nmap" description = [[ Attempts to get useful information about files from NFS exports. @@ -55,8 +56,7 @@ These access permissions are shown only with NFSv3: -- |_ lrwxrwxrwx 1000 1002 8 2010-06-10 08:34 symlink -- -- @args nfs-ls.maxfiles If set, limits the amount of files returned by --- the script when using the nfs-ls.dirlist argument. --- If set to 0 +-- the script. If set to 0 -- or less, all files are shown. The default value is 10. -- @args nfs-ls.human If set to 1 or true, -- shows file sizes in a human readable format with suffixes like @@ -88,19 +88,57 @@ categories = {"discovery", "safe"} portrule = shortport.port_or_service(111, "rpcbind", {"tcp", "udp"} ) +local mountport = nil +local nfsport = nil +hostrule = function(host) + 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 + if nfsport == nil then return false end + if nfsport.version.rpc_highver == 4 and nfsport.version.rpc_lowver <= 3 then + nfsport.version.rpc_goodver = 3 + else + nfsport.version.rpc_goodver = nfsport.version.rpc_highver + end + return (mountport and nfsport) +end + +local procedures = { } + local function table_attributes(nfs, mount, attr) local file = {} - 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) + 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.size = tostring(attr.size) + file.type = '?' + file.mode = '?????????' + file.uid = '?' + file.gid = '?' + file.size = '?' + file.time = '?' end - file.time = rpc.Util.TimeToString(attr[nfs.time].seconds) file.filename = mount return file @@ -145,12 +183,12 @@ local function nfs_ls(nfs, mount, results, access) local nfsobj = rpc.NFS:new() local mnt_comm, nfs_comm, fhandle - mnt_comm, fhandle = rpc.Helper.MountPath(nfs.host, nfs.port, mount) + mnt_comm, fhandle = procedures.MountPath(nfs.host, mount) if mnt_comm == nil then return false, fhandle end - local nfs_comm, status = rpc.Helper.NfsOpen(nfs.host, nfs.port) + local nfs_comm, status = procedures.NfsOpen(nfs.host) if nfs_comm == nil then rpc.Helper.UnmountPath(mnt_comm, mount) return false, status @@ -243,12 +281,11 @@ local function report(nfs, table) return tab.dump(outtab) end -action = function(host, port) +local mainaction = function(host) local o, results, mounts, status = {}, {}, {} local nfs_info = { host = host, - port = port, --recurs = tonumber(nmap.registry.args['nfs-ls.recurs']) or 1, } @@ -275,9 +312,13 @@ action = function(host, port) table.insert(o, args) end - status, mounts = rpc.Helper.ShowMounts(nfs_info.host, nfs_info.port) + status, mounts = procedures.ShowMounts(nfs_info.host) if not status or mounts == nil then - return stdnse.format_output(false, mounts) + 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 @@ -298,3 +339,85 @@ action = function(host, port) return stdnse.format_output(true, o) 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', mountport.version.rpc_highver) + status, result = mnt_comm:Connect(ahost, mountport) + if ( not(status) ) then + stdnse.print_debug(4, "ShowMounts: %s", result) + return false, result + end + status, mounts = mnt:Export(mnt_comm) + mnt_comm:Disconnect() + if ( not(status) ) then + stdnse.print_debug(4, "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", mountport.version.rpc_highver) + + status, err = mnt_comm:Connect(host, mountport) + if not status then + stdnse.print_debug(4, "MountPath: %s", err) + return nil, err + end + + status, fhandle = mnt:Mount(mnt_comm, path) + if not status then + mnt_comm:Disconnect() + stdnse.print_debug(4, "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', nfsport.version.rpc_goodver) + status, err = nfs_comm:Connect(host, nfsport) + if not status then + stdnse.print_debug(4, "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 diff --git a/scripts/nfs-showmount.nse b/scripts/nfs-showmount.nse index 8f6ae8042..182eb53bf 100644 --- a/scripts/nfs-showmount.nse +++ b/scripts/nfs-showmount.nse @@ -32,14 +32,34 @@ license = "Same as Nmap--See http://nmap.org/book/man-legal.html" categories = {"discovery", "safe"} -portrule = shortport.port_or_service(111, "rpcbind", {"tcp", "udp"} ) +portrule = shortport.port_or_service(111, {"rpcbind", "mountd"}, {"tcp", "udp"} ) + +local function get_exports(host, port) + local mnt = rpc.Mount:new() + mnt_comm = rpc.Comm:new('mountd', port.version.rpc_highver) + status, result = mnt_comm:Connect(host, port) + if ( not(status) ) then + stdnse.print_debug(4, "get_exports: %s", result) + return false, result + end + status, mounts = mnt:Export(mnt_comm) + mnt_comm:Disconnect() + if ( not(status) ) then + stdnse.print_debug(4, "get_exports: %s", mounts) + end + return status, mounts +end action = function(host, port) local status, mounts, proto local result = {} - status, mounts = rpc.Helper.ShowMounts( host, port ) + if port.service == "mountd" then + status, mounts = get_exports( host, port ) + else + status, mounts = rpc.Helper.ShowMounts( host, port ) + end if not status or mounts == nil then return stdnse.format_output(false, mounts) diff --git a/scripts/nfs-statfs.nse b/scripts/nfs-statfs.nse index 844db48f7..386e83881 100644 --- a/scripts/nfs-statfs.nse +++ b/scripts/nfs-statfs.nse @@ -4,6 +4,7 @@ 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. @@ -40,6 +41,35 @@ categories = {"discovery", "safe"} portrule = shortport.port_or_service(111, "rpcbind", {"tcp", "udp"} ) +local mountport = nil +local nfsport = nil +hostrule = function(host) + 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 + if nfsport == nil then return false end + if nfsport.version.rpc_highver == 4 and nfsport.version.rpc_lowver <= 3 then + nfsport.version.rpc_goodver = 3 + else + nfsport.version.rpc_goodver = nfsport.version.rpc_highver + end + 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 @@ -111,12 +141,12 @@ local function nfs_filesystem_info(nfs, mount, filesystem) local nfsobj = rpc.NFS:new() local mnt_comm, nfs_comm, fhandle - mnt_comm, fhandle = rpc.Helper.MountPath(nfs.host, nfs.port, mount) + mnt_comm, fhandle = procedures.MountPath(nfs.host, mount) if mnt_comm == nil then return false, fhandle end - local nfs_comm, status = rpc.Helper.NfsOpen(nfs.host, nfs.port) + local nfs_comm, status = procedures.NfsOpen(nfs.host) if nfs_comm == nil then rpc.Helper.UnmountPath(mnt_comm, mount) return false, status @@ -124,8 +154,8 @@ local function nfs_filesystem_info(nfs, mount, filesystem) nfs.version = nfs_comm.version - -- use simple check since NFSv1 is not used anymore. - if (mnt_comm.version ~= nfs_comm.version) then + -- 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) @@ -179,16 +209,15 @@ local function nfs_filesystem_info(nfs, mount, filesystem) return true, nil end -action = function(host, port) +mainaction = function(host) local fs_info, mounts, status = {}, {}, {} local nfs_info = { host = host, - port = port, } nfs_info.human = stdnse.get_script_args('nfs-statfs.human') - status, mounts = rpc.Helper.ShowMounts( host, port ) + status, mounts = procedures.ShowMounts( host ) if (not(status)) then return stdnse.format_output(false, mounts) end @@ -204,3 +233,85 @@ action = function(host, port) 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', mountport.version.rpc_highver) + status, result = mnt_comm:Connect(ahost, mountport) + if ( not(status) ) then + stdnse.print_debug(4, "ShowMounts: %s", result) + return false, result + end + status, mounts = mnt:Export(mnt_comm) + mnt_comm:Disconnect() + if ( not(status) ) then + stdnse.print_debug(4, "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", mountport.version.rpc_highver) + + status, err = mnt_comm:Connect(host, mountport) + if not status then + stdnse.print_debug(4, "MountPath: %s", err) + return nil, err + end + + status, fhandle = mnt:Mount(mnt_comm, path) + if not status then + mnt_comm:Disconnect() + stdnse.print_debug(4, "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', nfsport.version.rpc_goodver) + status, err = nfs_comm:Connect(host, nfsport) + if not status then + stdnse.print_debug(4, "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