local io = require "io" local nmap = require "nmap" local os = require "os" local packet = require "packet" local stdnse = require "stdnse" local stringaux = require "stringaux" local table = require "table" local url = require "url" description=[[ Sniffs an interface for HTTP traffic and dumps any URLs, and their originating IP address. Script output differs from other script as URLs are written to stdout directly. There is also an option to log the results to file. The script can be limited in time by using the timeout argument or run until a ctrl+break is issued, by setting the timeout to 0. ]] --- -- @usage -- nmap --script url-snarf -e -- -- @output -- | url-snarf: -- |_ Sniffed 169 URLs in 5 seconds -- -- @args url-snarf.timeout runs the script until the timeout is reached. -- a timeout of 0s can be used to run until ctrl+break. (default: 30s) -- @args url-snarf.nostdout doesn't write any output to stdout while running -- @args url-snarf.outfile filename to which all discovered URLs are written -- @args url-snarf.interface interface on which to sniff (overrides -e) -- author = "Patrik Karlsson" license = "Same as Nmap--See https://nmap.org/book/man-legal.html" categories = {"safe"} prerule = function() if not nmap.is_privileged() then stdnse.verbose1("not running for lack of privileges.") return false end return true end -- we should probably leverage code from the http library, but those functions -- are all declared local. local function get_url(data) local headers, body = table.unpack(stringaux.strsplit("\r\n\r\n", data)) if ( not(headers) ) then return end headers = stringaux.strsplit("\r\n", headers) if ( not(headers) or 1 > #headers ) then return end local parsed = {} parsed.path = headers[1]:match("^[^s%s]+ ([^%s]*) HTTP/1%.%d$") if ( not(parsed.path) ) then return end for _, v in ipairs(headers) do parsed.host, parsed.port = v:match("^Host: (.*):?(%d?)$") if ( parsed.host ) then break end end if ( not(parsed.host) ) then return end parsed.port = ( #parsed.port ~= 0 ) and parsed.port or nil parsed.scheme = "http" local u = url.build(parsed) if ( not(u) ) then return end return u end local arg_timeout = stdnse.parse_timespec(stdnse.get_script_args(SCRIPT_NAME..".timeout")) arg_timeout = arg_timeout or 30 local arg_nostdout= stdnse.get_script_args(SCRIPT_NAME..".nostdout") local arg_outfile = stdnse.get_script_args(SCRIPT_NAME..".outfile") local function log_entry(src_ip, url) local outfd = io.open(arg_outfile, "a") if ( outfd ) then local entry = ("%s\t%s\r\n"):format(src_ip, url) outfd:write(entry) outfd:close() end end action = function() local arg_iface local collect_interface = function (if_table) if not arg_iface and if_table.up == "up" and if_table.link ~= "loopback" then arg_iface = if_table.device end end stdnse.get_script_interfaces(collect_interface) if not arg_iface then stdnse.verbose1("no network interface was supplied, aborting ...") return false end local counter = 0 if ( arg_outfile ) then local outfd = io.open(arg_outfile, "a") if ( not(outfd) ) then return ("\n ERROR: Failed to open outfile (%s)"):format(arg_outfile) end outfd:close() end local socket = nmap.new_socket() socket:set_timeout(1000) socket:pcap_open(arg_iface, 1500, true, "tcp port 80 and (((ip[2:2] - ((ip[0]&0xf)<<2)) - ((tcp[12]&0xf0)>>2)) != 0)") local start, stop = os.time() repeat local status, len, _, l3 = socket:pcap_receive() if ( status ) then local p = packet.Packet:new( l3, #l3 ) local pos = p.tcp_data_offset + 1 local http_data = p.buf:sub(pos) local url = get_url(http_data) if ( url ) then counter = counter + 1 if ( not(arg_nostdout) ) then print(p.ip_src, url) end if ( arg_outfile ) then log_entry(p.ip_src, url) end end end if ( arg_timeout and arg_timeout > 0 and arg_timeout <= os.time() - start ) then stop = os.time() break end until(false) if ( counter > 0 ) then return ("\n Sniffed %d URLs in %d seconds"):format(counter, stop - start) end end