mirror of
https://github.com/nmap/nmap.git
synced 2025-12-06 04:31:29 +00:00
[NSE] Added http-stored-xss that identifies potential stored XSS vulnerabilities.
This commit is contained in:
281
scripts/http-stored-xss.nse
Normal file
281
scripts/http-stored-xss.nse
Normal file
@@ -0,0 +1,281 @@
|
||||
description = [[
|
||||
This script will POST specially crafted strings to every form it
|
||||
encounters and then it will search through the website for those
|
||||
strings to check if the payloads were succesful.
|
||||
]]
|
||||
|
||||
---
|
||||
-- @usage nmap -p80 --script http-stored-xss.nse <target>
|
||||
--
|
||||
-- This script works in two phases.
|
||||
-- 1) Posts specially crafted strings to every form it encounters.
|
||||
-- 2) Crawls through the page searching for these strings.
|
||||
--
|
||||
-- If any string is reflected on some page without any proper
|
||||
-- HTML escaping, it's a sign for potential XSS vulnerability.
|
||||
--
|
||||
-- @args http-fileupload-exploiter.formpaths The pages that contain
|
||||
-- the forms to exploit. For example, {/upload.php, /login.php}.
|
||||
-- Default: nil (crawler mode on)
|
||||
-- @args http-fileupload-exploiter.uploadspaths The pages that reflect
|
||||
-- back POSTed data. For example, {/comments.php, /guestbook.php}.
|
||||
-- Default: nil (Crawler mode on)
|
||||
-- @args http-fileupload-exploiter.fieldvalues The script will try to
|
||||
-- fill every field found in the form but that may fail due to
|
||||
-- fields' restrictions. You can manually fill those fields using
|
||||
-- this table. For example, {gender = "male", email = "foo@bar.com"}.
|
||||
-- Default: {}
|
||||
-- @args http-fileupload-exploiter.filedb The path of a plain text file
|
||||
-- that contains one XSS vector per line. Default: nil
|
||||
--
|
||||
-- @output
|
||||
-- PORT STATE SERVICE REASON
|
||||
-- 80/tcp open http syn-ack
|
||||
-- | http-stored-xss:
|
||||
-- | Found the following stored XSS vulnerabilities:
|
||||
-- |
|
||||
-- | Payload: ghz>hzx
|
||||
-- | Uploaded on: /guestbook.php
|
||||
-- | Description: Unfiltered '>' (greater than sign). An indication of potential XSS vulnerability.
|
||||
-- | Payload: zxc'xcv
|
||||
-- | Uploaded on: /guestbook.php
|
||||
-- | Description: Unfiltered ' (apostrophe). An indication of potential XSS vulnerability.
|
||||
-- |
|
||||
-- | Payload: ghz>hzx
|
||||
-- | Uploaded on: /posts.php
|
||||
-- | Description: Unfiltered '>' (greater than sign). An indication of potential XSS vulnerability.
|
||||
-- | Payload: hzx"zxc
|
||||
-- | Uploaded on: /posts.php
|
||||
-- |_ Description: Unfiltered " (double quotation mark). An indication of potential XSS vulnerability.
|
||||
--
|
||||
--
|
||||
--
|
||||
---
|
||||
|
||||
categories = {"intrusive", "exploit", "vuln"}
|
||||
author = "George Chatzisofroniou"
|
||||
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
|
||||
|
||||
local http = require "http"
|
||||
local string = require "string"
|
||||
local httpspider = require "httpspider"
|
||||
local shortport = require "shortport"
|
||||
local stdnse = require "stdnse"
|
||||
local table = require "table"
|
||||
|
||||
portrule = shortport.port_or_service( {80, 443}, {"http", "https"}, "tcp", "open")
|
||||
|
||||
|
||||
-- A list of payloads.
|
||||
--
|
||||
-- You can manually add / remove your own payloads but make sure you
|
||||
-- don't mess up, otherwise the script may succeed when it actually
|
||||
-- hasn't.
|
||||
--
|
||||
-- Note, that more payloads will slow down your scan.
|
||||
payloads = {
|
||||
|
||||
-- Basic vectors. Each one is an indication of potential XSS vulnerability.
|
||||
{ vector = 'ghz>hzx', description = "Unfiltered '>' (greater than sign). An indication of potential XSS vulnerability." },
|
||||
{ vector = 'hzx"zxc', description = "Unfiltered \" (double quotation mark). An indication of potential XSS vulnerability." },
|
||||
{ vector = 'zxc\'xcv', description = "Unfiltered ' (apostrophe). An indication of potential XSS vulnerability." },
|
||||
}
|
||||
|
||||
|
||||
-- Create customized requests for all of our payloads.
|
||||
local makeRequests = function(host, port, submission, fields, fieldvalues)
|
||||
|
||||
local postdata = {}
|
||||
for _, p in ipairs(payloads) do
|
||||
for __, field in ipairs(fields) do
|
||||
if field["type"] == "text" or field["type"] == "textarea" or field["type"] == "radio" or field["type"] == "checkbox" then
|
||||
|
||||
if fieldvalues[field["name"]] ~= nil then
|
||||
value = fieldvalues[field["name"]]
|
||||
else
|
||||
value = p.vector
|
||||
end
|
||||
|
||||
postdata[field["name"]] = value
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
stdnse.print_debug(2, "Making a POST request to " .. submission .. ": ")
|
||||
for i, content in pairs(postdata) do
|
||||
stdnse.print_debug(2, i .. ": " .. content)
|
||||
end
|
||||
local response = http.post(host, port, submission, { no_cache = true }, nil, postdata)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
local checkPayload = function(body, p)
|
||||
|
||||
if (body:match(p)) then
|
||||
return true
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- Check if the payloads were succesfull by checking the content of pages in the uploadspaths array.
|
||||
local checkRequests = function(body, target)
|
||||
|
||||
output = {}
|
||||
for _, p in ipairs(payloads) do
|
||||
if checkPayload(body, p.vector) then
|
||||
report = " Payload: " .. p.vector .. "\n\t Uploaded on: " .. target
|
||||
if p.description then
|
||||
report = report .. "\n\t Description: " .. p.description
|
||||
end
|
||||
table.insert(output, report)
|
||||
end
|
||||
end
|
||||
return output
|
||||
end
|
||||
|
||||
local readFromFile = function(filename)
|
||||
local database = { }
|
||||
for l in io.lines(filename) do
|
||||
table.insert(payloads, { vector = l })
|
||||
end
|
||||
end
|
||||
|
||||
action = function(host, port)
|
||||
|
||||
local formpaths = stdnse.get_script_args("http-stored-xss.formpaths")
|
||||
local uploadspaths = stdnse.get_script_args("http-stored-xss.uploadspaths")
|
||||
local fieldvalues = stdnse.get_script_args("http-stored-xss.fieldvalues") or {}
|
||||
local dbfile = stdnse.get_script_args("http-stored-xss.dbfile")
|
||||
|
||||
if dbfile then
|
||||
readFromFile(dbfile)
|
||||
end
|
||||
|
||||
local returntable = {}
|
||||
local result
|
||||
|
||||
local crawler = httpspider.Crawler:new( host, port, '/', { scriptname = SCRIPT_NAME, no_cache = true } )
|
||||
|
||||
if (not(crawler)) then
|
||||
return
|
||||
end
|
||||
|
||||
crawler:set_timeout(10000)
|
||||
|
||||
local index, k, target, response
|
||||
|
||||
-- Phase 1. Crawls through the website and POSTs malicious payloads.
|
||||
while (true) do
|
||||
|
||||
if formpaths then
|
||||
|
||||
k, target = next(formpaths, index)
|
||||
if (k == nil) then
|
||||
break
|
||||
end
|
||||
response = http.get(host, port, target, { no_cache = true })
|
||||
target = host.name .. 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(true, ("ERROR: %s"):format(r.reason))
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
target = tostring(r.url)
|
||||
response = r.response
|
||||
|
||||
end
|
||||
|
||||
if response.body then
|
||||
|
||||
local forms = http.grab_forms(response.body)
|
||||
|
||||
for i, form in ipairs(forms) do
|
||||
|
||||
form = http.parse_form(form)
|
||||
|
||||
if form then
|
||||
|
||||
local action_absolute = string.find(form["action"], "https*://")
|
||||
|
||||
-- Determine the path where the form needs to be submitted.
|
||||
if action_absolute then
|
||||
submission = form["action"]
|
||||
else
|
||||
local path_cropped = string.match(target, "(.*/).*")
|
||||
path_cropped = path_cropped and path_cropped or ""
|
||||
submission = path_cropped..form["action"]
|
||||
end
|
||||
|
||||
makeRequests(host, port, submission, form["fields"], fieldvalues)
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
if (index) then
|
||||
index = index + 1
|
||||
else
|
||||
index = 1
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
local crawler = httpspider.Crawler:new( host, port, '/', { scriptname = SCRIPT_NAME } )
|
||||
local index
|
||||
|
||||
-- Phase 2. Crawls through the website and searches for the special crafted strings that were POSTed before.
|
||||
while true do
|
||||
if uploadspaths then
|
||||
k, target = next(uploadspaths, index)
|
||||
if (k == nil) then
|
||||
break
|
||||
end
|
||||
response = http.get(host, port, 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(true, ("ERROR: %s"):format(r.reason))
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
target = tostring(r.url)
|
||||
response = r.response
|
||||
|
||||
end
|
||||
|
||||
if response.body then
|
||||
|
||||
result = checkRequests(response.body, target)
|
||||
|
||||
if next(result) then
|
||||
table.insert(returntable, result)
|
||||
end
|
||||
end
|
||||
if (index) then
|
||||
index = index + 1
|
||||
else
|
||||
index = 1
|
||||
end
|
||||
end
|
||||
|
||||
if next(returntable) then
|
||||
table.insert(returntable, 1, "Found the following stored XSS vulnerabilities: ")
|
||||
return returntable
|
||||
else
|
||||
return "Couldn't find any stored XSS vulnerabilities."
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user