The finger script (finger.nse
) is a perfect
example of how short typical NSE scripts are.
first the information fields are filled out, note that the
id
field is kept short, this is important since it is
printed in Nmap's output. A detailed description of what the script
actually does should go in the description
field.
id="Finger Results"
description="attempts to get a list of usernames via the finger service"
author = "Eddie Bell <ejlbell@gmail.com>"
license = "See nmaps COPYING for licence"
The categories
field is a table
containing all the categories the script belongs to—These are used for
script selection through the --script
option.
categories = {"discovery"}
You can use the facilities provided by the nselib (the section called “Lua Extensions”) by requiring
them. Here
we want to use shorter port rules.
require "shortport"
We want to check whether the service behind the port is finger,
or whether it runs on finger's well known port 79. Through this we can
use the information gathered during the version scan (if finger runs
on a non-standard port) or still run against at least the port we
expect it, should the version detection information not be available.
portrule = shortport.port_or_service(79, "finger")
action = function(host, port)
local socket = nmap.new_socket()
local results = ""
local status = true
The function err_catch()
will be called for
clean up, through NSE's exception handling mechanism. Here it only
closes the previously opened socket (which should be enough in most
cases).
local err_catch = function()
socket:close()
end
The clean up function gets registered for exception handling via
a call to nmap.new_try()
local try = nmap.new_try(err_catch())
The script sets a timeout of 5000, which is equivalent to 50
seconds. Should any operation require more time we'll receive a
TIMEOUT
error message.
socket:set_timeout(5000)
For actually using exception handling we need to wrap calls to
functions, which may return an error inside
try()
try(socket:connect(host.ip, port.number, port.protocol))
try(socket:send("\n\r"))
The call to receive_lines()
is not wrapped in
try()
, because we don't want to abort the script
just because we didn't receive the data we expected. Note that there
is less data than requested (100 lines), we still receive it and the
status is true
—consequent calls would yield
a false
status.
status, results = socket:receive_lines(100)
socket:close()
The script returns a string only if we got the data we
wanted, otherwise nil
is returned (automatically, since
scripts return one result).
if not(status) then
return results
end
end
Service Owner Lookup via Identd
showOwner.nse
demonstrates the flexibility
of the NSE, which is unmatched by other parts of Nmap. If the target
is running an identd
daemon it connects to it for
each running service and tries to identify its owner.
id = "Service owner"
description = "Opens a connection to the scanned port, opens a connection to \
port 113, queries the owner of the service on the scanned port and prints it."
author = "Diman Todorov <diman.todorov@gmail.com>"
license = "See nmaps COPYING for licence"
categories = {"safe"}
Portrules are not restricted to those provided by the
short-port module (the section called “Short Portrules”).
They can be any function taking a host- and a porttable as argument and
returning a boolean.
portrule = function(host, port)
local identd, decision
In order to determine the state of a port, which is not provided
as argument we just have to construct a table describing the port
(i.e. its number and the protocol it's using) and pass it to
nmap.get_port_state()
which returns a table filled
with the information Nmap has about the port.
local auth_port = { number=113, protocol="tcp" }
identd = nmap.get_port_state(host, auth_port)
if
identd ~= nil
and identd.state == "open"
then
decision = true
else
decision = false
end
return decision
end
action = function(host, port)
local owner = ""
Scripts can open any number of connection they want.
local client_ident = nmap.new_socket()
local client_service = nmap.new_socket()
local catch = function()
client_ident:close()
client_service:close()
end
local try = nmap.new_try(catch)
try(client_ident:connect(host.ip, 113))
try(client_service:connect(host.ip, port.number))
local localip,localport,remoteip,remoteport = try(client_service:get_info())
local request = port.number .. ", " .. localport .. "\n"
try(client_ident:send(request))
owner = try(client_ident:receive_lines(1))
if string.match(owner, "ERROR") then
owner = nil
else
owner = string.match(owner, "USERID : .+ : (.+)\n", 1)
end
try(client_ident:close())
try(client_service:close())
return owner
end