1
0
mirror of https://github.com/nmap/nmap.git synced 2025-12-12 10:49:02 +00:00

Add the nfs-showmount script by Patrik Karlsson.

This commit is contained in:
david
2009-11-26 16:52:30 +00:00
parent a4c2e4fc9b
commit 971a11f3fe
3 changed files with 343 additions and 0 deletions

View File

@@ -1,5 +1,8 @@
# Nmap Changelog ($Id$); -*-text-*- # Nmap Changelog ($Id$); -*-text-*-
o Added a new script, nfs-showmount by Patrik Karlsson, that shows nfs
exports like "showmount -e".
o Added a UDP SIPOptions probe corresponding to the TCP one thanks to o Added a UDP SIPOptions probe corresponding to the TCP one thanks to
the research and testing of Patrik Karlsson and Matt Selsky. the research and testing of Patrik Karlsson and Matt Selsky.

339
scripts/nfs-showmount.nse Normal file
View File

@@ -0,0 +1,339 @@
description = [[
Shows NFS exports, like the <code>showmount -e</code> command.
]]
---
-- @output
-- PORT STATE SERVICE
-- 111/tcp open rpcbind
--
-- Host script results:
-- | nfs-showmount:
-- | /home/storage/backup 10.46.200.0/255.255.255.0 10.46.200.66/255.255.255.255
-- |_ /home 10.46.200.0/255.255.255.0
--
-- Version 0.4
-- Created 11/23/2009 - v0.1 - created by Patrik Karlsson <patrik@cqure.net>
-- Revised 11/24/2009 - v0.2 - added RPC query to find mountd ports
-- Revised 11/24/2009 - v0.3 - added a hostrule instead of portrule
-- Revised 11/26/2009 - v0.4 - reduced packet sizes and documented them
author = "Patrik Karlsson"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"discovery", "safe"}
require 'comm'
require 'datafiles'
hostrule = function(host)
local port_t111 = nmap.get_port_state(host, {number=111, protocol="udp"})
local port_u111 = nmap.get_port_state(host, {number=111, protocol="tcp"})
return ( port_t111 ~= nil and port_t111.state == "open") or
(port_u111 ~= nil and (port_u111.state == "open" or
port_u111.state == "open|filtered"))
end
--
-- Calculates the number of fill bytes needed
-- @param length contains the length of the string
-- @return the amount of pad needed to be divideable by 4
--
function calc_fill_bytes(length)
-- calculate fill bytes
if math.mod( length, 4 ) ~= 0 then
return (4 - math.mod( length, 4))
else
return 0
end
end
--
-- extracts the group from the export list entry
-- @param data string should be start with 32-bit lenght field
--
-- @return pos numeric new position within buffer, grp_val string the group contents
--
function extract_group(data)
local pos, grp_val
-- retrieve the group length
pos, grp_len = bin.unpack( ">i", data )
data = data:sub(pos)
-- retrieve the group contents
grp_val = data:sub(0, grp_len)
pos = 4 + calc_fill_bytes(grp_len) + grp_len + 1
return pos, grp_val
end
--
-- extracts the directory from the export list entry
-- @param data string should be start with 32-bit lenght field
--
-- @return pos numeric new position within buffer, dir_name string the name of the directory
--
function extract_directory(data)
local pos, dir_len, dir_name
-- retrieve the length of the directory name
pos, dir_len = bin.unpack(">i", data)
data = data:sub(pos)
-- retrieve the directory name
dir_name = data:sub(0, dir_len)
pos = 4 + calc_fill_bytes(dir_len) + dir_len + 1
return pos, dir_name
end
--
-- processes the response back from the mountd service
-- @param proto string should be either "udp" or "tcp"
-- @param data string contains the response recieved from the service
--
-- @return string with exports from NFS
--
function process_response(proto, data)
local pos, val_follows
local header = {}
local response=" \n"
-- if we're running over UDP skip first 4 bytes ( theres no 16-bit something + 16-bit length)
if "udp" == proto then
pos, header['xid'], header['type'], header['state'],
header['verifier'], header['accept_state'] = bin.unpack(">iiili", data)
else
pos, _, header['length'], header['xid'], header['type'], header['state'],
header['verifier'], header['accept_state'] = bin.unpack("S>Siiili", data)
end
data = data:sub(pos)
-- We should probably be doing a lot more verification here, but let's stick to basics
-- Was the response from the server = Reply(1) and
-- Accept state = RPC Executed succefully (0)
if header['type'] ~= 1 or header['accept_state'] ~= 0 then
return
end
--
--
-- Each export list entry consists of:
--
-- One or more Directory entries:
-- 32-bit - length
-- length - directory name
--
-- One or more Group entries:
-- 32-bit - length
-- length - group contents
--
-- Every group entry is separated by
-- 32-bit - value follows - if set to 1 more groups exist
--
-- Every directory entry is separated by
-- 32-bit - value follows - if set to 1 more entries exist
--
--
-- Note: The length specifies the amount of characters for
-- both dir and group entries
--
-- However, directories and groups are padded by zeroes so that
-- they are divideable by 4. Hence calc_fill_bytes
--
pos, val_follows = bin.unpack(">i", data)
data = data:sub(pos)
while 1 == val_follows do
local dir_name, exp_group
local grp_follows, grp_len, grp_val
groups=""
pos, dir_name = extract_directory( data )
data = data:sub(pos)
-- check if we have a group following
pos, grp_follows = bin.unpack(">i", data )
data = data:sub(pos)
while grp_follows == 1 do
pos, grp_val = extract_group( data )
groups = groups .. " " .. grp_val
data = data:sub(pos)
-- check if there's antoher group following
pos, grp_follows = bin.unpack(">i", data )
data = data:sub(pos)
end
-- concatenate our dir_name and groups to the result
response = response .. dir_name .. "" .. groups .. "\n"
-- are there any more directory entries?
pos, val_follows = bin.unpack(">i", data)
data = data:sub(pos)
end
return response
end
--
-- Ruthlessly ripped, and modified, from Sven Klemm's rpcinfo.nse script
--
function get_rpc_port_for_service(host, svc_progname, svc_version)
local socket = nmap.new_socket()
socket:set_timeout(1000)
local catch = function() socket:close() end
local try = nmap.new_try(catch)
local rpc_numbers = try(datafiles.parse_rpc())
try(socket:connect(host.ip, 111))
-- build rpc dump call packet
local transaction_id = math.random(0x7FFFFFFF)
local request = bin.pack('>IIIIIIILL',0x80000028,transaction_id,0,2,100000,2,4,0,0)
try(socket:send(request))
local answer = try(socket:receive_bytes(1))
local _,offset,header,length,tx_id,msg_type,reply_state,accept_state,value,payload,last_fragment
last_fragment = false; offset = 1; payload = ''
-- extract payload from answer and try to receive more packets if
-- RPC header with last_fragment set has not been received
-- If we can't get further packets don't stop but process what we
-- got so far.
while not last_fragment do
if offset > #answer then
local status, data = socket:receive_bytes(1)
if not status then break end
answer = answer .. data
end
offset,header = bin.unpack('>I',answer,offset)
last_fragment = bit.band( header, 0x80000000 ) ~= 0
length = bit.band( header, 0x7FFFFFFF )
payload = payload .. answer:sub( offset, offset + length - 1 )
offset = offset + length
end
socket:close()
offset,tx_id,msg_type,reply_state,_,_,accept_state = bin.unpack( '>IIIIII', payload )
-- transaction_id matches, message type reply, reply state accepted and accept state executed successfully
if tx_id == transaction_id and msg_type == 1 and reply_state == 0 and accept_state == 0 then
local dir = { udp = {}, tcp = {}}
local protocols = {[6]='tcp',[17]='udp'}
local prog, version, proto, port
local ports = {}
offset, value = bin.unpack('>I',payload,offset)
while value == 1 and #payload - offset >= 19 do
offset,prog,version,proto,port,value = bin.unpack('>IIIII',payload,offset)
proto = protocols[proto] or tostring( proto )
if rpc_numbers[prog] == svc_progname and version == svc_version then
ports[proto] = port
end
end
return ports
end
return
end
action = function(host)
local data = {}
-- packet copy/pasted from wireshark, running showmount -e
data["tcp"] = string.char(
0x80, 0x00, 0x00, 0x28, -- Fragment Length: 44 bytes (31-bit?)
-- Last Fragment: Yes
0x21, 0x00, 0x46, 0x4c, -- XID: 0x2100464c
0x00, 0x00, 0x00, 0x00, -- Message type: Call(0)
0x00, 0x00, 0x00, 0x02, -- RPC Version: 2
0x00, 0x01, 0x86, 0xa5, -- Program: MOUNT(100005)
0x00, 0x00, 0x00, 0x01, -- Program Version: 1
0x00, 0x00, 0x00, 0x05, -- Procedure: EXPORT(5)
-- Credentials
0x00, 0x00, 0x00, 0x00, -- Flavor: AUTH_NULL (0)
0x00, 0x00, 0x00, 0x00, -- Length: 0
-- Verifier
0x00, 0x00, 0x00, 0x00, -- Flavor: AUTH_NULL
0x00, 0x00, 0x00, 0x00 -- Length: 0
)
data["udp"] = string.char(
0x21, 0x00, 0x46, 0x4c, -- XID: 0x2100464c
0x00, 0x00, 0x00, 0x00, -- Message type: Call(0)
0x00, 0x00, 0x00, 0x02, -- RPC Version: 2
0x00, 0x01, 0x86, 0xa5, -- Program: MOUNT(100005)
0x00, 0x00, 0x00, 0x01, -- Program Version: 1
0x00, 0x00, 0x00, 0x05, -- Procedure: EXPORT(5)
-- Credentials
0x00, 0x00, 0x00, 0x00, -- Flavor: AUTH_NULL (0)
0x00, 0x00, 0x00, 0x00, -- Length: 0
-- Verifier
0x00, 0x00, 0x00, 0x00, -- Flavor: AUTH_NULL
0x00, 0x00, 0x00, 0x00 -- Length: 0
)
local status, result
local ports = get_rpc_port_for_service(host, "mountd", 1)
for p in pairs(ports) do
status, result = comm.exchange(host, ports[p], data[p], {proto=p})
-- Fail gracefully
if not status then
if (nmap.verbosity() >= 2 or nmap.debugging() >= 1) then
return "ERROR: TIMEOUT"
else
return
end
end
result = process_response( p, result )
if result then
return result
end
end
return
end

View File

@@ -31,6 +31,7 @@ Entry { filename = "irc-info.nse", categories = { "default", "discovery", "safe"
Entry { filename = "ms-sql-info.nse", categories = { "default", "discovery", "intrusive", } } Entry { filename = "ms-sql-info.nse", categories = { "default", "discovery", "intrusive", } }
Entry { filename = "mysql-info.nse", categories = { "default", "discovery", "safe", } } Entry { filename = "mysql-info.nse", categories = { "default", "discovery", "safe", } }
Entry { filename = "nbstat.nse", categories = { "default", "discovery", "safe", } } Entry { filename = "nbstat.nse", categories = { "default", "discovery", "safe", } }
Entry { filename = "nfs-showmount.nse", categories = { "discovery", "safe", } }
Entry { filename = "p2p-conficker.nse", categories = { "default", "safe", } } Entry { filename = "p2p-conficker.nse", categories = { "default", "safe", } }
Entry { filename = "pjl-ready-message.nse", categories = { "intrusive", } } Entry { filename = "pjl-ready-message.nse", categories = { "intrusive", } }
Entry { filename = "pop3-brute.nse", categories = { "auth", "intrusive", } } Entry { filename = "pop3-brute.nse", categories = { "auth", "intrusive", } }