Attack from Nmap – The Nmap Scripting Engine

Scripting languages are great. There must be at least a few hundred or maybe even thousand of them. Most popular ones these days start with the letter P. Nmap supports scripts: The Nmap Scripting Engine. This has been around since version 4.21ALPHA1 [2006-12-10]. The Nmap Scripting Engine allows you to use scripts that will be executed when scanning for open ports on a network. Having Nmap executing scripts when scanning is awesome because it potentially allows you to save time NOT having to wait for the complete scan to finish to take further action.

Ofcourse you can first use Nmap to scan, wait for the results, and then continue using other tools. A lot of tools that make use of Nmap work like that and Nmap has some pretty neat features for that (-oA comes to mind). But wouldn’t it be nice to have them run automatically when Nmap is scanning? NSE sounds almost to perfect doesn’t it?

As it turns out, NSE scripts aren’t written in Perl, Python or Ruby, they are in LUA. From the Nmap man-page:

The Nmap Scripting Engine (NSE) is one of Nmap’s most powerful and flexible features. It allows users to write (and share) simple scripts (using the Lua programming language[11], ..

I have heard of LUA, but never bothered to actually use it. If I remember correctly, games like Quake used LUA. Well anyway, as with any programming language, it takes time and practise to become comfortable with it. I don’t know LUA and I actually have a life. I would really like to be able to use my own scripts right from Nmap without having to re-write scripts to LUA. I looked around for something that would allow me to do so, but didn’t find anything. But there sure is some interesting development on NSE scripts (here and here). So I took some time to figure out a way around it. This isn’t exactly rocket-science, but it might save you the better part of a day hacking LUA.

The way of getting around LUA was to write a small NSE script – yes, in LUA – that executes another script (program/command), whatever they’re written in and use them from Nmap. Then, LUA is no longer a requirement. For example: This would allow you to use Metasploit or Nikto from Nmap instead of the other way around.

Ofcourse this comes at some expense: Forking yet another interpreter or command from NSE is heavier on both memory and CPU on the system on which you are running Nmap. Thus not very efficient. In fact: It might turn out that a particular script would cause Nmap to become so slow, that it might be more time efficient to not use NSE, but to take the usual way to scan first and then parse the results of that to use in your script(s). On the other hand: the luxury of having scanning and scripts go hand in hand might be worth the wait (and stop abusing -T5 ;-)).

Oh, a little warning here: It may come as no surprise that my LUA skills are exceptionally poor, and the quality of the code is probably the same as my skills. To put it another way: THIS MAY TRASH YOUR MACHINE. USE AT OWN RISC. You are now warned. It is very likely that there are other and better ways of accomplishing the same. Feel free to improve it.

description  =  [[
Wrapper for other scripts than LUA
]]

author  =  "Jacco van Buuren"
license  =  "3 clause BSD"
categories  =  {"default", "safe"}

---
-- @args
-- Script to execute
--
-- @usage
-- nmap --script=this_script.nse --scripts-args=your_script
--
-- @output
-- Output depends on your script.
---

local stdnse = require "stdnse"
stdnse.print_debug(1, "*** Exec starting...\n")

portrule  =  function(host,port)
	stdnse.print_debug(1,"*** Portrule hit...")
	local script, str, t, key, val
	str = ""
	key = ""
	val = ""
	t  =  {
		host_ip = host.ip,
		host_os = host.os,
		host_name = host.name,
		host_targetname = host.targetname,
		host_directly_connected = host.directly_connected,
		host_interface = host.interface,
		host_interface_mtu = host.interface_mtu,
		host_times = host.times,
		host_traceroute = host.traceroute,
		port_number = port.number,
		port_protocol = port.protocol,
		port_service = port.service,
		port_version_name = port.version.name,
		port_version_name_confidence = port.version.name_confidence,
		port_version_product = port.version.product,
		port_version_version = port.version.version,
		port_version_extrainfo = port.version.extrainfo,
		port_version_hostname = port.version.hostname,
		port_version_ostype = port.version.ostype,
		port_version_devicetype = port.version.devicetype,
		port_version_service_tunnel = port.version.service_tunnel,
		port_version_rpc_status = port.version.rpc_status,
		port_version_rpc_program = port.version.rpc_program,
		port_version_rpc_lowver = port.version.rpc_lowver,
		port_version_rpc_highver = port.version.rpc_highver,
		port_state = port.state
	}
	if not nmap.registry.args then
		stdnse.print_debug(1,"*** No script to execute! Aborted\n")
		return
	end
	script  =  nmap.registry.args[1]
	if ( type(script) ~= "string" ) or ( script == "" ) then
		stdnse.print_debug(1,"*** No script to execute! Aborted")
		return
	end
	stdnse.print_debug(1,"*** Script to execute is: %s", script)
	for key,val in pairs(t) do
		if ( val ~= nil ) then
			val = tostring(val)
			stdnse.print_debug(1,"*** %s = '%s'", key, val)
			str = str .. " " .. key .. "='" .. val .. "'"
		else
			stdnse.print_debug(1,"*** %s holds no value", key)
		end
	end
	stdnse.print_debug(1,"*** Parameters are: %s", str)
	stdnse.print_debug(1,"*** Now executing:\n\t%s", script ..  str)
	os.execute(script .. str)
	return
end

action  =  function()
	stdnse.print_debug(1,"*** Action hit")
end

How to use it? First of all: Name this script exec.nse. It takes the path/filename of your script as a parameter. Like this:

nmap --script=./exec.nse --script-args=./hello.sh -sT -p 80 127.0.0.1 2>&1 | tee output.log

In this example, hello.sh would be your script, which is launched from exec.nse. Hello.sh is included below. Exec.nse passes Nmaps’ results as parameters as name-value pairs on the commandline, only if they have a value. This also introduces a security flaw: Exec.nse does not check for valid values before executing the script (hello.sh). More about that later.

#!/bin/sh

for j in $@
do
	if [ "X`echo $j | grep =`X" = "XX"  ]; then
		continue
	fi
	a="`echo $j | awk -F= '{print $1}'`"
	b="`echo $j | awk -F= '{print $2}'`"
	eval $a="$b"
	#echo "*** >>> $a = $b"
done

if [ "$port_state" != "open" ]; then
	echo "*** >>> Port $port_number to $host_ip is not open. Exiting..."
	exit 0
fi

echo "*** >>> --- Attemting $port_service attack on target $host_ip on $port_protocol port $port_number"

case "$port_service" in
http)
	printf "HEAD / HTTP/1.0\n\n\n" | nc $host_ip $port_number 2>&1 | sed 's/^/*** >>> /'
	;;
"")
	echo "*** >>> Unknown service. Sorry" >&2
	exit 1
	;;
*)
	echo "*** >>> $port_service is not implemented. Sorry" >&2
	exit 1
	;;
esac

echo "*** >>> --- End"

Output looks like this:

Starting Nmap 5.00 ( http://nmap.org ) at 2011-06-23 21:15 CEST
*** >>> --- Attemting http attack on target 127.0.0.1 on tcp port 80
*** >>> HTTP/1.1 200 OK
*** >>> Date: Thu, 23 Jun 2011 19:15:18 GMT
*** >>> Server: Apache/2.2.14 (Ubuntu)
*** >>> Last-Modified: Sat, 05 Jun 2010 16:01:44 GMT
*** >>> ETag: "82109-b1-4884a8e5b044f"
*** >>> Accept-Ranges: bytes
*** >>> Content-Length: 177
*** >>> Vary: Accept-Encoding
*** >>> Connection: close
*** >>> Content-Type: text/html
*** >>>
*** >>> --- End
Interesting ports on localhost (127.0.0.1):
PORT   STATE SERVICE
80/tcp open  http

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

Hello.sh is a bit silly, as Nmap has built-in version detection. But you can see that it is a Unix shell script that is being executed as Nmap is running, which was my goal in the first place. As said, Exec.nse could be considered insecure because it does not check values of variables that are passed onto the commandline. In theory the following COULD be executed:

./your_script.sh host_ip=''; rm -rf / # Ignore all other stuff ...

If such a scenario would occur, Nmap would have to set host_ip to “‘; rm -rf / # Ignore all other stuff…”. Host_ip may be a wrong example because that isn’t very likely to happen, but other information from Nmap (such as OS fingerprints or anything that was scanned from a target) just might. These are therefor not included / passed on as name-value pair. The following variables I considered “safe enough”, but the better option would ofcourse be to check and re-write variables.

  • host.ip
  • host.os
  • host.name
  • host.targetname
  • host.directly_connected
  • host.interface
  • host.interface_mtu
  • host.times
  • host.traceroute
  • port.number
  • port.protocol
  • port.service
  • port.version.name
  • port.version.name_confidence
  • port.version.product
  • port.version.version
  • port.version.extrainfo
  • port.version.hostname
  • port.version.ostype
  • port.version.devicetype
  • port.version.service_tunnel
  • port.version.rpc_status
  • port.version.rpc_program
  • port.version.rpc_lowver
  • port.version.rpc_highver
  • port.state

If you look closely at hello.sh, you’ll notice the absence of value checking and an “eval” statement to assign variables, which is also very insecure. I do not recommend that you use hello.sh in this form, it’s here only as an example.

Exec.nse is not limited to Unix shell, below is a (useless) example of how you could use perl instead of hello.sh:

#!/usr/bin/env perl
use warnings;
use strict;
use vars qw(@ARGV);

my(%t,@v);

foreach(@ARGV) {
        @v = split(/=/);
        next if ($#v != 1 );
        $t{$v[0]}=$v[1];
}

foreach(keys %t) {
        print "*** >>> " . $_ . " = " . $t{$_} . "\n";
}

Epilogue

Known issues: Not everything from NSE is passed on to scripts. These are mostly binary values, which are difficult to use from the commandline.

Known bugs: I tested exec.nse (and hello.sh) on several machines, but not very extensively. You may come across odd values that screw things up (somewhere along line 72). Nmap variables are not checked for valid or usable values before passing them on the commandline. This may be considered a security flaw, especially when running Nmap as a priviledged user (root).

A final note: If you’re not sure if exec.nse is working properly, add -d1 to the Nmap command you’re using. This will show debugging output from Nmap and exec.nse.

This entry was posted in IT Security and tagged . Bookmark the permalink.