Nmap API
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 aLua interpreter. Users need easy access to the informationNmap has learned about the target hosts. This data is passedas arguments to the NSE script'saction
method. The arguments,host
andport
, are Lua tables which containinformation on the target against which the script isexecuted. If a script matched a hostrule, it gets only thehost
table, and if it matched a portrule itgets bothhost
andport
.The following list describes each variable in these two tables.
host
This table is passed as a parameter to the rule and actionfunctions. It contains information on the operating system run bythe host (if the
-O
switch was supplied), theIP address and the host name of the scanned target.host.os
An array of OS match tables. An OS match consists of ahuman-readable name and an array of OS classes. Each OSclass consists of a vendor, OS family, OS generation,device type, and an array of CPE entries for the class. (Seethe section called “Decoding the Reference Fingerprint Format” for a description of OS match fields.) Fields may be
nil
if they are not defined. Thehost.os
table has this overall structure:host.os = { { name =
<string>
, classes = { { vendor =<string>
, osfamily =<string>
, osgen =<string>
, type =<string>
, cpe = { "cpe:/<...>
", [More CPE] } }, [More classes] }, }, [More OS matches]}For example, an OS match on this
nmap-os-db
entry:Fingerprint Linux 2.6.32 - 3.2Class Linux | Linux | 2.6.X | general purposeCPE cpe:/o:linux:linux_kernel:2.6Class Linux | Linux | 3.X | general purposeCPE cpe:/o:linux:linux_kernel:3
will result in this
host.os
table:host.os = { { name = "Linux 2.6.32 - 3.2", classes = { { vendor = "Linux", osfamily = "Linux", osgen = "2.6.X", type = "general purpose", cpe = { "cpe:/o:linux:linux_kernel:2.6" } }, { vendor = "Linux", osfamily = "Linux", osgen = "3.X", type = "general purpose", cpe = { "cpe:/o:linux:linux_kernel:3" } } }, }}
Only entries corresponding to perfect OS matches are put in the
host.os
table. If Nmap was run without the-O
option, thenhost.os
isnil
.host.ip
Contains a string representation of the IP address of thetarget host. If the scan was run against a host name and itsDNS lookup returned more than one IP addresses, then thesame IP address is used as the one chosen for the scan.
host.name
Contains the reverse DNS entry of the scanned target hostrepresented 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 IPaddress the value of the field is
nil
.host.reason
Contains a string representation of the reason why the target host is in its current state. The reason is given by the type of the packet that determined the state. For example, an
echo-reply
from an alive host.host.reason_ttl
Contains the TTL value of the response packet, that was used to determine the status of the target host, when it arrived. This response packet is the packet that is also used to set
host.reason
.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) if available, otherwise
nil
. The MAC address is generally only available for hosts directly connected on a LAN and only if Nmap is doing a raw packet scan such as SYN scan.host.mac_addr_next_hop
MAC address of the first hop in the route to the host, or
nil
if not available.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.interface_mtu
The MTU (maximum transmission unit) for
host.interface
,or 0 if not known.host.bin_ip
The target host's IP address as a 4-byte (IPv4) or 16-byte (IPv6) string.
host.bin_ip_src
Our host's (running Nmap) source IP address as a 4-byte (IPv4) or 16-byte (IPv6) string.
host.times
This table contains Nmap's timing data for the host (seethe section called “Round Trip Time Estimation”). Its keys are
srtt
(smoothedround trip time),rttvar
(round trip time variance), andtimeout
(the probe timeout), all given in floating-point seconds.host.traceroute
This is an array of traceroute hops, present when the
--traceroute
option was used. Each entry is a host table with fieldsname
,ip
andsrtt
(round trip time). The TTL for an entry is implicit given its position in the table. An empty table represents a timed-out hop.host.os_fp
If OS detection was performed, this is a string containing the OS fingerprint for the host. The format is described inthe section called “Understanding an Nmap Fingerprint”.
port
The port table is passed to an NSE service script (i.e. only those with a portrule rather than a hostrule) in the samefashion as the host table. It contains information about the portagainst which the script is running. While this table is not passed to host scripts, port states on the target can still be requested from Nmapusing the
nmap.get_port_state()
andnmap.get_ports()
calls.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 servicedetection. If theport.version.service_dtype
field is"table"
, Nmap has guessed the service basedon the port number. Otherwise version detection was able to determine the listening service and this field is equal toport.version.name
.port.reason
Contains a string representation of the reason why the target port is in its current state (given by
port.state
). The reason is given by the type of the packet that determined the state. For example, aRST
packet from a closed port orSYN-ACK
from an open port.port.reason_ttl
Contains the TTL value of the response packet, that was used to determine the status of the target port, when it arrived. This response packet is the packet that is also used to set
port.reason
.port.version
This entry is a table which contains informationretrieved by the Nmap version scanning engine. Someof the values (such as service name, service typeconfidence, and the RPC-related values) may be retrieved byNmap even if a version scan was not performed. Valueswhich were not determined default to
nil
. The meaning of each value is given in the following table:Table 9.1.port.version
valuesName 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. Ifport.version.service_dtype
is"table"
, this is 3.product
,version
,extrainfo
,hostname
,ostype
,devicetype
These five variables are the same as those described under <versioninfo>
inthe 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 inthe section called “Community Contributions”. service_dtype
Contains the string "table"
or"probed"
based on whether or not Nmap deducedport.version.name
from thenmap-services
file or from a service probe match.cpe
List of CPE codes for the detected service. As described in theofficial CPE specification these strings all start with the cpe:/
prefix.port.state
Contains information on the state of the port.Service scripts are only run against ports in the
open
oropen|filtered
states, soport.state
generally contains oneof those values. Other values might appear if the porttable is a result of theget_port_state
orget_ports
functions. You can adjust the port state using thenmap.set_port_state()
call. This isnormally done when anopen|filtered
port is determined to beopen
.
Network I/O API
To allow for efficient and parallelizable network I/O, NSEprovides an interface to Nsock, the Nmap socket library. Thesmart callback mechanism Nsock uses is fully transparent toNSE scripts. The main benefit of NSE's sockets is that theynever 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 isblocking. Even blocking I/O calls return once aspecified timeout has been exceeded. Two flavors of Network I/O are supported: connect-style and raw packet.
Connect-style network I/O
This 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 callingnmap.new_socket
, which returns a socket object. The socket object supports the usualconnect
,send
,receive
, andclose
methods. Additionally the functionsreceive_bytes
,receive_lines
, andreceive_buf
allow greater control over data reception.Example 9.3 shows the use of connect-style network operations. Thetry
function is used for error handling, as described inthe section called “Exception Handling”.
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()
Raw packet network I/O
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.
Thepcap_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 thepcap_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 thepcap_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 thepcap_receive
method. Register the empty string to receive all packets.
A script receives all packets for which a listener has been registered by calling thepcap_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 withnmap.new_socket
and later close the socket withsocket_object:close
—just like with the connection-based network I/O.
While receiving packets is important, sending them is certainly a key feature as well. To accomplish this, NSE provides access to sending at the IP and Ethernet layers. Raw packet writes do not use the same socket object as raw packet reads, so thenmap.new_dnet
function is called to create the required object for sending. After this, a raw socket or Ethernet interface handle can be opened for use.
Once the dnet object is created, the functionip_open
can be called to initialize the object for IP sending.ip_send
sends the actual raw packet, which must start with the IP header. The dnet object places no restrictions on which IP hosts may be sent to, so the same object may be used to send to many different hosts while it is open. To close the raw socket, callip_close
.
For sending at a lower level than IP, NSE provides functions for writing Ethernet frames.ethernet_open
initializes the dnet object for sending by opening an Ethernet interface. The raw frame is sent withethernet_send
. To close the handle, callethernet_close
.
Sometimes the easiest ways to understand complex APIs is by example. Theipidseq
script included with Nmap uses raw IP packets to test hosts for suitability for Nmap's Idle Scan (-sI
). Thesniffer-detect
script also included with Nmap uses raw Ethernet frames in an attempt to detect promiscuous-mode machines on the network (those running sniffers).
Structured and Unstructured Output
NSE scripts should usually return a table representing their output, one that is nicely organized and has thoughtfully chosen keys. Such a table will be automatically formatted for screen output and will be stored as nested elements in XML output. Having XML output broken down logically into keys and values makes it easier for other tools to make use of script output. It is possible for a script to return only a string, but doing so is deprecated. In the past, scripts could only return a string, and their output was simply copied to the XML as a blob of text–this is now known as“unstructured output”.
Suppose a script calleduser-list
returns a table as shown in this code sample. The following paragraphs show how it appears in normal and XML output.
local output = stdnse.output_table()output.hostname = "slimer"output.users = {}output.users[#output.users + 1] = "root"output.users[#output.users + 1] = "foo"output.users[#output.users + 1] = "bar"return output
A Lua table is converted to a string for normal output. The way this works is: each nested table gets a new level of indentation. Table entries with string keys are preceded by the key and a colon; entries with integer keys simply appear in order. Unlike normal Lua tables, which are unordered, a table that comes fromstdnse.output_table
will keep its keys in the order they were inserted.Example 9.4, “Automatic formatting of NSE structured output” shows how the example table appears in normal output.
PORT STATE SERVICE1123/tcp open unknown| user-list:| hostname: slimer| users:| root| foo|_ bar
The XML representation of a Lua table is constructed as follows. Nested table becometable
elements. Entries of tables that are not themselves tables becomeelem
elements. Entries (whethertable
orelem
) with string keys get akey
attribute (e.g.<elem key="username">foo</elem>
); entries with integer keys have nokey
element and their key is implicit in the order in which they appear.
In addition to the above, whatever normal output the script produces (even if automatically generated) is copied to theoutput
attribute of thescript
element. Newlines and other special characters will be encoded as XML character entities, for example

.Example 9.5, “NSE structured output in XML” shows how the example table appears in XML.
<script output="
hostname: slimer
users: 
 root
 foo
 bar"> <elem key="hostname">slimer</elem> <table key="users"> <elem>root</elem> <elem>foo</elem> <elem>bar</elem> </table></script>
Some scripts need more control their normal output. This is the case, for example, with scripts that need to display complex tables. For complete control over the output, these scripts may do either of these things:
return a string as second return value, or |
set the__tostring metamethod on the returned table. |
The resulting string will be used in normal output, and the table will be used in XML as usual. The formatted string may contain newline characters to appear as multiple lines.
If the above code example were modified in this way to return a formatted string,
local output = stdnse.output_table()output.hostname = "slimer"output.users = {}output.users[#output.users + 1] = "root"output.users[#output.users + 1] = "foo"output.users[#output.users + 1] = "bar"local output_str = string.format("hostname: %s\n", output.hostname)output_str = output_str .. "\n" .. stringaux.strjoin(", ", output.users)return output, output_str
then the normal output would appear as follows:
PORT STATE SERVICE1123/tcp open unknown| user-list:| hostname: slimer|_ users: root, foo, bar
There are conventions regarding the formatting of certain kinds of data in structured output. Users of NSE output benefit by being able to assume that some kinds of data, for instance dates and times, are formatted the same way, even in different scripts.
Network addresses, for example IPv4, IPv6, and MAC, are represented as strings.
Long hexadecimal strings such as public key fingerprints should be written using lower-case alphabetical characters and without separators such as colons.
Dates and times are formatted according toRFC 3339. If the time zone offset is known, they should appear like these examples:
2012-09-07T23:37:42+00:002012-09-07T23:37:42+02:00
If the time zone offset is not known (representing some unspecified local time), leave off the offset part:
2012-09-07T23:37:42
The library functiondatetime.format_timestamp
code exists to format times for structured output. It takes an optional time zone offset in seconds and automatically shifts the date to be correct within that offset.
datetime.format_timestamp(os.time(), 0) --> "2012-09-07T23:37:42+00:00"
Exception Handling
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. Thenmap.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 tonew_try
which will be called if an exception is caught. The function would generally perform any required cleanup operations.
Example 9.6 shows cleanup exception handling at work. A new function namedcatch
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.
local result, socket, try, catchresult = ""socket = nmap.new_socket()catch = function() socket:close() endtry = 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 istrue
upon successful completion of the function andfalse
(ornil
) 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 notnil
orfalse
it is treated astrue
so you can return your value in the normal case and returnnil,
if an error occurs.<error description>
The Registry
Scripts can share information by storing values in aregister, which is a special table that can beaccessed by all scripts. There is a global registry with the namenmap.registry
, shared by all scripts. Each hostadditionally has its own registry calledhost.registry
, wherehost
is thehost table passed to a script.Information in the registries is not stored between Nmapexecutions.
The global registry persists throughout an entire scan session.Scripts can use it, for example, to store values that will later bedisplayed by a postrule script. The per-host registries, on the otherhand, only exist while a host is being scanned. They can be used to sendinformation from one script to another one that runs against the samehost. When possible, use the per-host registry; this not only saves youfrom having to make key names unique across hosts, but also allows thememory used by the registry to be reclaimed when it is no longerneeded.
Here are examples of using both registries:
The portrule of thessh-hostkey script collects SSH key fingerprintsand stores them in the globalnmap.registry so theycan be printed later by the postrule. |
Thessl-cert script collects SSL certificates andstores them in the per-host registry so that thessl-google-cert-catalog script can use them withouthaving to make another connection to the server. |
Because every script can write to the global registry table, it isimportant to make the keys you use unique, to avoid overwriting the keysof other scripts (or the same script running in parallel).
Scripts that use the results of another script must declare it usingthedependencies
variable to make sure that the earlierscript runs first.