155 lines
4.3 KiB
Plaintext
155 lines
4.3 KiB
Plaintext
|
description = [[
|
||
|
It looks for places where attacker-controlled information in the DOM may be used
|
||
|
to affect JavaScript execution in certain ways. The attack is explained here:
|
||
|
http://www.webappsec.org/projects/articles/071105.shtml
|
||
|
]]
|
||
|
|
||
|
---
|
||
|
-- @usage nmap -p80 --script http-dombased-xss.nse <target>
|
||
|
--
|
||
|
-- DOM-based XSS occur in client-side JavaScript and this script tries to detect
|
||
|
-- them by using some patterns. Please note, that the script may generate some
|
||
|
-- false positives. Don't take everything in the output as a vulnerability, if
|
||
|
-- you don't review it first.
|
||
|
--
|
||
|
-- Most of the patterns used to determine the vulnerable code have been taken
|
||
|
-- from this page: https://code.google.com/p/domxsswiki/wiki/LocationSources
|
||
|
--
|
||
|
-- @args http-dombased-xss.singlepages The pages to test. For example,
|
||
|
-- {/index.php, /profile.php}. Default: nil (crawler mode on)
|
||
|
--
|
||
|
-- @output
|
||
|
-- PORT STATE SERVICE REASON
|
||
|
-- 80/tcp open http syn-ack
|
||
|
-- | http-dombased-xss:
|
||
|
-- | Spidering limited to: maxdepth=3; maxpagecount=20; withinhost=some-very-random-page.com
|
||
|
-- | Found the following indications of potential DOM based XSS:
|
||
|
-- |
|
||
|
-- | Source: document.write("<OPTION value=1>"+document.location.href.substring(document.location.href.indexOf("default=")
|
||
|
-- | Pages: http://some-very-random-page.com:80/, http://some-very-random-page.com/foo.html
|
||
|
-- |
|
||
|
-- | Source: document.write(document.URL.substring(pos,document.URL.length)
|
||
|
-- |_ Pages: http://some-very-random-page.com/foo.html
|
||
|
--
|
||
|
-- @see http-stored-xss.nse
|
||
|
-- @see http-phpself-xss.nse
|
||
|
-- @see http-xssed.nse
|
||
|
-- @see http-unsafe-output-escaping.nse
|
||
|
---
|
||
|
|
||
|
categories = {"intrusive", "exploit", "vuln"}
|
||
|
author = "George Chatzisofroniou"
|
||
|
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
|
||
|
|
||
|
local http = require "http"
|
||
|
local shortport = require "shortport"
|
||
|
local stdnse = require "stdnse"
|
||
|
local table = require "table"
|
||
|
local string = require "string"
|
||
|
local httpspider = require "httpspider"
|
||
|
|
||
|
JS_FUNC_PATTERNS = {
|
||
|
'(document%.write%s*%((.-)%))',
|
||
|
'(document%.writeln%s*%((.-)%))',
|
||
|
'(document%.execCommand%s*%((.-)%))',
|
||
|
'(document%.open%s*%((.-)%))',
|
||
|
'(window%.open%s*%((.-)%))',
|
||
|
'(eval%s*%((.-)%))',
|
||
|
'(window%.execScript%s*%((.-)%))',
|
||
|
}
|
||
|
|
||
|
JS_CALLS_PATTERNS = {
|
||
|
'document%.URL',
|
||
|
'document%.documentURI',
|
||
|
'document%.URLUnencoded',
|
||
|
'document%.baseURI',
|
||
|
'document%.referrer',
|
||
|
'location',
|
||
|
}
|
||
|
|
||
|
portrule = shortport.port_or_service( {80, 443}, {"http", "https"}, "tcp", "open")
|
||
|
|
||
|
action = function(host, port)
|
||
|
|
||
|
local singlepages = stdnse.get_script_args("http-dombased-xss.singlepages")
|
||
|
|
||
|
local domxss = {}
|
||
|
|
||
|
local crawler = httpspider.Crawler:new( host, port, '/', { scriptname = SCRIPT_NAME, withinhost = 1 } )
|
||
|
|
||
|
if (not(crawler)) then
|
||
|
return
|
||
|
end
|
||
|
|
||
|
crawler:set_timeout(10000)
|
||
|
|
||
|
local index, k, target, response, path
|
||
|
while (true) do
|
||
|
|
||
|
if singlepages then
|
||
|
k, target = next(singlepages, index)
|
||
|
if (k == nil) then
|
||
|
break
|
||
|
end
|
||
|
response = http.get(host, port, target)
|
||
|
path = target
|
||
|
|
||
|
else
|
||
|
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
|
||
|
|
||
|
response = r.response
|
||
|
path = tostring(r.url)
|
||
|
end
|
||
|
|
||
|
if response.body then
|
||
|
|
||
|
for _, fp in ipairs(JS_FUNC_PATTERNS) do
|
||
|
for i in string.gmatch(response.body, fp) do
|
||
|
for _, cp in ipairs(JS_CALLS_PATTERNS) do
|
||
|
if string.find(i, cp) then
|
||
|
if not domxss[i] then
|
||
|
domxss[i] = {path}
|
||
|
else
|
||
|
table.insert(domxss[i], ", " .. path)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
if (index) then
|
||
|
index = index + 1
|
||
|
else
|
||
|
index = 1
|
||
|
end
|
||
|
end
|
||
|
|
||
|
end
|
||
|
|
||
|
-- If the table is empty.
|
||
|
if next(domxss) == nil then
|
||
|
return "Couldn't find any DOM based XSS."
|
||
|
end
|
||
|
|
||
|
local results = {}
|
||
|
for x, _ in pairs(domxss) do
|
||
|
table.insert(results, { "\nSource: " .. x, "Pages: " .. table.concat(_) })
|
||
|
end
|
||
|
|
||
|
table.insert(results, 1, "Found the following indications of potential DOM based XSS: ")
|
||
|
|
||
|
results.name = crawler:getLimitations()
|
||
|
|
||
|
return stdnse.format_output(true, results)
|
||
|
|
||
|
end
|