← Nmap Scripting Engine (NSE): Writing Your Own Scripts

Lesson 2 of 10

The Nmap Scripting Engine and its use cases

How NSE works, what scripts are used for, how to select and run them, and how to read an existing script.

20 minHands-on lab

What NSE is

The Nmap Scripting Engine is a Lua interpreter embedded in Nmap. It lets you write small scripts that run during a scan and reuse everything Nmap already provides: fast parallel I/O, target and port lists, service detection results, timing controls and output formats.

Nmap ships with hundreds of scripts and a large library of reusable Lua modules (the nselib). You can use them as they are, copy and adapt them, or write your own.

Use cases

Use caseExample scriptsWhat they do
Service discoveryhttp-title, ssh-hostkey, ssl-certGrab extra information from services already found.
Configuration auditssl-enum-ciphers, ftp-anon, smb-os-discoveryCheck settings against expectations, list weak or exposed configuration.
Vulnerability detectionhttp-vuln-cve2017-5638 and friendsTest whether a specific known flaw is present.
Enumerationhttp-enum, dns-bruteFind directories, subdomains, users, shares.
Credential checksssh-brute, ftp-bruteTry password lists (only with permission!).
Network reconbroadcast-dhcp-discoverDiscover things on the local network without a target.
Custom internal checksyour scripts“Is our internal service still on v1.4?” across a whole estate.

That last row is the reason to learn to write scripts. Off-the-shelf scripts cover public software. Your organisation’s own protocols, appliances and policies are only covered if you cover them.

Script categories

Every script declares one or more categories. They’re how you select scripts by intent and how you keep a scan safe.

CategoryMeaning
defaultRun by -sC. Should be fast, useful, safe and reliable.
safeWon’t crash services, use lots of bandwidth or exploit anything.
intrusiveMight crash a service, use lots of resources, or be noticed.
discoveryLearns more about the network (registries, directories, and so on).
versionExtends version detection. Runs only with -sV.
vulnChecks for specific vulnerabilities.
authDeals with authentication credentials (or bypassing them).
bruteBrute-force credential guessing.
exploitActively exploits a vulnerability.
dosMay cause denial of service.
fuzzerSends unexpected or randomised input.
malwareChecks for backdoors and malware infections.
broadcastDiscovers hosts by broadcasting on the local network.
externalSends data to a third party (for example a whois database).

Choosing categories honestly matters. Anyone running --script safe is trusting yours.

Running scripts

nmap -sC 10.0.0.5                              # default category
nmap --script vuln 10.0.0.5                    # a whole category
nmap --script http-title,ssl-cert 10.0.0.5     # by name
nmap --script "http-*" -p 80,443 10.0.0.5      # wildcard
nmap --script "default and safe" 10.0.0.5      # boolean expression
nmap --script "not intrusive" 10.0.0.5
nmap --script ./my-script.nse 10.0.0.5         # a file path
nmap --script-args 'http.useragent=MyScanner' --script http-title 10.0.0.5

Useful companions:

OptionPurpose
--script-help <name>Print a script’s documentation (description, usage, arguments).
--script-args / --script-args-filePass arguments to scripts.
--script-updatedbRebuild script.db, the index of names and categories.
--script-timeout <time>Cap how long a single script may run.
--script-traceShow all network traffic sent and received by scripts.

The four script types

A script’s rule decides when it runs. There are four types:

RuleRunsReceives
preruleOnce, before scanning starts.nothing
hostruleOnce per host that matches.host
portruleOnce per matching host and port.host, port
postruleOnce, after all scanning finishes.nothing

Most scripts you’ll write use a portrule: “if this port looks like a web server, run my code.”

How NSE executes scripts

Two properties shape how you write scripts:

  • Scripts run in parallel, cooperatively. Each script instance (one per host or port) runs in its own Lua coroutine. When a script waits on the network, NSE switches to another one. That’s why scanning thousands of ports with scripts is fast.
  • There is no pre-emption. A script that spins in a long CPU-bound loop blocks every other script. Keep loops small and let network calls do the waiting. (More in lesson 10.)

Where scripts live

OSDefault scripts directory
Linux/usr/share/nmap/scripts/
macOS (Homebrew)/opt/homebrew/share/nmap/scripts/ (Apple silicon) or /usr/local/share/nmap/scripts/
WindowsC:\Program Files (x86)\Nmap\scripts\

The libraries sit beside it in nselib/. Reading them is the fastest way to learn what’s available.

To use your own script by name, put it in a scripts/ directory Nmap searches (for example ~/.nmap/scripts/, or anywhere via --datadir) and run nmap --script-updatedb so it lands in script.db. Passing an explicit path (--script ./my-script.nse) always works and is what we’ll use in the labs.

Lab: read a real script

  1. The description, author, license and categories fields at the top.
  2. The @usage, @args and @output documentation comments.
  3. The portrule line. Which shortport predicate does it use?
  4. The action function. What does it return when there’s no title?

Then run it against the lab server from lesson 1:

nmap -p 8000 --script http-title 127.0.0.1
nmap -p 8000 --script-help http-title
nmap -p 8000 --script http-title -d 127.0.0.1 2>&1 | grep -i "NSE"

Checkpoint

You want a script to run only against SSH services. Which rule type, and which argument does your action receive?

A portrule, and action(host, port) receives both the host and port tables.

Why is a busy while true do loop in an action harmful even if it eventually ends?

NSE scheduling is cooperative. Nothing else runs while the loop holds the CPU, so every other script instance stalls until it yields (typically at a network call).

What does --script "default and safe" select?

Scripts that belong to both categories.