In my last blog post, I gave a high-level introduction to the Nmap Scripting Engine (NSE). In this blog post, I’d like to cover an example of writing a simple one with focus on the process around creating one. For this example, we are going to create a simple Nmap script that will perform a simple check to attempt to obtain the version of WordPress a website is running. This is a simple script to write and allows us to focus not only on writing it, but the entire process around it, including the thought process leading to its creation and testing it.
Why Would We Want to Create this Script?
Nmap overall is good at scanning ports and detecting services. With a little extra logic applied through a NSE script, we can have Nmap process a large list of hosts at one time and return the versions of WordPress it was able to detect. Furthermore, when coupled with the XML output format, this information can be extracted with a simple Python script. This speeds up and automates what would otherwise be a large time sink without the risk of human error.
The first thing that needs to occur when writing a version detection script is finding a reliable way to detect the version by hand first. WordPress has been around for a while and there are already some known methods. One of the most common is the generator meta tag. It would be found in the head of the page and look like one of the following:
Versions can be in a 2 dot version or 3 dot version in my experience, so we might want to attempt to capture both. This solution isn’t perfect. There are techniques to strip this meta tag out of the page, or change it like the main WordPress website does. Below is a screenshot of the generator meta tag from https://www.wordpress.com/, which intentionally omits the version number.
So this method isn’t perfect, but it is good enough for the default setup of a WordPress site and we won’t let perfect be the enemy of good enough with the initial script.
Creating a Game Plan for How the Script Works
So before we start writing the script, we will want to make a game plan first on what it needs to do. In this example it’s pretty straightforward:
- Configure the scripts portrule to trigger on all http services
- Make a get request for the the webroot (“/”)
- Examine the HTML response and search for the generator meta tag
- If found, attempt to extract a 3 dot version number
- If that fails, attempt to extract a 2 dot version number
- If either version format was found, have the script return it.
Creating the Directory Structure
For testing purposes, I will use a Kali VM and create a folder called nmap_nse_testing and nmap_nse_testing/serve under the home directory using
mkdir -p ~/nmap_nse_testing/serve. The nmap_nse_testing script is where we will put our script for testing and the nmap_nse_testing/serve will be where we will create an index.html file to fake a WordPress page with a generator meta tag.
Creating a Mock-up Test WordPress Page
For the initial testing we can use the following HTML inside of ~/nmap_nse_testing/serve/index.html.
This can be served via Python’s built in http.server as shown in the screenshot below.
Which should be accessible via a web browser when you access http://127.0.0.1:8000/. The screenshot below shows this in the browser.
Now that we have a safe target to experiment with, let’s get to work on our script!
Creating the NSE Script
For the development of the NSE script, we will use the ~/nmap_nse_testing directory we created earlier. In this directory, we will create a script called http-wordpress-version.nse. You can use Nano, VSCode, Geany, or any other text/code editor for this. With that in mind, most do not recognize the extension. NSE scripts are built in LUA. If you use Geany, you can have it treat it as a LUA file by going to Document -> Set Filetype -> Scripting Languages -> Lua Source File.
Filling in the Head and Including the Needed Libraries
NSE scripts generally need some basic metadata to describe the script. For this part we will populate the description, author, license, and categories values. The table below shows how this would look in our NSE script.
For this script there are three libraries we will want to make use of: http, stdnse, and shortport. The shortport provides us with the shortport.http portrule to allow us to have the script trigger on web services. The stdnse library we want to include for stdnse.debug() so we can have debug output in case it’s needed. Finally, the http library provides us with the http.get() and http.response_contains() method we will use to send the HTTP GET request with.
We will use need to include each of the libraries using the following code in our script:
Creating our Portrule
Since we included the shortport library, the portrule will be easy to define. Under the library includes we can add the following line so that our script triggers on HTTP services:
Now we can create the action function, which will be the main code, but first we will create a support function called
get_wordpress_version_from_meta_tag that will do most of the heavy lifting for us.
Creating our Supporting Function
To make the main
action function (the entry point of the script's execution), we will create a support function to send the HTTP GET request and parse the HTML from the response to find the version number using regular expression capture groups. This function will return two values:
found is false, then this function failed to extract the WordPress version. However, if it is true, then the WordPress version was extracted.
The code for this function can be found below:
Creating our Action Function
The meat of the NSE script is the
action function. This function will call the support function we just created and if a WordPress version is found, we will report it by returning it as a string. If nothing was found, then we will return nothing. In the event that nothing was returned nmap will just suppress the script output entirely for that service, making tidy output.
Re-cap of our Script So Far
Since I covered this in sections, I want to show the completed script so far. Note that we are still missing the NSEDoc comment, but we need to run it to get a sample of what the output looks like before we write that part.
Test Run of the Script
Now that we’ve got a script written, it’s time to take it for a test drive against our sample index.html page we are serving via Python’s http.server module. For this we will scan just the port TCP 8000. I’ve also decided to have it write the XML output file so we can examine what that looks like as well. The output for the command
nmap -sT -p 8000 --script=./http-wordpress-version.nse -oX nmap_test.xml 127.0.0.1 is shown in the screenshot below.
Note how under the port 8000 we see the output of our script stating it found WordPress 4.7.
Why The XML Output File?
We can also find that information now in our XML file! This is awesome as you can use a large target list, have it dump it all to a XML file and search each host and port for the script node with the id of our script and grab its output attribute. The screenshot below shows an example of the XML output from our script.
As stated earlier, you can parse this with a simple Python script to extract a list of hosts, ports, and detected WordPress versions. The Python script below is an example of how to parse an XML file for our script output.
If we run that Python script against our Nmap XML file, we can see it works as intended, as shown in the screenshot below.
This seems boring since there was only one host in our example, but it is very nice when you need to scan thousands of hosts and generate a report off of that.
Adding the Script Documentation
Now that we had a successful execution, we can add the script documentation comments. Usually at minimum, I like to include the @usage and @output comments. The code for our script’s NSEDoc comments is shown below, which will add between the description and author values.
The Completed Script
The final script is as follows:
You can continue to use this script from the current directory or add it to the /usr/share/nmap/scripts/ directory and use it like any other nmap NSE script. If you add it to the /usr/share/nmap/scripts/ directory, you should also run
nmap --script-updatedb as root to update Nmap’s script database so it’s aware of it and its categories. Speaking of categories, you could also add the default category if you wanted to always run with the
This blog covered a basic example of how to create a simple WordPress version detection script. Hopefully, this information was useful and can help to understand the layout of NSE scripts and get started with building your own! If you’re interested in network security fundamentals, we have a Network Security channel that covers a variety of network topics. We also offer training via our Professionally Evil Network Testing (PENT) course and answer general basic questions in our Knowledge Center. Finally, if you’re looking for a penetration test, professional training for your organization, or just have general security questions, please Contact Us.