Nmap Development mailing list archives

Re: [NSE] Local IP geolocation script


From: jah <jah () zadkiel plus com>
Date: Mon, 17 Nov 2008 21:48:05 +0000

On 16/11/2008 19:10, Philipp Emanuel Weidmann wrote:
Hi everyone,

It's been quite a while since I worked on Nmap (last time I looked
into the code NSE was just a pipe dream), but recently I found time
again and whipped up this little script which I find very useful.

It provides IP geolocation (country lookup) for each scanned host
WITHOUT making any outbound connections and therefore without sending
out any data to any registries about your scanning activities. It is
in fact a 100% locally acting script. Its lookups are extremely fast
(usually less than 0.5 seconds) and since it is absolutely
nonintrusive and provides valuable information I have placed it in the
"default" category.

For its lookups it uses the MaxMind GeoLite Country database from
http://www.maxmind.com (they are claiming 99.5% accuracy; I personally
have not found one IP it is incorrect about. It is also updated
regularly). To make this script work, install it as usual in your
/scripts directory, then download the GeoLite database from
http://www.maxmind.com/download/geoip/database/GeoIPCountryCSV.zip and
unpack it into your main Nmap directory (where the other data files
are so it can be found by nmap.fetchfile).

I hacked this together without knowing any LUA, so if you can improve
it in any way, please do so.


Sample output:

Interesting ports on fx-in-f99.google.com (74.125.39.99):
PORT  STATE    SERVICE
5/tcp filtered unknown

Host script results:
|_ IP geolocation: United States (US)


Hope you find it as useful as I do. Have a great day.

Philipp Emanuel Weidmann 
Hi Philipp

This is a nice script and probably caters well for the seriously
paranoid!  I think it should also be in the "safe" category, but not so
sure about being in the default one given that it requires a file which
is unlikely (?) to be shipped with Nmap and thus not present in many
installations.
Having said that, it occurred to me that nmap.fetchfile() could be
called from the scripts hostrule to check whether the file is present
and to store a value in the registry so that this check would not need
to be repeated for any further targets in that hostgroup.  This would
have the effect of reducing some of the time wasted on calling the
script if the required file is not present.

The speed of lookup is OK for a single target, but for a large number of
targets these small timescales would add-up to be quite lengthy.  This
is because the geo file would be read for each target consecutively as,
with no network activity, each instance of the script would complete
before the next one begins.  Parsing the geo file once and storing the
data for access by other instances of the script would slow down the
execution for a single target by a small amount, but subsequent
instances of the script would finish much more quickly and this would
have a big impact when scanning lots of targets.

Some other little points: ipOps.todword() could be used to convert
host.ip into a number.  The script should be prevented from running
against IPv6 targets where it would fail.  The script "id" field is
being deprecated and the script filename is instead printed in the
script results - so a more descriptive filename such as
ip-geolocation.nse might be better.  You might also like to have a look
at http://nmap.org/book/nsedoc.html for guidance on documenting the script.

I've attached ip-geolocation.nse which calls datafiles.parse_file()
inside the hostrule - combining the check for existence of the file and
the one-time parsing and storage in the registry.  The hostrule also
checks for IPv6 addresses (host.ip contains a colon).

Regards,

jah


-- NOTE: In order for this script to work, you must download the GeoLite Country database from
-- http://www.maxmind.com/download/geoip/database/GeoIPCountryCSV.zip,
-- unpack it, and place it in the Nmap main directory (where the other Nmap data files are, too)

description = "Displays the location of the scanned host (country only), \
taken from the GeoLite Country database.\n\
NO information WHATSOEVER is sent from the scanning machine."

author      = "Philipp E. Weidmann <philipp.weidmann () gmx de>"

license     = "Script: Same as Nmap, see http://nmap.org/book/man-legal.html; \
GeoLite Country database: See http://www.maxmind.com/download/geoip/database/LICENSE.txt";

categories  = {"default", "discovery", "safe"}


local ipOps     = require "ipOps"
local stdnse    = require "stdnse"
local datafiles = require "datafiles"

local geofile   = "GeoIPCountryWhois.csv"

if not nmap.registry.locateip then nmap.registry.locateip = {} end


hostrule = function(host)

  if host.ip:match(":") or ipOps.isPrivate(host.ip) then return false end

  local status, lookup

  if nmap.registry.locateip.lookup == nil then

    local p = { -- table passed to parse_file
      range = {function(l) a,b = l:match('"(%d+)","(%d+)"') return {lo=tonumber(a),hi=tonumber(b)} end},
         co = {function(l) a,b = l:match('"(%u%u)","(.+)"') return ("%s (%s)"):format(b or "",a or "") end}
    }
    status, lookup = datafiles.parse_file(geofile,p)

    if not status then
      stdnse.print_debug(1, "Error finding or trying to parse %s", geofile)
      nmap.registry.locateip.lookup = false -- prevent repetition of parse_file
      return false
    end

    nmap.registry.locateip.lookup = lookup

  elseif nmap.registry.locateip.lookup == false then
    return false
  end

  return true -- host.ip is ok and data is in the registry

end


action = function(host)

  local lookup = nmap.registry.locateip.lookup

  local ipnumber = ipOps.todword(host.ip)

  for i=1,#lookup.co,1 do
    if ipnumber >= lookup.range[i].lo and ipnumber <= lookup.range[i].hi then
      return lookup.co[i]
    end
  end

end

_______________________________________________
Sent through the nmap-dev mailing list
http://cgi.insecure.org/mailman/listinfo/nmap-dev
Archived at http://SecLists.Org

Current thread: