diff options
Diffstat (limited to '')
-rw-r--r-- | scripts/http-fetch.nse | 251 |
1 files changed, 251 insertions, 0 deletions
diff --git a/scripts/http-fetch.nse b/scripts/http-fetch.nse new file mode 100644 index 0000000..d90d5d2 --- /dev/null +++ b/scripts/http-fetch.nse @@ -0,0 +1,251 @@ +local http = require "http" +local httpspider = require "httpspider" +local io = require "io" +local lfs = require "lfs" +local nmap = require "nmap" +local shortport = require "shortport" +local stdnse = require "stdnse" +local string = require "string" +local stringaux = require "stringaux" +local table = require "table" + +description = [[The script is used to fetch files from servers. + +The script supports three different use cases: +* The paths argument isn't provided, the script spiders the host + and downloads files in their respective folders relative to + the one provided using "destination". +* The paths argument(a single item or list) is provided and the path starts + with "/", the script tries to fetch the path relative to the url + provided via the argument "url". +* The paths argument(a single item or list) is provided and the path doesn't + start with "/". Then the script spiders the host and tries to find + files which contain the path(now treated as a pattern). +]] + +--- +-- @usage nmap --script http-fetch --script-args destination=/tmp/mirror <target> +-- nmap --script http-fetch --script-args 'paths={/robots.txt,/favicon.ico}' <target> +-- nmap --script http-fetch --script-args 'paths=.html' <target> +-- nmap --script http-fetch --script-args 'url=/images,paths={.jpg,.png,.gif}' <target> +-- +-- @args http-fetch.destination - The full path of the directory to save the file(s) to preferably with the trailing slash. +-- @args http-fetch.files - The name of the file(s) to be fetched. +-- @args http-fetch.url The base URL to start fetching. Default: "/" +-- @args http-fetch.paths A list of paths to fetch. If relative, then the site will be spidered to find matching filenames. +-- Otherwise, they will be fetched relative to the url script-arg. +-- @args http-fetch.maxdepth The maximum amount of directories beneath +-- the initial url to spider. A negative value disables the limit. +-- (default: 3) +-- @args http-fetch.maxpagecount The maximum amount of pages to fetch. +-- @args http-fetch.noblacklist By default files like jpg, rar, png are blocked. To +-- fetch such files set noblacklist to true. +-- @args http-fetch.withinhost The default behavior is to fetch files from the same host. Set to False +-- to do otherwise. +-- @args http-fetch.withindomain If set to true then the crawling would be restricted to the domain provided +-- by the user. +-- +-- @output +-- | http-fetch: +-- | Successfully Downloaded: +-- | http://scanme.nmap.org:80/ as /tmp/mirror/45.33.32.156/80/index.html +-- |_ http://scanme.nmap.org/shared/css/insecdb.css as /tmp/mirror/45.33.32.156/80/shared/css/insecdb.css +-- +-- @xmloutput +-- <table key="Successfully Downloaded"> +-- <elem>http://scanme.nmap.org:80/ as /tmp/mirror/45.33.32.156/80/index.html</elem> +-- <elem>http://scanme.nmap.org/shared/css/insecdb.css as /tmp/mirror/45.33.32.156/80/shared/css/insecdb.css</elem> +-- </table> +-- <elem key="result">Successfully Downloaded Everything At: /tmp/mirror/45.33.32.156/80/</elem> + +author = "Gyanendra Mishra" + +license = "Same as Nmap--See https://nmap.org/book/man-legal.html" + +categories = {"safe"} + +portrule = shortport.http + +local SEPARATOR = lfs.get_path_separator() + +local function build_path(file, url) + local path = '/' .. url .. file + return path:gsub('//', '/') +end + +local function create_directory(path) + local status, err = lfs.mkdir(path) + if status then + stdnse.debug2("Created path %s", path) + return true + elseif err == "No such file or directory" then + stdnse.debug2("Parent directory doesn't exist %s", path) + local index = string.find(path:sub(1, path:len() -1), SEPARATOR .. "[^" .. SEPARATOR .. "]*$") + local sub_path = path:sub(1, index) + stdnse.debug2("Trying path...%s", sub_path) + create_directory(sub_path) + lfs.mkdir(path) + end +end + +local function save_file(content, file_name, destination, url) + + local file_path + + if file_name then + file_path = destination .. file_name + else + file_path = destination .. url:getDir() + create_directory(file_path) + if url:getDir() == url:getFile() then + file_path = file_path .. "index.html" + else + file_path = file_path .. stringaux.filename_escape(url:getFile():gsub(url:getDir(),"")) + end + end + + file_path = file_path:gsub("//", "/") + file_path = file_path:gsub("\\/", "\\") + + local file,err = io.open(file_path,"r") + if not err then + stdnse.debug1("File Already Exists") + return true, file_path + end + file, err = io.open(file_path,"w") + if file then + stdnse.debug1("Saving to ...%s",file_path) + file:write(content) + file:close() + return true, file_path + else + stdnse.debug1("Error encountered in writing file.. %s",err) + return false, err + end +end + +local function fetch_recursively(host, port, url, destination, patterns, output) + local crawler = httpspider.Crawler:new(host, port, url, { scriptname = SCRIPT_NAME }) + crawler:set_timeout(10000) + while(true) do + local status, r = crawler:crawl() + if ( not(status) ) then + if ( r.err ) then + return stdnse.format_output(false, r.reason) + else + break + end + end + local body = r.response.body + local url_string = tostring(r.url) + local file = r.url:getFile():gsub(r.url:getDir(),"") + if body and r.response.status == 200 and patterns then + for _, pattern in pairs(patterns) do + if file:find(pattern, nil, true) then + local status, err_message = save_file(r.response.body, nil, destination, r.url) + if status then + output['Matches'] = output['Matches'] or {} + output['Matches'][pattern] = output['Matches'][pattern] or {} + table.insert(output['Matches'][pattern], string.format("%s as %s",r.url:getFile()),err_message) + else + output['ERROR'] = output['ERROR'] or {} + output['ERROR'][url_string] = err_message + end + break + end + end + elseif body and r.response.status == 200 then + stdnse.debug1("Processing url.......%s",url_string) + local stat, path_or_err = save_file(body, nil, destination, r.url) + if stat then + output['Successfully Downloaded'] = output['Successfully Downloaded'] or {} + table.insert(output['Successfully Downloaded'], string.format("%s as %s", url_string, path_or_err)) + else + output['ERROR'] = output['ERROR'] or {} + output['ERROR'][url_string] = path_or_err + end + else + if not r.response.body then + stdnse.debug1("No Body For: %s",url_string) + elseif r.response and r.response.status ~= 200 then + stdnse.debug1("Status not 200 For: %s",url_string) + else + stdnse.debug1("False URL picked by spider!: %s",url_string) + end + end + end +end + + +local function fetch(host, port, url, destination, path, output) + local response = http.get(host, port, build_path(path, url), nil) + if response and response.status and response.status == 200 then + local file = path:sub(path:find("/[^/]*$") + 1) + local save_as = (host.targetname or host.ip) .. SEPARATOR .. tostring(port.number) .. "-" .. file + local status, err_message = save_file(response.body, save_as, destination) + if status then + output['Successfully Downloaded'] = output['Successfully Downloaded'] or {} + table.insert(output['Successfully Downloaded'], string.format("%s as %s", path, save_as)) + else + output['ERROR'] = output['ERROR'] or {} + output['ERROR'][path] = err_message + end + else + stdnse.debug1("%s doesn't exist on server at %s.", path, url) + end +end + +action = function(host, port) + + local destination = stdnse.get_script_args(SCRIPT_NAME..".destination") or false + local url = stdnse.get_script_args(SCRIPT_NAME..".url") or "/" + local paths = stdnse.get_script_args(SCRIPT_NAME..'.paths') or nil + + local output = stdnse.output_table() + local patterns = {} + + if not destination then + output.ERROR = "Please enter the complete path of the directory to save data in." + return output, output.ERROR + end + + local sub_directory = tostring(host.ip) .. SEPARATOR .. tostring(port.number) .. SEPARATOR + + if destination:sub(-1) == '\\' or destination:sub(-1) == '/' then + destination = destination .. sub_directory + else + destination = destination .. SEPARATOR .. sub_directory + end + + if paths then + if type(paths) ~= 'table' then + paths = {paths} + end + for _, path in pairs(paths) do + if path:sub(1, 1) == "/" then + fetch(host, port, url, destination, path, output) + else + table.insert(patterns, path) + end + end + if #patterns > 0 then + fetch_recursively(host, port, url, destination, patterns, output) + end + else + fetch_recursively(host, port, url, destination, nil, output) + end + + if #output > 0 then + if paths then + return output + else + if nmap.verbosity() > 1 then + return output + else + output.result = "Successfully Downloaded Everything At: " .. destination + return output, output.result + end + end + end +end + |