diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 07:42:04 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 07:42:04 +0000 |
commit | 0d47952611198ef6b1163f366dc03922d20b1475 (patch) | |
tree | 3d840a3b8c0daef0754707bfb9f5e873b6b1ac13 /scripts/http-unsafe-output-escaping.nse | |
parent | Initial commit. (diff) | |
download | nmap-0d47952611198ef6b1163f366dc03922d20b1475.tar.xz nmap-0d47952611198ef6b1163f366dc03922d20b1475.zip |
Adding upstream version 7.94+git20230807.3be01efb1+dfsg.upstream/7.94+git20230807.3be01efb1+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | scripts/http-unsafe-output-escaping.nse | 160 |
1 files changed, 160 insertions, 0 deletions
diff --git a/scripts/http-unsafe-output-escaping.nse b/scripts/http-unsafe-output-escaping.nse new file mode 100644 index 0000000..7d1bc7e --- /dev/null +++ b/scripts/http-unsafe-output-escaping.nse @@ -0,0 +1,160 @@ +local http = require "http" +local httpspider = require "httpspider" +local shortport = require "shortport" +local stdnse = require "stdnse" +local table = require "table" +local url = require "url" + +description = [[ +Spiders a website and attempts to identify output escaping problems +where content is reflected back to the user. This script locates all +parameters, ?x=foo&y=bar and checks if the values are reflected on the +page. If they are indeed reflected, the script will try to insert +ghz>hzx"zxc'xcv and check which (if any) characters were reflected +back onto the page without proper html escaping. This is an +indication of potential XSS vulnerability. +]] + +--- +-- @usage +-- nmap --script=http-unsafe-output-escaping <target> +-- +-- @output +-- PORT STATE SERVICE REASON +-- | http-unsafe-output-escaping: +-- | Characters [> " '] reflected in parameter kalle at http://foobar.gazonk.se/xss.php?foo=bar&kalle=john +-- |_ Characters [> " '] reflected in parameter foo at http://foobar.gazonk.se/xss.php?foo=bar&kalle=john +-- +-- @args http-unsafe-output-escaping.maxdepth the maximum amount of directories beneath +-- the initial url to spider. A negative value disables the limit. +-- (default: 3) +-- @args http-unsafe-output-escaping.maxpagecount the maximum amount of pages to visit. +-- A negative value disables the limit (default: 20) +-- @args http-unsafe-output-escaping.url the url to start spidering. This is a URL +-- relative to the scanned host eg. /default.html (default: /) +-- @args http-unsafe-output-escaping.withinhost only spider URLs within the same host. +-- (default: true) +-- @args http-unsafe-output-escaping.withindomain only spider URLs within the same +-- domain. This widens the scope from <code>withinhost</code> and can +-- not be used in combination. (default: false) +-- +-- @see http-dombased-xss.nse +-- @see http-stored-xss.nse +-- @see http-phpself-xss.nse +-- @see http-xssed.nse + +author = "Martin Holst Swende" +license = "Same as Nmap--See https://nmap.org/book/man-legal.html" +categories = {"discovery", "intrusive"} + + +portrule = shortport.http + +local dbg = stdnse.debug2 + +local function getHostPort(parsed) + return parsed.host, parsed.port or url.get_default_port(parsed.scheme) +end + +local function getReflected(parsed, r) + local reflected_values,not_reflected_values = {},{} + local count = 0 + -- Now, we need to check the parameters and keys + local q = url.parse_query(parsed.query) + -- Check the values (and keys) and see if they are reflected in the page + for k,v in pairs(q) do + if r.response.body and r.response.body:find(v, 1, true) then + dbg("Reflected content %s=%s", k,v) + reflected_values[k] = v + count = count +1 + else + not_reflected_values[k] = v + end + end + if count > 0 then + return reflected_values,not_reflected_values,q + end +end + +local function addPayload(v) + return v.."ghz>hzx\"zxc'xcv" +end + +local function createMinedLinks(reflected_values, all_values) + local new_links = {} + for k,v in pairs(reflected_values) do + -- First of all, add the payload to the reflected param + local urlParams = { [k] = addPayload(v)} + for k2,v2 in pairs(all_values) do + if k2 ~= k then + urlParams[k2] = v2 + end + end + new_links[k] = url.build_query(urlParams) + end + return new_links +end + +local function locatePayloads(response) + local results = {} + if response.body:find("ghz>hzx") then table.insert(results,">") end + if response.body:find('hzx"zxc') then table.insert(results,'"') end + if response.body:find("zxc'xcv") then table.insert(results,"'") end + return #results > 0 and results +end + +local function visitLinks(host, port,parsed,new_links, results,original_url) + for k,query in pairs(new_links) do + local ppath = url.parse_path(parsed.path or "") + local url = url.build_path(ppath) + if parsed.params then url = url .. ";" .. parsed.params end + url = url .. "?" .. query + dbg("Url to visit: %s", url) + local response = http.get(host, port, url) + local result = locatePayloads(response) + if result then + table.insert(results, ("Characters [%s] reflected in parameter %s at %s"):format(table.concat(result," "),k, original_url)) + end + end +end + +action = function(host, port) + + local crawler = httpspider.Crawler:new(host, port, nil, { scriptname = SCRIPT_NAME } ) + crawler:set_timeout(10000) + + local results = {} + while(true) do + local status, r = crawler:crawl() + -- if the crawler fails it can be due to a number of different reasons + -- most of them are "legitimate" and should not be reason to abort + if ( not(status) ) then + if ( r.err ) then + return stdnse.format_output(false, r.reason) + else + break + end + end + + -- parse the returned url + local parsed = url.parse(tostring(r.url)) + -- We are only interested in links which have parameters + if parsed.query and #parsed.query > 0 then + local host, port = getHostPort(parsed) + local reflected_values,not_reflected_values,all_values = getReflected(parsed, r) + + + -- Now,were any reflected ? + if reflected_values then + -- Ok, create new links with payloads in the reflected slots + local new_links = createMinedLinks(reflected_values, all_values) + + -- Now, if we had 2 reflected values, we should have 2 new links to fetch + visitLinks(host, port,parsed, new_links, results,tostring(r.url)) + end + end + end + if ( #results> 0 ) then + return stdnse.format_output(true, results) + end +end |