The Nmap Scripting Engine offers finer control over script parallelism by implementing threads, condition variables, and mutexes. Each NSE script is normally executed inside a Lua coroutine or thread but it may yield additional worker threads if the programmer decides to do so.
This recipe will teach you how to deal with parallelism in NSE.
NSE threads are recommended for scripts that need to perform network operations in parallel. Let's see how to deal with parallelism in our scripts:
new_thread()
from the library stdnse
:local co = stdnse.new_thread(worker_main_function, arg1, arg2, arg3, ...)
local my_mutex = nmap.mutex(object)
nmap.mutex(object)
can be locked as follows:my_mutex("trylock")
"done"
:my_mutex("done")
nmap.condvar(object)
:local o = {} local my_condvar = nmap.condvar(o)
my_condvar("signal")
NSE scripts transparently yield when a network operation occurs. Script writers may want to perform parallel networking tasks, like the script http-slowloris
which opens several sockets and keeps them open concurrently. NSE threads solve this problem by allowing script writers to yield parallel network operations.
The function stdnse.new_thread
receives as the first argument the new worker's main function. This function will be executed after the new thread is created. Script writers may pass any additional arguments as optional parameters in stdnse.new_thread()
.
local co = stdnse.new_thread(worker_main_function, arg1, arg2, arg3, ...)
The worker's return values are ignored by NSE and they can't report script output. The official documentation recommends using upvalues
, function parameters, or environments to report results back to the base thread.
After execution, it returns the base coroutine and a status query function. This status query function returns up to two values: the results of coroutine.status
using the base coroutine
and, if an error occurs, an error object.
Mutexes or mutual exclusive objects were implemented to protect resources such as NSE sockets. The following operations can be performed on a mutex:
lock
: Locks the mutex. If the mutex is taken, the worker thread will yield and wait until it is released.trylock
: Attempts to lock the mutex in a non-blocking way. If the mutex is taken, it will return false. (It will not yield as in the function lock
.)done
: Releases the mutex. Other threads can lock it after this.running
: This function should not be used at all other than for debugging, because it affects the thread collection of finished threads.Condition variables were implemented to help developers coordinate the communication between threads. The following operations can be performed on a conditional variable:
broadcast
: Resumes all threads in the condition variable queuewait
: Adds the current thread to the waiting queue on the condition variablesignal
: Signals a thread from the waiting queueTo read implementations of script parallelism, I recommend that you read the source code of the NSE scripts broadcast-ping
, ssl-enum-ciphers
, firewall-bypass
, http-slowloris
, or broadcast-dhcp-discover
.
Lua provides an interesting feature called coroutines. Each coroutine has its own execution stack. The most important part is that we can suspend and resume the execution via coroutine.resume()
and coroutine.yield()
. The function stdnse.base()
was introduced to help identify if the main script thread is still running. It returns the base coroutine of the running script.
You can learn more about coroutines from Lua's official documentation:
If something unexpected happens, turn on debugging to get additional information. Nmap uses the flag -d
for debugging and you can set any integer between 0 and 9:
$ nmap -p80 --script http-google-email -d4 <target>
The library nmap
provides an exception handling mechanism for NSE scripts that is designed to help with networking I/O tasks.
The exception handling mechanism from the nmap
library works as expected. We wrap the code that we want to monitor for exceptions inside a nmap.try()
call. The first value returned by the function indicates the completion status. If it returns false
or nil
, the second returned value must be an error string. The rest of the return values in a successful execution can be set and used as you wish. The catch
function defined by nmap.new_try()
will execute when an exception is raised.
The following example is a code snippet of the script mysql-vuln-cve2012-2122.nse
(http://nmap.org/nsedoc/scripts/mysql-vuln-cve2012-2122.html). In this script a catch
function performs some simple garbage collection if a socket is left open:
local catch = function() socket:close() end local try = nmap.new_try(catch) … try( socket:connect(host, port) ) response = try( mysql.receiveGreeting(socket) )
The official documentation of the NSE library nmap
can be found at http://nmap.org/nsedoc/lib/nmap.html.
52.15.80.101