o [NSE] Added a natpmp library and the script nat-pmp-mapport that allows

NAT mapping of external TCP and UDP ports to internal addresses. [Patrik]
This commit is contained in:
patrik 2012-01-02 11:41:21 +00:00
parent f5b14a2099
commit 2269e76438
5 changed files with 364 additions and 95 deletions

View file

@ -1,104 +1,35 @@
description = [[
Queries a NAT-PMP service for its external address.
Get's the routers WAN IP using the NAT Port Mapping Protocol (NAT-PMP).
The NAT-PMP protocol is supported by a broad range of routers including:
- Apple AirPort Express
- Apple AirPort Extreme
- Apple Time Capsule
- DD-WRT
- OpenWrt v8.09 or higher, with MiniUPnP daemon
- pfSense v2.0
- Tarifa (firmware) (Linksys WRT54G/GL/GS)
- Tomato Firmware v1.24 or higher. (Linksys WRT54G/GL/GS and many more)
- Peplink Balance
]]
---
-- @usage
-- nmap -sU --script nat-pmp-info -p 5351 <host>
--
-- @output
-- PORT STATE SERVICE REASON
-- 5351/udp open unknown udp-response
-- | nat-pmp-info:
-- |_ External ip: 1.2.3.4
--
--
-- The implementation is based on the following documentation:
-- http://files.dns-sd.org/draft-cheshire-nat-pmp.txt
--
--
-- Version 0.1
-- Created 09/15/2010 - v0.1 - created by Patrik Karlsson <patrik@cqure.net>
--
author = "Patrik Karlsson"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"default", "safe", "discovery"}
categories = {"default", "discovery", "safe"}
require "stdnse"
require "shortport"
require 'shortport'
require 'natpmp'
portrule = shortport.port_or_service(5351, "nat-pmp", "udp")
portrule = shortport.port_or_service(5351, "nat-pmp", {"udp"} )
process_response = function( data )
--
-- Make sure we received exactly 12 bytes:
--
-- 0 1 2 3
-- 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
-- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-- | Vers = 0 | OP = 128 + 0 | Result Code |
-- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-- | Seconds Since Start of Epoch |
-- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-- | External IP Address (a.b.c.d) |
-- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
--
action = function(host, port)
local helper = natpmp.Helper:new(host, port)
local status, response = helper:getWANIP()
if ( #data ~= 12 ) then return false, "Invalid length" end
local pos, version, op, result, time = bin.unpack("CCSI", data )
-- Make sure the result code is zero (OK)
if ( result ~= 0 ) then
return false, ("Non-zero (%d) result code returned"):format(result)
if ( status ) then
nmap.set_port_state(host, port, "open")
port.version.name = "nat-pmp"
nmap.set_port_version(host, port, "hardmatched")
return stdnse.format_output(true, ("WAN IP: %s"):format(response.ip))
end
local _, o1, o2, o3, o4 = bin.unpack("CCCC", data, pos )
return true, ("%d.%d.%d.%d"):format(o1,o2,o3,o4)
end
action = function( host, port )
local socket = nmap.new_socket()
local status = socket:connect( host, port, "udp" )
socket:set_timeout(5000)
-- 0 1
-- 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
-- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-- | Vers = 0 | OP = 0 |
-- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
--
-- Layout of the query for external IP packet
--
local packet = string.char( 0, 0 )
status = socket:send( packet )
if( not(status) ) then
stdnse.print_debug(3, "ERROR: Failed to send data")
return
end
local data
status, data = socket:receive_bytes(12)
if( not(status) ) then
stdnse.print_debug(3, "ERROR: Failed to receive data")
return
end
local external_ip
status, external_ip = process_response( data )
if ( not(status) ) then stdnse.print_debug(3, external_ip) end
-- set port to open
nmap.set_port_state(host, port, "open")
port.version.name = "nat-pmp"
nmap.set_port_version(host, port, "hardmatched")
return (" \n External ip: %s"):format( external_ip )
end

114
scripts/nat-pmp-mapport.nse Normal file
View file

@ -0,0 +1,114 @@
description = [[
Maps a WAN port on the router to a local port on the client.
The script uses the NAT Port Mapping Protocol (NAT-PMP) to do so and supports the following operations:
o map - maps a new external port on the router to an internal port of the requesting IP
o unmap - unmaps a previously mapped port for the requesting IP
o unmapall - unmaps all previously mapped ports for the requesting IP
]]
---
-- @usage
-- nmap -sU -p 5351 <ip> --script nat-pmp-mapport --script-args='op=map,pubport=8080,privport=8080,protocol=tcp'
-- nmap -sU -p 5351 <ip> --script nat-pmp-mapport --script-args='op=unmap,pubport=8080,privport=8080,protocol=tcp'
-- nmap -sU -p 5351 <ip> --script nat-pmp-mapport --script-args='op=unmapall,protocol=tcp'
--
-- @output
-- PORT STATE SERVICE
-- 5351/udp open nat-pmp
-- | nat-pmp-mapport:
-- |_ Successfully mapped tcp 1.2.3.4:8080 -> 192.168.0.100:80
--
-- @args nat-pmp-mapport.op operation, can be either map, unmap or unmap all
-- o map allows you to map an external port to an internal port of the calling IP
-- o unmap removes the external port mapping for the specified ports and protocol
-- o unmapall removes all mappings for the specified protocol and calling IP
--
-- @args nat-pmp-mapport.pubport the external port to map on the router. The
-- specified port is treated as the requested port. If the port is available
-- it will be allocated to the caller, otherwise the router will simply
-- choose another port, create the mapping and return the resulting port.
--
-- @args nat-pmp-mapport.privport the internal port of the calling IP to map requests
-- to. This port will recieve all requests coming in to the external port on the
-- router.
--
-- @args nat-pmp-mapport.protocol the protocol to map, can be either tcp or udp.
--
-- @args nat-pmp-mapport.lifetime the lifetime of the mapping in seconds (default: 3600)
--
author = "Patrik Karlsson"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"discovery", "safe"}
require 'shortport'
require 'natpmp'
require 'tab'
portrule = shortport.port_or_service(5351, "nat-pmp", {"udp"} )
local arg_pubport = stdnse.get_script_args(SCRIPT_NAME .. ".pubport")
local arg_privport= stdnse.get_script_args(SCRIPT_NAME .. ".privport")
local arg_protocol= stdnse.get_script_args(SCRIPT_NAME .. ".protocol")
local arg_lifetime= stdnse.get_script_args(SCRIPT_NAME .. ".lifetime") or 3600
local arg_op = stdnse.get_script_args(SCRIPT_NAME .. ".op") or "map"
local function fail(str) return "\n ERROR: " .. str end
action = function(host, port)
local op = arg_op:lower()
if ( "map" ~= op and "unmap" ~= op and "unmapall" ~= op ) then
return fail("Operation must be either \"map\", \"unmap\" or \"unmapall\"")
end
if ( ("map" == op or "unmap" == op ) and
( not(arg_pubport) or not(arg_privport) or not(arg_protocol) ) ) then
return fail("The arguments pubport, privport and protocol are required")
elseif ( "unmapall" == op and not(arg_protocol) ) then
return fail("The argument protocol is required")
end
local helper = natpmp.Helper:new(host, port)
if ( "unmap" == op or "unmapall" == op ) then
arg_lifetime = 0
end
if ( "unmapall" == op ) then
arg_pubport, arg_privport = 0, 0
end
local status, response = helper:getWANIP()
if ( not(status) ) then
return fail("Failed to retrieve WAN IP")
end
local wan_ip = response.ip
local lan_ip = (nmap.get_interface_info(host.interface)).address
local status, response = helper:mapPort(arg_pubport, arg_privport, arg_protocol, arg_lifetime)
if ( not(status) ) then
return fail(response)
end
local output
if ( "unmap" == op ) then
output = ("Successfully unmapped %s %s:%d -> %s:%d"):format(
arg_protocol, wan_ip, response.pubport, lan_ip, response.privport )
elseif ( "unmapall" == op ) then
output = ("Sucessfully unmapped all %s NAT mappings for %s"):format(arg_protocol, lan_ip)
else
output = ("Successfully mapped %s %s:%d -> %s:%d"):format(
arg_protocol, wan_ip, response.pubport, lan_ip, response.privport )
if ( tonumber(arg_pubport) ~= tonumber(response.pubport) ) then
output = { output }
table.insert(output, "WARNING: Requested public port could not be allocated")
end
end
return stdnse.format_output(true, output)
end

View file

@ -184,6 +184,7 @@ Entry { filename = "mysql-info.nse", categories = { "default", "discovery", "saf
Entry { filename = "mysql-users.nse", categories = { "auth", "intrusive", } }
Entry { filename = "mysql-variables.nse", categories = { "discovery", "intrusive", } }
Entry { filename = "nat-pmp-info.nse", categories = { "default", "discovery", "safe", } }
Entry { filename = "nat-pmp-mapport.nse", categories = { "discovery", "safe", } }
Entry { filename = "nbstat.nse", categories = { "default", "discovery", "safe", } }
Entry { filename = "ncp-enum-users.nse", categories = { "auth", "safe", } }
Entry { filename = "ncp-serverinfo.nse", categories = { "default", "discovery", "safe", } }