Nmap Security Scanner
Intro
Ref Guide
Install Guide
Download
Changelog
Book
Docs
Security Lists
Nmap Hackers
Nmap Dev
Bugtraq
Full Disclosure
Pen Test
Basics
More
Security Tools
Pass crackers
Sniffers
Vuln Scanners
Web scanners
Wireless
Exploitation
Packet crafters
More
Site News
Site Search:
Exploit World
Advertising
About/Contact
Credits
Sponsors:
|

NSE scripts have access to several Nmap facilities for writing
flexible and elegant scripts. The API provides target host
details such as port states and version detection results. It
also offers an interface to the Nsock
library
for efficient network I/O.
Information Passed to a Script
An effective Nmap scripting engine requires more than just a
Lua interpreter. Users need easy access to the information
Nmap has learned about the target hosts. This data is passed
as arguments to the NSE script's
action method.
The arguments, host and
port, are Lua tables which contain
information on the target against which the script is
executed. If a script matched a hostrule, it gets only the
host table, and if it matched a portrule it
gets both host and port.
The following list describes each variable in these two tables.
host
This table is passed as a parameter to the rule and action
functions. It contains information on the operating system run by
the host (if the -O switch was supplied), the
IP address and the host name of the scanned target.
host.os
The os entry in the host table is
an array of strings. The strings (as many as eight) are the
names of the operating systems the target is possibly
running. Strings are only entered in this array if the
target machine is a perfect match for one or more OS
database entries. If Nmap was run without the
-O option, then
host.os is nil.
host.ip
Contains a string representation of the IP address of the
target host. If the scan was run against a host name and the
reverse DNS query returned more than one IP addresses then the
same IP address is used as the one chosen for the scan.
host.name
Contains the reverse DNS entry of the scanned target host
represented as a string. If the host has no reverse DNS entry,
the value of the field is an empty string.
host.targetname
Contains the name of the host as specified on the command line.
If the target given on the command line contains a netmask or is an IP
address the value of the field is nil.
host.directly_connected
A Boolean value indicating whether or not the target host is
directly connected to (i.e. on the same network segment as) the host running Nmap.
host.mac_addr
MAC address
of the destination host (six-byte long binary
string) or nil, if the host is not directly connected.
host.mac_addr_src
Our own MAC address, which was used to connect to the
host (either our network card's, or (with
--spoof-mac)
the spoofed address).
host.interface
A string containing the interface name
(dnet-style)
through
which packets to the host are sent.
host.bin_ip
The target host's IPv4 address as a 32-bit binary value.
host.bin_ip_src
Our host's (running Nmap) source IPv4 address as a 32-bit binary value.
port
The port table is passed to an NSE service script (i.e. only those with a portrule rather than a hostrule) in the same
fashion as the host table. It contains information about the port
against which the script is running. While this table is not passed to host scripts, port states on the target can still be requested from Nmap
using the nmap.get_port_state() call.
port.number
Contains the port number of the target port.
port.protocol
Defines the protocol of the target port. Valid values are
"tcp" and "udp".
port.service
Contains a string representation of the service running on
port.number as detected by the Nmap service
detection. If the port.version field is
nil, Nmap has guessed the service based
on the port number. Otherwise version detection was able to determine the listening service and this field is equal to
port.version.name.
port.version
This entry is a table which contains information
retrieved by the Nmap version scanning engine. Some
of the values (such as service name, service type
confidence, and the RPC-related values) may be retrieved by
Nmap even if a version scan was not performed. Values
which were not determined default to
nil. The meaning of each value is given in the following table: Table 9.1. port.version values | Name | Description |
|---|
name | Contains the service name Nmap decided on for the port. | name_confidence | Evaluates how confident Nmap is about the accuracy of name, from 1 (least confident) to 10. | product, version, extrainfo, hostname, ostype, devicetype | These five variables are the same as those described under <versioninfo> in the section called “match Directive”.
| service_tunnel | Contains the string "none" or "ssl" based on whether or not Nmap used SSL tunneling to detect the service. | service_fp | The service fingerprint, if any, is provided in this value. This is described in
the section called “Community Contributions”.
| rpc_status | Contains a string value of good_prog if
we were able to determine the program number of an RPC service
listening on the port, unknown if the port
appears to be RPC but we couldn't determine the program
number, not_rpc if the port doesn't appear be
RPC, or untested if we haven't checked for RPC
status. | rpc_program, rpc_lowver, rpc_highver | The detected RPC program number and the range of version
numbers supported by that program. These will be
nil if rpc_status is
anything other than good_prog. |
port.state
Contains information on the state of the port.
Service scripts are only run against ports in the
open or
open|filtered states, so
port.state generally contains one
of those values. Other values might appear if the port
table is a result of the
get_port_state function. You can
adjust the port state using the
nmap.set_port_state() call. This is
normally done when an open|filtered
port is determined to be open.
To allow for efficient and parallelizable network I/O, NSE
provides an interface to Nsock, the Nmap socket library. The
smart callback mechanism Nsock uses is fully transparent to
NSE scripts. The main benefit of NSE's sockets is that they
never block on I/O operations, allowing many scripts to be run in parallel.
The I/O parallelism is fully transparent to authors of NSE scripts.
In NSE you can either program as if you were using a single
non-blocking socket or you can program as if your connection is
blocking. Even blocking I/O calls return once a
specified timeout has been exceeded. Two flavors of Network I/O are
supported: connect-style and raw packet.
Connect-style network I/OThis part of the network API should be suitable for most
classical network uses: Users create a socket, connect it to a
remote address, send and receive data and finally close the socket.
Everything up to the Transport layer (which is either TCP, UDP or
SSL) is handled by the library.
An NSE socket is created by calling
nmap.new_socket, which returns a socket object.
The socket object supports the usual connect,
send, receive, and
close methods. Additionally the functions
receive_bytes,
receive_lines, and
receive_buf allow greater control
over data reception.
Example 9.2
shows the use of connect-style network operations. The
try function is used for error handling, as described in
the section called “Exception Handling”.
Example 9.2. Connect-style I/O require("nmap")
local socket = nmap.new_socket()
socket:set_timeout(1000)
try = nmap.new_try(function() socket:close() end)
try(socket:connect(host.ip, port.number))
try(socket:send("login"))
response = try(socket:receive())
socket:close()
For those cases where the connection-oriented approach is too high-level,
NSE provides script developers with the
option of raw packet network I/O. Raw packet reception is handled through a
Libpcap
wrapper inside the Nsock
library.
The steps are to open a capture device, register listeners
with the device, and then process packets as they are
received. The pcap_open method creates a handle for raw socket reads from an
ordinary socket object. This method takes a
callback function, which computes a packet hash from
a packet (including its headers). This hash can return any
binary string, which is later compared to the strings
registered with the pcap_register
function. The packet hash callback will normally extract some
portion of the packet, such as its source address. The pcap reader is instructed to listen for certain
packets using the pcap_register function.
The function takes a binary string which is compared against
the hash value of every packet received. Those packets whose
hashes match any registered strings will be returned by the
pcap_receive method. Register the empty
string to receive all packets. A script receives all packets for which a listener has
been registered by calling the
pcap_receive method. The method blocks
until a packet is received or a timeout occurs. The more general the packet hash computing function is
kept, the more scripts may receive the packet and proceed with
their execution. To handle packet capture inside your
script you first have to create a socket with
nmap.new_socket and later close the socket
with socket_object:close—just like
with the connection-based network I/O.
Receiving raw packets is important, but sending them is a key feature as well. To accomplish this, NSE can
access a wrapper around the
libdnet library. Raw packet writes do not
use a standard socket object like reads do. Instead, call the function
nmap.new_dnet to create a dnet object
with ethernet sending methods. Then open an interface with the
ethernet_open method. Raw ethernet
frames can then be sent
with ethernet_send. When you're done,
close the ethernet handle
with ethernet_close. Sometimes the easiest ways to understand complex APIs
is by example. The sniffer-detect.nse
script included with Nmap uses raw packet capture and
sending in an attempt to detect promiscuous-mode machines on
the network (those running sniffers).
Each script execution thread (e.g. ftp-anon running against an FTP server on the target host) yields to other
scripts whenever it makes a call on network objects (sending or receiving
data). Some scripts require finer concurrency control over thread execution. An
example is the whois script which queries
whois
servers for each target IP address. Because many concurrent queries often result in
getting one's IP banned for abuse, and because a single query may return additional
information for targets other threads are running against, it is useful
to have other threads pause while one thread performs a query.
To solve this problem, NSE includes a
mutex function which provides a
mutex
(mutual exclusion object) usable by scripts. The mutex allows
for only one thread to be working on an object. Competing threads
waiting to work on this object are put in the waiting queue
until they can get a "lock" on the mutex. A solution for
the whois problem above is to have each
thread block on a mutex using a common string, thus ensuring
that only one thread is querying whois servers at once. That
thread can store the results in the NSE registry before
releasing unlocking the mutex. The next script in the waiting
queue can then run. It will first check the registry and only
query whois servers if the previous results were insufficient.
The first step is to create a mutex object using a statement such as: mutexfn = nmap.mutex(object)
The mutexfn returned is a function
which works as a mutex for the object passed
in. This object can be any
Lua data
type except nil,
booleans, and numbers.
The returned function allows you to lock, try to lock, and
release the mutex. Its first and only parameter must be one of
the following: "lock"Make a blocking lock on the mutex. If the mutex is busy (another thread has a lock on it), then the thread will yield and wait. The function returns with the mutex locked. "trylock"Makes a non-blocking lock on the mutex. If the mutex is
busy then it immediately returns with a return value of
false. Otherwise the mutex locks the
mutex and returns true. "done"Releases the mutex and allows
another thread to lock it. If the thread does not have a lock on the mutex, an
error will be raised. "running"Returns the thread locked
on the mutex or nil if the mutex is not
locked. This should only be used for debugging as it
interferes with garbage collection of finished threads.
A simple example of using the API is provided in Example 9.3. For real-life examples, read the asn-query.nse and whois.nse scripts in the Nmap distribution. Example 9.3. Mutex manipulation local mutex = nmap.mutex("My Script's Unique ID");
function action(host, port)
mutex "lock";
-- Do critical section work - only one thread at a time executes this.
mutex "done";
return script_output;
end
NSE provides an exception handling mechanism which is not present in
the base Lua language. It is tailored
specifically for network I/O operations, and
follows a functional programming paradigm rather than an
object oriented one. The nmap.new_try API method is used to
create an exception handler. This method returns a function which takes a variable
number of arguments that are assumed to be the return values of
another function. If an exception is detected in the return
values (the first return value is false),
then the script execution is aborted and no
output is produced. Optionally, you can pass a function to
new_try which will be called
if an exception is caught. The function would generally perform any required cleanup operations.
Example 9.4 shows cleanup
exception handling at work. A new function named
catch is defined to simply close the
newly created socket in case of an error. It is then used
to protect connection and communication attempts on that
socket. If no catch function is specified, execution of the
script aborts without further ado—open sockets will
remain open until the next run of Lua's garbage
collector. If the verbosity level is at least one or if the
scan is performed in debugging mode a description of the
uncaught error condition is printed on standard output.
Note that it is currently not easily possible to group
several statements in one try block.
Example 9.4. Exception handling example local result, socket, try, catch
result = ""
socket = nmap.new_socket()
catch = function()
socket:close()
end
try = nmap.new_try(catch)
try(socket:connect(host.ip, port.number))
result = try(socket:receive_lines(1))
try(socket:send(result))
Writing a function which is treated properly by the
try/catch mechanism is straightforward. The function should
return multiple values. The first value should be a Boolean
which is true upon successful completion of the function and
false otherwise. If the function completed successfully, the try
construct consumes the indicator value and returns the
remaining values. If the function failed then the second
returned value must be a string describing the error
condition. Note that if the value is not
nil or false it is
treated as true so you can return your
value in the normal case and return nil, <error description>
if an error occurs.
The registry is a Lua table (accessible
as nmap.registry) with the special property
that it is visible by all scripts and retains its state
between script executions. The registry is transient—it
is not stored between Nmap executions. Every script can read
and write to the registry. Scripts commonly use it to save
information for other instances of the same script. For
example, the whois
and asn-query scripts may query one IP
address, but receive information which may apply to tens of
thousands of IPs on that network. Saving the information in
the registry may prevent other script threads from having to
repeat the query. The registry may also be used to hand
information to completely different scripts. For example,
the snmp-brute script saves a discovered
community name in the registry where it may be used by other
SNMP scripts. Scripts which leave information behind for a
second script must have a lower runlevel
than that second script, or there is no guarantee that they
will run first.
Because every script can write to the registry table, it
is important to avoid conflicts by choosing keys wisely
(uniquely). |
|