mirror of
https://github.com/nmap/nmap.git
synced 2025-12-15 04:09:01 +00:00
o [NSE] Added the firewalk script, which maps firewall rules in a way
similar to the firewalk tool. [Henri Doreau]
This commit is contained in:
@@ -1,5 +1,8 @@
|
|||||||
# Nmap Changelog ($Id$); -*-text-*-
|
# Nmap Changelog ($Id$); -*-text-*-
|
||||||
|
|
||||||
|
o [NSE] Added the firewalk script, which maps firewall rules in a way
|
||||||
|
similar to the firewalk tool. [Henri Doreau]
|
||||||
|
|
||||||
o [NSE] Made the ftp-anon script return a directory listing when
|
o [NSE] Made the ftp-anon script return a directory listing when
|
||||||
anonymous login is allowed. [Gutek, David]
|
anonymous login is allowed. [Gutek, David]
|
||||||
|
|
||||||
|
|||||||
319
scripts/firewalk.nse
Normal file
319
scripts/firewalk.nse
Normal file
@@ -0,0 +1,319 @@
|
|||||||
|
description = [[
|
||||||
|
Try to discover firewall rules by using ip ttl expiration technique (method
|
||||||
|
also known as firewalking").
|
||||||
|
|
||||||
|
The scan requires a firewall (or "gateway") and a metric (or "target").
|
||||||
|
|
||||||
|
For each filtered port on the target, send a probe with an ip ttl one greater
|
||||||
|
than the number of hops to the gateway.
|
||||||
|
|
||||||
|
If the probe is forwarded by the gateway, then we can expect to receive an
|
||||||
|
ICMP_TIME_EXCEEDED reply from the gateway next hop router, or eventually the
|
||||||
|
target if it is directly connected to the gateway. Otherwise, the probe will
|
||||||
|
timeout.
|
||||||
|
|
||||||
|
As for udp scans, this process can be quite slow if lots of ports are blocked
|
||||||
|
by the gateway.
|
||||||
|
|
||||||
|
From an original idea of M. Schiffman and D. Goldsmith, authors of the
|
||||||
|
firewalk tool.
|
||||||
|
]]
|
||||||
|
|
||||||
|
---
|
||||||
|
-- @usage nmap --script firewalk --script-args firewalk.gateway=a.b.c.d --traceroute target
|
||||||
|
--
|
||||||
|
-- @args firewalk.gateway IP address of the tested firewall. Must be present in the traceroute results
|
||||||
|
--
|
||||||
|
-- @output
|
||||||
|
-- |_firewalk: forwarded ports (tcp): 21-80,443
|
||||||
|
--
|
||||||
|
|
||||||
|
|
||||||
|
-- 08/10/2010
|
||||||
|
author = "Henri Doreau <henri.doreau[at]gmail.com>"
|
||||||
|
|
||||||
|
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
|
||||||
|
|
||||||
|
categories = {"safe", "discovery"}
|
||||||
|
|
||||||
|
|
||||||
|
require('bin')
|
||||||
|
require('packet')
|
||||||
|
|
||||||
|
|
||||||
|
local ICMP_TIME_EXCEEDED = 11
|
||||||
|
|
||||||
|
|
||||||
|
-- number of retries for unanswered probes
|
||||||
|
local MAX_RETRIES = 2
|
||||||
|
|
||||||
|
|
||||||
|
--- ensure that the catched reply is a valid icmp time exceeded and return
|
||||||
|
-- wether the reply appears to be valid or not
|
||||||
|
local checkpkt = function(reply, orig)
|
||||||
|
local ip = packet.Packet:new(reply, reply:len())
|
||||||
|
|
||||||
|
if ip.ip_p ~= packet.IPPROTO_ICMP or ip.icmp_type ~= ICMP_TIME_EXCEEDED then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local is = ip.buf:sub(ip.icmp_offset + 9)
|
||||||
|
local ip2 = packet.Packet:new(is, is:len(), true)
|
||||||
|
|
||||||
|
-- Check sent packet against ICMP payload
|
||||||
|
if ip2.ip_p ~= packet.IPPROTO_TCP or
|
||||||
|
ip2.ip_bin_src ~= orig.ip_bin_src or
|
||||||
|
ip2.ip_bin_dst ~= orig.ip_bin_dst or
|
||||||
|
ip2.tcp_sport ~= orig.tcp_sport or
|
||||||
|
ip2.tcp_dport ~= orig.tcp_dport then
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
--- pcap callback
|
||||||
|
-- @return destination ip address, the ip protocol and icmp type
|
||||||
|
local callback = function(size, layer2, layer3)
|
||||||
|
local ip = packet.Packet:new(layer3, layer3:len())
|
||||||
|
return bin.pack('ACC', ip.ip_bin_dst, ip.ip_p, ip.icmp_type)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- set destination port and ip ttl to a generic tcp packet
|
||||||
|
-- @param ip the ip object
|
||||||
|
-- @param dport the layer 4 destination port
|
||||||
|
-- @param ttl the ip ttl to set
|
||||||
|
local updatepkt = function(ip, dport, ttl)
|
||||||
|
ip:ip_set_ttl(ttl)
|
||||||
|
ip:tcp_set_sport(math.random(0x401, 0xffff))
|
||||||
|
ip:tcp_set_dport(dport)
|
||||||
|
ip:tcp_set_seq(math.random(1, 0x7fffffff))
|
||||||
|
ip:tcp_count_checksum(ip.ip_len)
|
||||||
|
ip:ip_count_checksum()
|
||||||
|
end
|
||||||
|
|
||||||
|
--- create a generic tcp packet, with ip ttl and destination port set to zero
|
||||||
|
-- @param host Host object that represents the destination
|
||||||
|
-- @return the ip packet object
|
||||||
|
local genericpkt = function(host)
|
||||||
|
local pkt = bin.pack("H",
|
||||||
|
"4500 002c 55d1 0000 8006 0000 0000 0000" ..
|
||||||
|
"0000 0000 0000 0000 0000 0000 0000 0000" ..
|
||||||
|
"6002 0c00 0000 0000 0204 05b4"
|
||||||
|
)
|
||||||
|
|
||||||
|
local tcp = packet.Packet:new(pkt, pkt:len())
|
||||||
|
|
||||||
|
tcp:ip_set_bin_src(host.bin_ip_src)
|
||||||
|
tcp:ip_set_bin_dst(host.bin_ip)
|
||||||
|
|
||||||
|
updatepkt(tcp, 0, 0)
|
||||||
|
|
||||||
|
return tcp
|
||||||
|
end
|
||||||
|
|
||||||
|
--- get the list of ports to probe
|
||||||
|
-- @param host Host object that represents the targetted host
|
||||||
|
-- @return list of ports to probe
|
||||||
|
local getports = function(host)
|
||||||
|
local ports = {}
|
||||||
|
local port = nil
|
||||||
|
|
||||||
|
repeat
|
||||||
|
port = nmap.get_ports(host, port, "tcp", "filtered")
|
||||||
|
if port then
|
||||||
|
table.insert(ports, port.number)
|
||||||
|
end
|
||||||
|
until not port
|
||||||
|
|
||||||
|
return ports
|
||||||
|
end
|
||||||
|
|
||||||
|
--- store the firewalk ports into the registry
|
||||||
|
-- @param host Host object that represents the targetted host
|
||||||
|
-- @param ports list of ports to firewalk
|
||||||
|
local setregs = function(host, ports)
|
||||||
|
if not nmap.registry[host.ip] then
|
||||||
|
nmap.registry[host.ip] = {}
|
||||||
|
end
|
||||||
|
nmap.registry[host.ip]['firewalk_ports'] = ports
|
||||||
|
end
|
||||||
|
|
||||||
|
--- host rule, check for requirements before to launch the script
|
||||||
|
hostrule = function(host)
|
||||||
|
-- firewalk requires privileges to run
|
||||||
|
if not nmap.is_privileged() then
|
||||||
|
if not nmap.registry['firewalk'] then
|
||||||
|
nmap.registry['firewalk'] = {}
|
||||||
|
end
|
||||||
|
if nmap.registry['firewalk']['rootfail'] then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
nmap.registry['firewalk']['rootfail'] = true
|
||||||
|
if nmap.verbosity() > 0 then
|
||||||
|
nmap.log_write("stdout", "FIREWALK: not running for lack of privileges")
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
-- also needs traceroute to have been performed before
|
||||||
|
if not host.traceroute then
|
||||||
|
if not nmap.registry['firewalk'] then
|
||||||
|
nmap.registry['firewalk'] = {}
|
||||||
|
end
|
||||||
|
if nmap.registry['firewalk']['traceroutefail'] then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
nmap.registry['firewalk']['traceroutefail'] = true
|
||||||
|
if nmap.verbosity() > 0 then
|
||||||
|
nmap.log_write("stdout", "FIREWALK: won't run, need unavailable traceroute informations")
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
if not host.interface then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
local ports = getports(host)
|
||||||
|
if #ports < 1 then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
setregs(host, ports)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
--- bind the scan to gateway(ttl) + 1
|
||||||
|
-- @param host Host object that represents the targetted host
|
||||||
|
-- @param gateway ip address of the gatewau, as a decimal-dotted string
|
||||||
|
-- @return the value of the ttl to use in our probes (or nil on error)
|
||||||
|
local ttlmetric = function(host, gateway)
|
||||||
|
if gateway == packet.toip(host.bin_ip) then
|
||||||
|
if nmap.verbosity() > 0 then
|
||||||
|
nmap.log_write("stdout", "FIREWALK: metric and gateway cannot be the same host")
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- look for the ttl value to use according to traceroute results
|
||||||
|
for i, hop in pairs(host.traceroute) do
|
||||||
|
if hop == gateway then
|
||||||
|
return i + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if nmap.verbosity() > 0 then
|
||||||
|
nmap.log_write("stdout", "FIREWALK: metric " .. gateway .. " doesn't appears in traceroute")
|
||||||
|
end
|
||||||
|
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
--- convert an array of ports into a port ranges string like "x,y-z"
|
||||||
|
-- @param ports an array of numbers
|
||||||
|
-- @return a string representing the ports as folded ranges
|
||||||
|
local portrange = function(ports)
|
||||||
|
table.sort(ports)
|
||||||
|
local numranges = {}
|
||||||
|
for _, p in ipairs(ports) do
|
||||||
|
local stored = false
|
||||||
|
|
||||||
|
-- iterate over the ports list
|
||||||
|
for k, range in ipairs(numranges) do
|
||||||
|
-- increase an existing range by the left
|
||||||
|
if p == range["start"]-1 then
|
||||||
|
numranges[k]["start"] = p
|
||||||
|
stored = true
|
||||||
|
-- increase an existing range by the right
|
||||||
|
elseif p == range["stop"]+1 then
|
||||||
|
numranges[k]["stop"] = p
|
||||||
|
stored = true
|
||||||
|
-- port contained in an already existing range (catch doublons)
|
||||||
|
elseif p >= range["start"] and p <= range["stop"] then
|
||||||
|
stored = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- start a new range
|
||||||
|
if not stored then
|
||||||
|
local range = {}
|
||||||
|
range["start"] = p
|
||||||
|
range["stop"] = p
|
||||||
|
table.insert(numranges, range)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- stringify the ranges
|
||||||
|
local strrange = {}
|
||||||
|
for i, val in ipairs(numranges) do
|
||||||
|
local start = tostring(val["start"])
|
||||||
|
local stop = tostring(val["stop"])
|
||||||
|
if start == stop then
|
||||||
|
table.insert(strrange, start)
|
||||||
|
else
|
||||||
|
-- contiguous ranges are represented as x-z
|
||||||
|
table.insert(strrange, start .. "-" .. stop)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- ranges are delimited by `,'
|
||||||
|
return stdnse.strjoin(",", strrange)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- main firewalking logic
|
||||||
|
action = function(host)
|
||||||
|
local sock = nmap.new_dnet()
|
||||||
|
local pcap = nmap.new_socket()
|
||||||
|
local saddr = packet.toip(host.bin_ip_src)
|
||||||
|
local gateway = stdnse.get_script_args("firewalk.gateway")
|
||||||
|
local ports = nmap.registry[host.ip]['firewalk_ports']
|
||||||
|
local ttl = ttlmetric(host, gateway)
|
||||||
|
local try = nmap.new_try()
|
||||||
|
local fwdports = {}
|
||||||
|
|
||||||
|
-- abort if unable to bind the scan
|
||||||
|
if not ttl then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- filter for incoming icmp time exceeded replies
|
||||||
|
pcap:pcap_open(host.interface, 104, 0, callback, "icmp and dst host " .. saddr)
|
||||||
|
|
||||||
|
try(sock:ip_open())
|
||||||
|
|
||||||
|
try = nmap.new_try(function() sock:ip_close() end)
|
||||||
|
|
||||||
|
pcap:set_timeout(3000)
|
||||||
|
|
||||||
|
local pkt = genericpkt(host)
|
||||||
|
|
||||||
|
-- iterate over the list of ports
|
||||||
|
for _, port in ipairs(ports) do
|
||||||
|
updatepkt(pkt, port, ttl)
|
||||||
|
local retry = 0
|
||||||
|
|
||||||
|
-- resend on timeout to increase reliability
|
||||||
|
while retry < MAX_RETRIES do
|
||||||
|
try(sock:ip_send(pkt.buf))
|
||||||
|
|
||||||
|
pcap:pcap_register(bin.pack('ACC', pkt.ip_bin_src, packet.IPPROTO_ICMP, ICMP_TIME_EXCEEDED))
|
||||||
|
local status, _, _, rep = pcap:pcap_receive()
|
||||||
|
|
||||||
|
if status then
|
||||||
|
if checkpkt(rep, pkt) then
|
||||||
|
stdnse.print_debug(1, "Firewalk: discovered fwd port " .. port)
|
||||||
|
table.insert(fwdports, port)
|
||||||
|
break
|
||||||
|
end
|
||||||
|
else
|
||||||
|
retry = retry + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
sock:ip_close()
|
||||||
|
pcap:pcap_close()
|
||||||
|
|
||||||
|
if #fwdports < 1 then
|
||||||
|
return "\n no forwarded ports found"
|
||||||
|
else
|
||||||
|
return "\n forwarded ports (tcp): " .. portrange(fwdports)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@@ -31,6 +31,7 @@ Entry { filename = "domino-enum-users.nse", categories = { "auth", "intrusive",
|
|||||||
Entry { filename = "drda-brute.nse", categories = { "auth", "intrusive", } }
|
Entry { filename = "drda-brute.nse", categories = { "auth", "intrusive", } }
|
||||||
Entry { filename = "drda-info.nse", categories = { "discovery", "safe", "version", } }
|
Entry { filename = "drda-info.nse", categories = { "discovery", "safe", "version", } }
|
||||||
Entry { filename = "finger.nse", categories = { "default", "discovery", "safe", } }
|
Entry { filename = "finger.nse", categories = { "default", "discovery", "safe", } }
|
||||||
|
Entry { filename = "firewalk.nse", categories = { "discovery", "safe", } }
|
||||||
Entry { filename = "ftp-anon.nse", categories = { "auth", "default", "safe", } }
|
Entry { filename = "ftp-anon.nse", categories = { "auth", "default", "safe", } }
|
||||||
Entry { filename = "ftp-bounce.nse", categories = { "default", "intrusive", } }
|
Entry { filename = "ftp-bounce.nse", categories = { "default", "intrusive", } }
|
||||||
Entry { filename = "ftp-brute.nse", categories = { "auth", "intrusive", } }
|
Entry { filename = "ftp-brute.nse", categories = { "auth", "intrusive", } }
|
||||||
|
|||||||
Reference in New Issue
Block a user