1
0
mirror of https://github.com/nmap/nmap.git synced 2025-12-07 13:11:28 +00:00

New scripts for geo mapping. Closes #606

This commit is contained in:
dmiller
2016-12-17 14:37:35 +00:00
parent 916ce30d0f
commit c12c2eb1c9
9 changed files with 566 additions and 7 deletions

View File

@@ -1,5 +1,13 @@
# Nmap Changelog ($Id$); -*-text-*- # Nmap Changelog ($Id$); -*-text-*-
o [NSE][GH#606] Three new scripts render IP geolocation data as maps.
ip-geolocation-map-bing uses Bing Maps, ip-geolocation-map-google uses Google
Maps, and ip-geolocation-map-kml outputs KML map data for import into other
mapping software. [Mak Kolybabi]
o [NSE][GH#606] New NSE library, geoip.lua, provides a common framework for
storing and retrieving IP geolocation results. [Mak Kolybabi]
o [NSE][GH#518] Brute scripts are faster and more accurate. New feedback and o [NSE][GH#518] Brute scripts are faster and more accurate. New feedback and
adaptivity mechanisms in brute.lua help brute scripts use resources more adaptivity mechanisms in brute.lua help brute scripts use resources more
efficiently, dynamically changing number of threads based on protocol efficiently, dynamically changing number of threads based on protocol

81
nselib/geoip.lua Normal file
View File

@@ -0,0 +1,81 @@
local nmap = require "nmap"
local stdnse = require "stdnse"
local table = require "table"
_ENV = stdnse.module("geoip", stdnse.seeall)
---
-- Consolidation of GeoIP functions.
--
-- @author "Mak Kolybabi <mak@kolybabi.com>"
-- @copyright Same as Nmap--See https://nmap.org/book/man-legal.html
--- Add a geolocation to the registry
-- @param ip The IP that was geolocated
-- @param lat The latitude in degrees
-- @param lon The longitude in degrees
add = function(ip, lat, lon)
if not nmap.registry.geoip then
nmap.registry.geoip = {}
end
if not nmap.registry.geoip[ip] then
nmap.registry.geoip[ip] = {}
end
local lat_n = tonumber(lat)
if lat_n < -90 or lat_n > 90 then
stdnse.debug1("Invalid latitude for %s: %s.", ip, lat)
return
end
local lon_n = tonumber(lon)
if lon_n < -180 or lon_n > 180 then
stdnse.debug1("Invalid longitude for %s: %s.", ip, lon)
return
end
nmap.registry.geoip[ip]["latitude"] = lat
nmap.registry.geoip[ip]["longitude"] = lon
end
--- Check if any coordinates have been stored in the registry
--@return True if any coordinates have been returned, false otherwise
empty = function()
return not nmap.registry.geoip
end
--- Retrieve the table of coordinates by IP
--@return A table of coordinates keyed by IP.
get_all_by_ip = function()
if empty() then
return nil
end
return nmap.registry.geoip
end
--- Retrieve a table of IPs by coordinate
--@return A table of IPs keyed by coordinate in <code>lat,lon</code> format
get_all_by_gps = function(limit)
if empty() then
return nil
end
local t = {}
for ip, coords in pairs(get_all_by_ip()) do
if limit and limit < #t then
break
end
local key = coords["latitude"] .. "," .. coords["longitude"]
if not t[key] then
t[key] = {}
end
table.insert(t[key], ip)
end
return t
end
return _ENV;

View File

@@ -1,3 +1,4 @@
local geoip = require "geoip"
local http = require "http" local http = require "http"
local ipOps = require "ipOps" local ipOps = require "ipOps"
local json = require "json" local json = require "json"
@@ -39,24 +40,35 @@ end
local geoplugin = function(ip) local geoplugin = function(ip)
local response = http.get("www.geoplugin.net", 80, "/json.gp?ip="..ip, {any_af=true}) local response = http.get("www.geoplugin.net", 80, "/json.gp?ip="..ip, {any_af=true})
local stat, loc = json.parse(response.body) local stat, loc = json.parse(response.body)
if not stat then return nil end if not stat then
return false, loc
end
local output = {} local output = {}
table.insert(output, "coordinates (lat,lon): "..loc.geoplugin_latitude..","..loc.geoplugin_longitude) table.insert(output, "coordinates (lat,lon): "..loc.geoplugin_latitude..","..loc.geoplugin_longitude)
local regionName = (loc.geoplugin_regionName == json.NULL) and "Unknown" or loc.geoplugin_regionName local regionName = (loc.geoplugin_regionName == json.NULL) and "Unknown" or loc.geoplugin_regionName
table.insert(output,"state: ".. regionName ..", ".. loc.geoplugin_countryName) table.insert(output,"state: ".. regionName ..", ".. loc.geoplugin_countryName)
return output geoip.add(ip, loc.geoplugin_latitude, loc.geoplugin_longitude)
return true, output
end end
action = function(host,port) action = function(host,port)
local output = geoplugin(host.ip) local output = stdnse.output_table()
if(#output~=0) then local status, result = geoplugin(host.ip)
output.name = host.ip if not status then
if host.targetname then if result == "syntax error" then
output.name = output.name.." ("..host.targetname..")" result = "The geoPlugin service has likely blocked you due to excessive usage, but the response received was 'syntax error'."
end end
output.ERROR = result
return output, output.ERROR
end
output.name = host.ip
if host.targetname then
output.name = output.name.." ("..host.targetname..")"
end end
return stdnse.format_output(true,output) return stdnse.format_output(true,output)

View File

@@ -1,3 +1,4 @@
local geoip = require "geoip"
local http = require "http" local http = require "http"
local ipOps = require "ipOps" local ipOps = require "ipOps"
local json = require "json" local json = require "json"
@@ -70,6 +71,8 @@ local ipinfodb = function(ip)
table.insert(output, "coordinates (lat,lon): "..loc.latitude..","..loc.longitude) table.insert(output, "coordinates (lat,lon): "..loc.latitude..","..loc.longitude)
table.insert(output,"city: ".. loc.cityName..", ".. loc.regionName..", ".. loc.countryName) table.insert(output,"city: ".. loc.cityName..", ".. loc.regionName..", ".. loc.countryName)
geoip.add(ip, loc.latitude, loc.longitude)
return output return output
end end

View File

@@ -0,0 +1,177 @@
local http = require "http"
local geoip = require "geoip"
local io = require "io"
local stdnse = require "stdnse"
local string = require "string"
local table = require "table"
local url = require "url"
description = [[
This script queries the Nmap registry for the GPS coordinates of targets stored
by previous geolocation scripts and renders a Bing Map of markers representing
the targets.
Additional information for the Bing Maps REST Services API can be found at:
- https://msdn.microsoft.com/en-us/library/ff701724.aspx
]]
---
-- @usage
-- nmap -sn -Pn --script ip-geolocation-geoplugin,ip-geolocation-map-bing --script-args ip-geolocation-map-bing.api_key=[redacted],ip-geolocation-map-bing.map_path=map.png <target>
--
-- @output
-- | ip-geolocation-map-bing:
-- |_ The map has been saved at 'map.png'.
--
-- @args ip-geolocation-map-bing.api_key The required Bing Maps API key for your
-- account. An API key can be generated at https://www.bingmapsportal.com/
--
-- ip-geolocation-map-bing.center GPS coordinates defining the center of the
-- image. If omitted, Bing Maps will choose a center that shows all the
-- markers.
--
-- @args ip-geolocation-map-bing.format The default value is 'jpeg', 'png', and
-- 'gif' are also allowed.
--
-- @args ip-geolocation-map-bing.language The default value is 'en', but other
-- two-letter language codes are accepted.
--
-- @args ip-geolocation-map-bing.layer The default value is 'Road',
-- 'Aerial', and 'AerialWithLabels' are also allowed.
--
-- @args ip-geolocation-map-bing.map_path The path at which the rendered
-- Bing Map will be saved to the local filesystem.
--
-- @args ip-geolocation-map-bing.marker_style This argument can apply styling
-- to the markers.
-- https://msdn.microsoft.com/en-us/library/ff701719.aspx
--
-- @args ip-geolocation-map-bing.size The default value is '640x640' pixels, but
-- can be changed so long as the width is between 80 and 2000 pixels and the
-- height is between 80 and 1500 pixels.
author = "Mak Kolybabi <mak@kolybabi.com>"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"external", "safe"}
local render = function(params, options)
-- Format marker style for inclusion in parameters.
local style = ""
if options["marker_style"] then
style = ";" .. options["marker_style"]
end
-- Add in a marker for each host.
local markers = {}
for coords, ip in pairs(geoip.get_all_by_gps(100)) do
table.insert(markers, "pp=" .. coords .. style)
end
local body = table.concat(markers, "&")
-- Format the parameters into a properly encoded URL.
local query = "/REST/v1/Imagery/Map/" .. options["layer"] .. "?" .. url.build_query(params)
stdnse.debug1("The query URL is: %s", query)
stdnse.debug1("The query body is: %s", body)
local headers = {
["header"] = {
["Content-Type"] = "text/plain; charset=utf-8"
}
}
local res = http.post("dev.virtualearth.net", 80, query, headers, nil, body)
if not res or res.status ~= 200 then
stdnse.debug1("Error %d from API: %s", res.status, res.body)
return false, ("Failed to recieve map using query '%s'."):format(query)
end
local f = io.open(options["map_path"], "w")
if not f then
return false, ("Failed to open file '%s'."):format(options["map_path"])
end
if not f:write(res.body) then
return false, ("Failed to write file '%s'."):format(options["map_path"])
end
f:close()
local msg
return true, ("The map has been saved at '%s'."):format(options["map_path"])
end
local parse_args = function()
local options = {}
local params = {}
local api_key = stdnse.get_script_args(SCRIPT_NAME .. '.api_key')
if not api_key then
return false, "Need to specify an API key, get one at https://www.bingmapsportal.com/."
end
params["key"] = api_key
local center = stdnse.get_script_args(SCRIPT_NAME .. ".center")
if center then
params["centerPoint"] = center
end
local format = stdnse.get_script_args(SCRIPT_NAME .. ".format")
if format then
params["format"] = format
end
local language = stdnse.get_script_args(SCRIPT_NAME .. ".language")
if language then
params["language"] = language
end
local layer = stdnse.get_script_args(SCRIPT_NAME .. ".layer")
if not layer then
layer = "Road"
end
options["layer"] = layer
local map_path = stdnse.get_script_args(SCRIPT_NAME .. '.map_path')
if map_path then
options["map_path"] = map_path
else
return false, "Need to specify a path for the map."
end
local size = stdnse.get_script_args(SCRIPT_NAME .. ".size")
if not size then
-- This size is arbitrary, and is chosen to match the default that Google
-- Maps will produce.
size = "640x640"
end
size = string.gsub(size, "x", ",")
params["mapSize"] = size
return true, params, options
end
postrule = function()
-- Only run if a previous script has registered geolocation data.
return not geoip.empty()
end
action = function()
local output = stdnse.output_table()
-- Parse and sanity check the command line arguments.
local status, params, options = parse_args()
if not status then
output.ERROR = params
return output, output.ERROR
end
-- Render the map.
local status, msg = render(params, options)
if not status then
output.ERROR = msg
return output, output.ERROR
end
return output, stdnse.format_output(true, msg)
end

View File

@@ -0,0 +1,182 @@
local http = require "http"
local geoip = require "geoip"
local io = require "io"
local stdnse = require "stdnse"
local table = require "table"
local url = require "url"
description = [[
This script queries the Nmap registry for the GPS coordinates of targets stored
by previous geolocation scripts and renders a Google Map of markers representing
the targets.
Additional information for the Google Static Maps API can be found at:
- https://developers.google.com/maps/documentation/static-maps/intro
]]
---
-- @usage
-- nmap -sn -Pn --script ip-geolocation-geoplugin,ip-geolocation-map-google --script-args ip-geolocation-map-google.api_key=[redacted],ip-geolocation-map-google.map_path=map.png <target>
--
-- @output
-- | ip-geolocation-map-google:
-- |_ The map has been saved at 'map.png'.
--
-- @args ip-geolocation-map-google.api_key The required Google Maps API key for
-- your account. An API key can be generated at
-- https://developers.google.com/maps/documentation/static-maps/." @args
--
-- ip-geolocation-map-google.center GPS coordinates defining the center of the
-- image. If omitted, Google Maps will choose a center that shows all the
-- markers.
--
-- @args ip-geolocation-map-google.format The default value is 'png' (alias for
-- 'png8'), 'png32', 'gif', 'jpg', and 'jpg-baseline' are also allowed.
-- https://developers.google.com/maps/documentation/static-maps/intro#ImageFormats
--
-- @args ip-geolocation-map-google.language The default value is 'en', but other
-- two-letter language codes are accepted.
--
-- @args ip-geolocation-map-google.layer The default value is 'roadmap',
-- 'satellite', 'hybrid', and 'terrain' are also allowed.
-- https://developers.google.com/maps/documentation/static-maps/intro#MapTypes
--
-- @args ip-geolocation-map-google.map_path The path at which the rendered
-- Google Map will be saved to the local filesystem.
--
-- @args ip-geolocation-map-google.marker_style This argument can apply styling
-- to the markers.
-- https://developers.google.com/maps/documentation/static-maps/intro#MarkerStyles
--
-- @args ip-geolocation-map-google.scale The default value is 1, but values 2
-- and 4 are permitted. Scale level 4 is only available to Google Maps Premium
-- customers.
-- https://developers.google.com/maps/documentation/static-maps/intro#scale_values
--
-- @args ip-geolocation-map-google.size The default value is '640x640' pixels,
-- but can be increased by Google Maps Premium customers.
-- https://developers.google.com/maps/documentation/static-maps/intro#Imagesizes
author = "Mak Kolybabi <mak@kolybabi.com>"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"external", "safe"}
local render = function(params, options)
-- Add in a marker for each GPS coordinate.
local markers = {}
for coords, ips in pairs(geoip.get_all_by_gps()) do
table.insert(markers, coords)
end
params["markers"] = options["marker_style"] .. "|" .. table.concat(markers, "|")
-- Format the parameters into a properly encoded URL.
local query = "/maps/api/staticmap?" .. url.build_query(params)
stdnse.debug1("The query URL is: %s", query)
-- Check that the query string is below the 8192 character limit after
-- URL-encoding.
if #query > 8192 then
return false, ("Refused to send query since URL path is %d chararacters, but Google Maps limits to 8192."):format(#query)
end
local res = http.get("maps.googleapis.com", 80, query)
if not res or res.status ~= 200 then
return false, ("Failed to recieve map using query '%s'."):format(query)
end
local f = io.open(options["map_path"], "w")
if not f then
return false, ("Failed to open file '%s'."):format(options["map_path"])
end
if not f:write(res.body) then
return false, ("Failed to write file '%s'."):format(options["map_path"])
end
f:close()
return true, ("The map has been saved at '%s'."):format(options["map_path"])
end
local parse_args = function()
local options = {}
local params = {}
local api_key = stdnse.get_script_args(SCRIPT_NAME .. '.api_key')
if not api_key then
return false, "Need to specify an API key, get one at https://developers.google.com/maps/documentation/static-maps/."
end
params["key"] = api_key
local center = stdnse.get_script_args(SCRIPT_NAME .. ".center")
if center then
params["center"] = center
end
local format = stdnse.get_script_args(SCRIPT_NAME .. ".format")
if format then
params["format"] = format
end
local language = stdnse.get_script_args(SCRIPT_NAME .. ".language")
if language then
params["language"] = language
end
local layer = stdnse.get_script_args(SCRIPT_NAME .. ".layer")
if layer then
params["layer"] = layer
end
local map_path = stdnse.get_script_args(SCRIPT_NAME .. '.map_path')
if map_path then
options["map_path"] = map_path
else
return false, "Need to specify a path for the map."
end
local marker_style = stdnse.get_script_args(SCRIPT_NAME .. ".marker_style")
stdnse.debug1('--> [%s]', marker_style)
if not marker_style then
marker_style = ""
end
options["marker_style"] = marker_style
local scale = stdnse.get_script_args(SCRIPT_NAME .. ".scale")
if scale then
params["scale"] = scale
end
local size = stdnse.get_script_args(SCRIPT_NAME .. ".size")
if not size then
size = "640x640"
end
params["size"] = size
return true, params, options
end
postrule = function()
-- Only run if a previous script has registered geolocation data.
return not geoip.empty()
end
action = function()
local output = stdnse.output_table()
-- Parse and sanity check the command line arguments.
local status, params, options = parse_args()
if not status then
output.ERROR = params
return output, output.ERROR
end
-- Render the map.
local status, msg = render(params, options)
if not status then
output.ERROR = msg
return output, output.ERROR
end
return output, stdnse.format_output(true, msg)
end

View File

@@ -0,0 +1,91 @@
local http = require "http"
local geoip = require "geoip"
local io = require "io"
local stdnse = require "stdnse"
local table = require "table"
description = [[
This script queries the Nmap registry for the GPS coordinates of targets stored
by previous geolocation scripts and produces a KML file of points representing
the targets.
]]
---
-- @usage
-- nmap -sn -Pn --script ip-geolocation-geoplugin,ip-geolocation-map-kml --script-args ip-geolocation-map-kml.map_path=map.kml <target>
--
-- @output
-- | ip-geolocation-map-kml:
-- |_ The map has been saved at 'map.kml'.
--
-- @args ip-geolocation-map-kml.map_path (REQUIRED)
author = "Mak Kolybabi <mak@kolybabi.com>"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"safe"}
local render = function(path)
local kml = {'<?xml version="1.0" encoding="UTF-8"?>\n<kml xmlns="http://www.opengis.net/kml/2.2">\n <Document>'}
for ip, coords in pairs(geoip.get_all_by_ip()) do
table.insert(kml, ([[
<Placemark>
<name>%s</name>
<Point>
<coordinates>%s,%s</coordinates>
</Point>
</Placemark>]]):format(ip, coords["longitude"], coords["latitude"])
)
end
table.insert(kml, ' </Document>\n</kml>\n')
kml = table.concat(kml, "\n")
local f = io.open(path, "w")
if not f then
return false, ("Failed to open file '%s'."):format(path)
end
if not f:write(kml) then
return false, ("Failed to write file '%s'."):format(path)
end
f:close()
return true, ("The map has been saved at '%s'."):format(path)
end
local parse_args = function()
local map_path = stdnse.get_script_args(SCRIPT_NAME .. '.map_path')
if not map_path then
return false, "Need to specify a path for the map."
end
return true, map_path
end
postrule = function()
-- Only run if a previous script has registered geolocation data.
return not geoip.empty()
end
action = function()
local output = stdnse.output_table()
-- Parse and sanity check the command line arguments.
local status, path = parse_args()
if not status then
output.ERROR = path
return output, output.ERROR
end
-- Render the map.
local status, msg = render(path)
if not status then
output.ERROR = msg
return output, output.ERROR
end
return msg
end

View File

@@ -1,4 +1,5 @@
local bit = require "bit" local bit = require "bit"
local geoip = require "geoip"
local io = require "io" local io = require "io"
local ipOps = require "ipOps" local ipOps = require "ipOps"
local math = require "math" local math = require "math"
@@ -492,6 +493,7 @@ local GeoIP = {
output_record_by_addr = function(self,addr) output_record_by_addr = function(self,addr)
local loc = self:record_by_addr(addr) local loc = self:record_by_addr(addr)
if not loc then return nil end if not loc then return nil end
geoip.add(addr, loc.latitude, loc.longitude)
setmetatable(loc, record_metatable) setmetatable(loc, record_metatable)
return loc return loc
end, end,

View File

@@ -279,6 +279,9 @@ Entry { filename = "informix-tables.nse", categories = { "auth", "intrusive", }
Entry { filename = "ip-forwarding.nse", categories = { "discovery", "safe", } } Entry { filename = "ip-forwarding.nse", categories = { "discovery", "safe", } }
Entry { filename = "ip-geolocation-geoplugin.nse", categories = { "discovery", "external", "safe", } } Entry { filename = "ip-geolocation-geoplugin.nse", categories = { "discovery", "external", "safe", } }
Entry { filename = "ip-geolocation-ipinfodb.nse", categories = { "discovery", "external", "safe", } } Entry { filename = "ip-geolocation-ipinfodb.nse", categories = { "discovery", "external", "safe", } }
Entry { filename = "ip-geolocation-map-bing.nse", categories = { "external", "safe", } }
Entry { filename = "ip-geolocation-map-google.nse", categories = { "external", "safe", } }
Entry { filename = "ip-geolocation-map-kml.nse", categories = { "safe", } }
Entry { filename = "ip-geolocation-maxmind.nse", categories = { "discovery", "external", "safe", } } Entry { filename = "ip-geolocation-maxmind.nse", categories = { "discovery", "external", "safe", } }
Entry { filename = "ip-https-discover.nse", categories = { "default", "discovery", "safe", } } Entry { filename = "ip-https-discover.nse", categories = { "default", "discovery", "safe", } }
Entry { filename = "ipidseq.nse", categories = { "discovery", "safe", } } Entry { filename = "ipidseq.nse", categories = { "discovery", "safe", } }