Writing Nmap NSE scripts for vulnerability scanning

Nmap Scripting Engine became a part of the mainline codebase with the release of Nmap 4.21ALPHA1 back in December, 2006. Today, the NSE library has grown to more than 400 scripts covering an amazing array of different network technologies (from SMB vulnerability checks to Stuxnet detection and everything in between). The power of NSE lies in its versatile library collection which allows easy interaction with most major network services and protocols.

Often times it is necessary to scan a large number of hosts for a new vulnerability which does not yet have signatures in existing scanning engines. The challenge is to quickly develop a signature and scan your enterprise to enumerate hosts running the application and check them for the vulnerability.

You may be familiar with a scripting language (e.g. Python, Perl, etc.) and are likely to quickly prototype a vulnerability check for the application. However, once faced with scanning hundreds or thousands of IP addresses, you will quickly realize that what works great for one or two targets is terribly inefficient for a larger number of hosts.

Nmap to the rescue! By using a combination of embedded Lua language and a powerful collection of libraries you will be able to develop the same solution that will work on a large scale all while using nmap’s lightning fast host and port scanning engine.

Imlementation

Skeleton Code

-- The Head Section --
-- The Rule Section --
portrule = function(host, port)
return port.protocol == "tcp"
and port.number == 80
and port.state == "open"
end

-- The Action Section --
action = function(host, port)
return "Hello world!"
end

NOTE: Anything beginning with is a comment.

NSE scripts consist of three sections:

  • The Head Section contains meta-data which describes script’s functionality, author, impact, category and other descriptive data. This section will be left blank for now; however, we will populate it later once the sample nse script is more complete.
  • The Rule Section defines necessary conditions for the script to execute. This section must contain at least one function from this list: portrule, hostrule, prerule, postrule. For the purposes of this tutorial (and the majority of scripts), I will concentrate on the portrule which can perform checks on both host and port properties before deciding to run. In the script above, portrule takes advantage of nmap’s API to check for an open TCP port 80.
  • The Action Section defines the script logic. In the tradition of K&R, I will simply output “Hello world!” for any open port 80. It is important to note that script output displayed during nmap’s execution will be based on the string returned by this section.

Let’s run the above script as follows:

# nmap --script http-vuln-check thesprawl.org -p 22,80,443

Starting Nmap 6.25 ( http://nmap.org ) at 2013-01-09 02:36 EST
Nmap scan report for thesprawl.org (108.59.3.64)
Host is up (0.023s latency).
rDNS record for 108.59.3.64: web219.webfaction.com
PORT STATE SERVICE
22/tcp filtered ssh
80/tcp open http
|_http-vuln-check: Hello world!
443/tcp open https

Nmap done: 1 IP address (1 host up) scanned in 1.77 seconds

http-vuln-check was triggered for an open TCP port 80 just as we have specified. It was not triggered for a filtered ssh port or an open https port since they were not specified in the portrule function. The output of the action function was displayed in the script output.

Using NSE Libraries

-- The Rule Section --
portrule = shortport.http

-- The Action Section --
action = function(host, port)
return "Hello world!"
end

Repeating the same nmap scan will yield slightly different results:

# nmap --script http-vuln-check thesprawl.org -p 22,80,443

Starting Nmap 6.25 ( http://nmap.org ) at 2013-01-09 02:51 EST
Nmap scan report for thesprawl.org (108.59.3.64)
Host is up (0.032s latency).
rDNS record for 108.59.3.64: web219.webfaction.com
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
|_http-vuln-check: Hello world!
443/tcp open https
|_http-vuln-check: Hello world!

Nmap done: 1 IP address (1 host up) scanned in 0.19 seconds

The script has now executed on port 443/tcp as well as 80/tcp. This is because shortport.http will be true for any of the likely HTTP ports (80, 443, 631, 7080, 8080, 8088, 5800, 3872, 8180, 8000). Even more exciting, shortport.http will actually match based on nmap’s service detection for any “http”, “https”, “ipp”, “http-alt”, “vnc-http”, “oem-agent”, “soap”, “http-proxy” services running on a non-standard port as well. That’s some powerful stuff! Check NSE Library shortport documentation for additional details.

Service Detection

local shortport = require "shortport"
local http = require "http"

-- The Rule Section --
portrule = shortport.http

-- The Action Section --
action = function(host, port)

local uri = "/arcticfission.html"
local response = http.get(host, port, uri)
return response.status

end

In the code snippet above, I have used NSE Library http to quickly retrieve and process web pages.

# nmap --script http-vuln-check localhost thesprawl.org -p 80,443
Starting Nmap 6.25 ( http://nmap.org ) at 2013-01-09 03:44 EST
Nmap scan report for localhost (127.0.0.1)
Host is up (0.000040s latency).
Other addresses for localhost (not scanned): 127.0.0.1
PORT STATE SERVICE
80/tcp open http
|_http-vuln-check: 200
443/tcp open https
|_http-vuln-check: 200

Nmap scan report for thesprawl.org (108.59.3.64)
Host is up (0.023s latency).
rDNS record for 108.59.3.64: web219.webfaction.com
PORT STATE SERVICE
80/tcp open http
|_http-vuln-check: 404
443/tcp open https
|_http-vuln-check: 404

Nmap done: 2 IP addresses (2 hosts up) scanned in 8.63 seconds

The above output shows two webservers with and without ‘arcticfission.html’ file. Notice that ‘http’ library works transparently for https and http ports, so you don’t have to implement any additional TLS/SSL logic.

Let’s introduce additional code to only return script output for services which are running the vulnerable web application:

local shortport = require "shortport"
local http = require "http"

-- The Rule Section --
portrule = shortport.http

-- The Action Section --
action = function(host, port)

local uri = "/arcticfission.html"
local response = http.get(host, port, uri)

if ( response.status == 200 ) then
return response.body
end

end

In the script above, I am returning HTTP response body in case the response.code is equal to 200.

Notice that not returning anything or returning an empty string (“”) will result in no script output being displayed at all (even script’s name):

# nmap --script http-vuln-check localhost thesprawl.org -p 80,443

Starting Nmap 6.25 ( http://nmap.org ) at 2013-01-09 04:03 EST
Nmap scan report for localhost (127.0.0.1)
Host is up (0.000049s latency).
Other addresses for localhost (not scanned): 127.0.0.1
PORT STATE SERVICE
80/tcp open http
| http-vuln-check: <html>
| <head>
| <title>ArcticFission 1.0</title>
| </head>
| <body>
| <h1>Welcome to ArcticFission 1.0</h1>
| </body>
|_</html>
443/tcp open https
| http-vuln-check: <html>
| <head>
| <title>ArcticFission 1.0</title>
| </head>
| <body>
| <h1>Welcome to ArcticFission 1.0</h1>
| </body>
|_</html>

Nmap scan report for thesprawl.org (108.59.3.64)
Host is up (0.023s latency).
rDNS record for 108.59.3.64: web219.webfaction.com
PORT STATE SERVICE
80/tcp open http
443/tcp open https

Nmap done: 2 IP addresses (2 hosts up) scanned in 8.64 seconds

At this point we can clearly identify hosts running ArcticFission and are now ready to check for the actual vulnerability.

Vulnerability Detection

local shortport = require "shortport"
local http = require "http"
local string = require "string"

-- The Rule Section --
portrule = shortport.http

-- The Action Section --
action = function(host, port)

local uri = "/arcticfission.html"
local response = http.get(host, port, uri)

if ( response.status == 200 ) then
local title = string.match(response.body, "<[Tt][Ii][Tt][Ll][Ee][^>]*>ArcticFission ([^<]*)</[Tt][Ii][Tt][Ll][Ee]>")
return title
end

end

Using some straightforward regex from Lua’s string library, we can extract and display page title:

# nmap --script http-vuln-check localhost -p 80,443

Starting Nmap 6.25 ( http://nmap.org ) at 2013-01-09 04:17 EST
Nmap scan report for localhost (127.0.0.1)
Host is up (0.000031s latency).
Other addresses for localhost (not scanned): 127.0.0.1
PORT STATE SERVICE
80/tcp open http
|_http-vuln-check: 1.0
443/tcp open https
|_http-vuln-check: 1.0

At this point, all we have to do is compare the extracted value against a known vulnerable version and report appropriate results:

local shortport = require "shortport"
local http = require "http"
local string = require "string"

-- The Rule Section --
portrule = shortport.http

-- The Action Section --
action = function(host, port)

local uri = "/arcticfission.html"
local response = http.get(host, port, uri)

if ( response.status == 200 ) then
local title = string.match(response.body, "<[Tt][Ii][Tt][Ll][Ee][^>]*>ArcticFission ([^<]*)</[Tt][Ii][Tt][Ll][Ee]>")

if ( title == "1.0" ) then
return "Vulnerable"
else
return "Not Vulnerable"
end
end
end

And here is the final output:

# nmap --script http-vuln-check localhost -p 80,443

Starting Nmap 6.25 ( http://nmap.org ) at 2013-01-09 04:24 EST
Nmap scan report for localhost (127.0.0.1)
Host is up (0.000034s latency).
Other addresses for localhost (not scanned): 127.0.0.1
PORT STATE SERVICE
80/tcp open http
|_http-vuln-check: Vulnerable
443/tcp open https
|_http-vuln-check: Vulnerable

Nmap done: 1 IP address (1 host up) scanned in 8.08 seconds

Another approach to version detection (and a possible way to eliminate false positives) is to generate and compare page hash against a known value. For this version of the script I am going to use the NSE Library openssl:

local shortport = require "shortport"
local http = require "http"
local stdnse = require "stdnse"
local openssl = require "openssl"

-- The Rule Section --
portrule = shortport.http

-- The Action Section --
action = function(host, port)

local uri = "/arcticfission.html"
local response = http.get(host, port, uri)

if ( response.status == 200 ) then
local vulnsha1 = "984c6f159d5b5baba8fe23dfa5372d047ed1de2e"
local sha1 = string.lower(stdnse.tohex(openssl.sha1(response.body)))

if ( sha1 == vulnsha1 ) then
return "Vulnerable"
else
return "Not Vulnerable"
end
end
end

The above script will produce same output.

Adding a bit of stealth

# nmap --script http-vuln-check localhost -p 80,443 --script-trace
Starting Nmap 6.25 ( http://nmap.org ) at 2013-01-11 01:42 EST
NSOCK (0.0490s) nsi_new (IOD #1)
NSOCK (0.0790s) TCP connection requested to 127.0.0.1:80 (IOD #1) EID 8
NSOCK (0.0790s) nsi_new (IOD #2)
NSOCK (0.0790s) SSL connection requested to 127.0.0.1:443/tcp (IOD #2) EID 17
NSOCK (0.0800s) Callback: CONNECT SUCCESS for EID 8 [127.0.0.1:80]
NSE: TCP 127.0.0.1:56968 > 127.0.0.1:80 | CONNECT
NSOCK (0.0800s) Callback: SSL-CONNECT SUCCESS for EID 17 [127.0.0.1:443]
NSE: TCP 127.0.0.1:55825 > 127.0.0.1:443 | CONNECT
NSE: TCP 127.0.0.1:56968 > 127.0.0.1:80 | 00000000: 47 45 54 20 2f 61 72 63 74 69 63 66 69 73 73 69 GET /arcticfissi
00000010: 6f 6e 2e 68 74 6d 6c 20 48 54 54 50 2f 31 2e 31 on.html HTTP/1.1
00000020: 0d 0a 55 73 65 72 2d 41 67 65 6e 74 3a 20 4d 6f User-Agent: Mo
00000030: 7a 69 6c 6c 61 2f 35 2e 30 20 28 63 6f 6d 70 61 zilla/5.0 (compa
00000040: 74 69 62 6c 65 3b 20 4e 6d 61 70 20 53 63 72 69 tible; Nmap Scri
00000050: 70 74 69 6e 67 20 45 6e 67 69 6e 65 3b 20 68 74 pting Engine; ht
00000060: 74 70 3a 2f 2f 6e 6d 61 70 2e 6f 72 67 2f 62 6f tp://nmap.org/bo
00000070: 6f 6b 2f 6e 73 65 2e 68 74 6d 6c 29 0d 0a 43 6f ok/nse.html) Co
00000080: 6e 6e 65 63 74 69 6f 6e 3a 20 63 6c 6f 73 65 0d nnection: close
00000090: 0a 48 6f 73 74 3a 20 6c 6f 63 61 6c 68 6f 73 74 Host: localhost
000000a0: 0d 0a 0d 0a
...

In the trace above, the NSE Library ‘http’ is using a default User-Agent string “Mozilla/5.0 (compatible; Nmap Scripting Engine; http://nmap.org/book/nse.html)". You may want to change this and possibly other connection parameters for performance or security reasons. There are two options to change the user-agent string. First (easiest) is to simply include an extra command-line script argument to override default user agent:

# nmap --script http-vuln-check localhost -p 80,443 --script-args="http.useragent='Mozilla/5.0 (compatible; ArcticFission)'"

Alternatively, it is possible to override the User-Agent header parameter (or other request parameters) in the script itself as follows:

local shortport = require "shortport"
local http = require "http"
local stdnse = require "stdnse"
local string = require "string"

-- The Rule Section --
portrule = shortport.http

-- The Action Section --
action = function(host, port)

local uri = "/arcticfission.html"

local options = {header={}}
options['header']['User-Agent'] = "Mozilla/5.0 (compatible; ArcticFission)"

local response = http.get(host, port, uri, options)

if ( response.status == 200 ) then
local title = string.match(response.body, "<[Tt][Ii][Tt][Ll][Ee][^>]*>ArcticFission ([^<]*)</[Tt][Ii][Tt][Ll][Ee]>")

if ( title == "1.0" ) then
return "Vulnerable"
else
return "Not Vulnerable"
end
end
end

Packaging the Script

-- The Head Section --
description = [[Sample script to detect a fictional vulnerability
in a fictional ArcticFission 1.0 web server]]
author = "iphelix"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"default", "safe"}

local shortport = require "shortport"
local http = require "http"
local stdnse = require "stdnse"
local string = require "string"

-- The Rule Section --
portrule = shortport.http

-- The Action Section --
action = function(host, port)

local uri = "/arcticfission.html"

local options = {header={}}
options['header']['User-Agent'] = "Mozilla/5.0 (compatible; ArcticFission)"

local response = http.get(host, port, uri, options)

if ( response.status == 200 ) then
local title = string.match(response.body, "<[Tt][Ii][Tt][Ll][Ee][^>]*>ArcticFission ([^<]*)</[Tt][Ii][Tt][Ll][Ee]>")

if ( title == "1.0" ) then
return "Vulnerable"
else
return "Not Vulnerable"
end
end
end

At last, you may want to include some documentation in the NSEDoc format. Script documentation may include special tags which will be processed by the documentation system (e.g @output for script output, @args for script arguments, @usage for sample command line parameters, etc.). Here is the final script example:

-- The Head Section --
description = [[Sample script to detect a fictional vulnerability
in a fictional ArcticFission 1.0 web server]]
author = "iphelix"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"default", "safe"}

local shortport = require "shortport"
local http = require "http"
local stdnse = require "stdnse"
local string = require "string"

-- The Rule Section --
portrule = shortport.http

-- The Action Section --
action = function(host, port)

local uri = "/arcticfission.html"

local options = {header={}}
options['header']['User-Agent'] = "Mozilla/5.0 (compatible; ArcticFission)"

local response = http.get(host, port, uri, options)

if ( response.status == 200 ) then
local title = string.match(response.body, "<[Tt][Ii][Tt][Ll][Ee][^>]*>ArcticFission ([^<]*)</[Tt][Ii][Tt][Ll][Ee]>")

if ( title == "1.0" ) then
return "Vulnerable"
else
return "Not Vulnerable"
end
end
end

Parsing the output

#!/usr/bin/env python
# nmap-xml-parse by iphelix
import sys
from xml.dom.minidom import parse

if len(sys.argv) != 2:
print "Usage: %s nmap_output.xml"
sys.exit(1)

nmap = parse(sys.argv[1])

for host in nmap.getElementsByTagName("host"):
addresses = [addr.getAttribute("addr") for addr in host.getElementsByTagName("address")]

for port in host.getElementsByTagName("port"):
portid = port.getAttribute("portid")

for script in port.getElementsByTagName("script"):
if script.getAttribute("id") == "http-vuln-check":
output = script.getAttribute("output")

for address in addresses:
print "%s,%s,%s" % (address, portid, output)

The above Python script can be used as follows:

# nmap --script http-vuln-check localhost -p 80,443 -oA http-vuln

Starting Nmap 6.25 ( http://nmap.org ) at 2013-01-11 02:47 EST
Nmap scan report for localhost (127.0.0.1)
Host is up (0.000066s latency).
Other addresses for localhost (not scanned): 127.0.0.1
PORT STATE SERVICE
80/tcp open http
|_http-vuln-check: Vulnerable
443/tcp open https
|_http-vuln-check: Vulnerable

Nmap done: 1 IP address (1 host up) scanned in 8.08 seconds
# ./nmap-xml-parse.py http-vuln.xml
127.0.0.1,80,Vulnerable
127.0.0.1,443,Vulnerable

The output can be easily parsed as a CSV file. Feel free to adopt the script to your specific needs.

Vulnerability Management

NSE Vulnerability Library

-- The Head Section --
description = [[Sample script to detect a fictional vulnerability
in a fictional ArcticFission 1.0 web server]]

---
-- @usage
-- nmap --script http-vuln-check <target>
-- @output
-- PORT STATE SERVICE
-- 80/tcp open http
-- | http-vuln-check:
-- | VULNERABLE:
-- | ArcticFission 1.0 Vulnerability
-- | State: VULNERABLE
-- | IDs: CVE:CVE-XXXX-XX
-- | References:
-- |_ http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-XXXX-XX


author = "iphelix"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"default", "safe"}

local shortport = require "shortport"
local http = require "http"
local stdnse = require "stdnse"
local string = require "string"
local vulns = require "vulns"

-- The Rule Section --
portrule = shortport.http

-- The Action Section --
action = function(host, port)

-- The Vuln Definition Section --
local vuln = {
title = "ArcticFission 1.0 Vulnerability",
state = vulns.STATE.NOT_VULN, --default
IDS = { CVE = 'CVE-XXXX-XX' }
}
local report = vulns.Report:new(SCRIPT_NAME, host, port)

local uri = "/arcticfission.html"

local options = {header={}}
options['header']['User-Agent'] = "Mozilla/5.0 (compatible; ArcticFission)"

local response = http.get(host, port, uri, options)

if ( response.status == 200 ) then
local title = string.match(response.body, "<[Tt][Ii][Tt][Ll][Ee][^>]*>ArcticFission ([^<]*)</[Tt][Ii][Tt][Ll][Ee]>")

if ( title == "1.0" ) then
vuln.state = vulns.STATE.VULN
else
vuln.state = vulns.STATE.NOT_VULN
end
end

return report:make_output(vuln)
end

There are several changes made to the original script. First, notice the vulnerability definition table containing detailed information about the vulnerability:

-- The Vuln Definition Section --
local vuln = {
title = "ArcticFission 1.0 Vulnerability",
state = vulns.STATE.NOT_VULN, --default
IDS = { CVE = 'CVE-XXXX-XX' }
}

This section can actually be expanded with more standardized vulnerability descriptors such as disclosure date, CSV scores, risk factors, etc.

Next, notice that vulnerability state is recorded in the above structure using vulns.STATE.VULN and vulns.STATE.NOT_VULN variables with the latter being set as default:

if ( title == "1.0" ) then
vuln.state = vulns.STATE.VULN
else
vuln.state = vulns.STATE.NOT_VULN
end

At last (and the most powerful part) we have added an automatic report generator using the ‘make_output’ function.

Here is an updated nmap script output:

# nmap --script http-vuln-check localhost -p 80,443

Starting Nmap 6.25 ( http://nmap.org ) at 2013-01-11 02:52 EST
Nmap scan report for localhost (127.0.0.1)
Host is up (0.000038s latency).
Other addresses for localhost (not scanned): 127.0.0.1
PORT STATE SERVICE
80/tcp open http
| http-vuln-check:
| VULNERABLE:
| ArcticFission 1.0 Vulnerability
| State: VULNERABLE
| IDs: CVE:CVE-XXXX-XX
| References:
|_ http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-XXXX-XX
443/tcp open https
| http-vuln-check:
| VULNERABLE:
| ArcticFission 1.0 Vulnerability
| State: VULNERABLE
| IDs: CVE:CVE-XXXX-XX
| References:
|_ http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-XXXX-XX

At the expense of a few additional lines of code, you gain automatically generated vulnerability report standardized across a growing number of nmap scripts.

Aggregating output

local stdnse = require "stdnse"
local vulns = require "vulns"

local FID -- my script FILTER ID

prerule = function()
FID = vulns.save_reports()
if FID then
return true
end
return false
end

postrule = function()
if nmap.registry[SCRIPT_NAME] then
FID = nmap.registry[SCRIPT_NAME].FID
if vulns.get_ids(FID) then
return true
end
end
return false
end

prerule_action = function()
nmap.registry[SCRIPT_NAME] = nmap.registry[SCRIPT_NAME] or {}
nmap.registry[SCRIPT_NAME].FID = FID
return nil
end

postrule_action = function()
return vulns.make_output(FID) -- show all the vulnerabilities
end

local tactions = {
prerule = prerule_action,
postrule = postrule_action,
}

action = function(...) return tactions[SCRIPT_TYPE](...) end

Now execute nmap with both scripts running at the same time:

# nmap --script http-vuln-check,vulns-post-process localhost thesprawl.org -p 80,443

Starting Nmap 6.25 ( http://nmap.org ) at 2013-01-11 03:12 EST
Nmap scan report for localhost (127.0.0.1)
Host is up (0.000051s latency).
Other addresses for localhost (not scanned): 127.0.0.1
PORT STATE SERVICE
80/tcp open http
| http-vuln-check:
| VULNERABLE:
| ArcticFission 1.0 Vulnerability
| State: VULNERABLE
| IDs: CVE:CVE-XXXX-XX
| References:
|_ http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-XXXX-XX
443/tcp open https
| http-vuln-check:
| VULNERABLE:
| ArcticFission 1.0 Vulnerability
| State: VULNERABLE
| IDs: CVE:CVE-XXXX-XX
| References:
|_ http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-XXXX-XX

Nmap scan report for thesprawl.org (108.59.3.64)
Host is up (0.012s latency).
rDNS record for 108.59.3.64: web219.webfaction.com
PORT STATE SERVICE
80/tcp open http
443/tcp open https

Post-scan script results:
| vulns-post-process:
| Vulnerability report for 108.59.3.64: NOT VULNERABLE
|
| Vulnerability report for 127.0.0.1: VULNERABLE
| Target: localhost (127.0.0.1) Port: 80/http
| ArcticFission 1.0 Vulnerability
| State: VULNERABLE
| IDs: CVE:CVE-XXXX-XX
| References:
| http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-XXXX-XX
| Reported by scripts: http-vuln-check
|
| Target: localhost (127.0.0.1) Port: 443/https
| ArcticFission 1.0 Vulnerability
| State: VULNERABLE
| IDs: CVE:CVE-XXXX-XX
| References:
| http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-XXXX-XX
|_ Reported by scripts: http-vuln-check
Nmap done: 2 IP addresses (2 hosts up) scanned in 0.42 seconds

As you can see, it is now possible to aggregate and report on results across multiple hosts. The script operates by saving vulnerability in nmap’s registry which is persistent across multiple host scans and later formats combined output as a postrule script.

In fact, it is possible to further format the post-scan output. Here is an updated ‘vulns-post-process’ script which will output a list of vulnerable hosts:

local stdnse = require "stdnse"
local vulns = require "vulns"

local FID -- my script FILTER ID

prerule = function()
FID = vulns.save_reports()
if FID then
return true
end
return false
end

postrule = function()
if nmap.registry[SCRIPT_NAME] then
FID = nmap.registry[SCRIPT_NAME].FID
if vulns.get_ids(FID) then
return true
end
end
return false
end

prerule_action = function()
nmap.registry[SCRIPT_NAME] = nmap.registry[SCRIPT_NAME] or {}
nmap.registry[SCRIPT_NAME].FID = FID
return nil
end

postrule_action = function()
local filter = {state = vulns.STATE.VULN}
local list = vulns.find(FID, filter)
if list then
local out = {}
for _, vuln_table in ipairs(list) do
local ip = vuln_table.host.ip
local port = vuln_table.port.number
local state = vulns.STATE_MSG[vuln_table.state]
local title = vuln_table.title
table.insert(out, string.format("%s:%d - %s - %s", ip, port, title, state))

end
return stdnse.format_output(true, out)
end
end

local tactions = {
prerule = prerule_action,
postrule = postrule_action,
}

action = function(...) return tactions[SCRIPT_TYPE](...) end

Here is an updated nmap output:

# nmap --script http-vuln-check,vulns-post-process localhost thesprawl.org -p 80,443

Starting Nmap 6.25 ( http://nmap.org ) at 2013-01-11 04:15 EST
Nmap scan report for localhost (127.0.0.1)
Host is up (0.000067s latency).
Other addresses for localhost (not scanned): 127.0.0.1
PORT STATE SERVICE
80/tcp open http
| http-vuln-check:
| VULNERABLE:
| ArcticFission 1.0 Vulnerability
| State: VULNERABLE
| IDs: CVE:CVE-XXXX-XX
| References:
|_ http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-XXXX-XX
443/tcp open https
| http-vuln-check:
| VULNERABLE:
| ArcticFission 1.0 Vulnerability
| State: VULNERABLE
| IDs: CVE:CVE-XXXX-XX
| References:
|_ http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-XXXX-XX

Nmap scan report for thesprawl.org (108.59.3.64)
Host is up (0.012s latency).
rDNS record for 108.59.3.64: web219.webfaction.com
PORT STATE SERVICE
80/tcp open http
443/tcp open https

Post-scan script results:
| vulns-post-process:
| 127.0.0.1:80 - ArcticFission 1.0 Vulnerability - VULNERABLE
|_ 127.0.0.1:443 - ArcticFission 1.0 Vulnerability - VULNERABLE
Nmap done: 2 IP addresses (2 hosts up) scanned in 13.44 seconds

The final output was generated using the postrule_action function by enumerating a list of vulnerabilities and extracting only relevant information:

postrule_action = function()
local filter = {state = vulns.STATE.VULN}
local list = vulns.find(FID, filter)
if list then
local out = {}
for _, vuln_table in ipairs(list) do
local ip = vuln_table.host.ip
local port = vuln_table.port.number
local state = vulns.STATE_MSG[vuln_table.state]
local title = vuln_table.title
table.insert(out, string.format("%s:%d - %s - %s", ip, port, title, state))

end
return stdnse.format_output(true, out)
end
end

Notice that the final list only includes hosts identified as vulnerable. This is due to an additional filter state = vulns.STATE.VULN which ignores not vulnerable hosts when executing the vulns.find().

Where to go from here

External Links and References

--

--

Blockchain Security, Malware Analysis, Incident Response, Pentesting, BlockThreat.net

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store