From 995988ea8c1968bb566680def8297691db97565d Mon Sep 17 00:00:00 2001 From: vinamra Date: Mon, 7 Aug 2017 18:16:44 +0000 Subject: [PATCH] Adds http-jsonp-detection. Closes #937 --- CHANGELOG | 4 + scripts/http-jsonp-detection.nse | 189 +++++++++++++++++++++++++++++++ scripts/script.db | 1 + 3 files changed, 194 insertions(+) create mode 100755 scripts/http-jsonp-detection.nse diff --git a/CHANGELOG b/CHANGELOG index 91e1a79d8..9eb4ff7b3 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,9 @@ # Nmap Changelog ($Id$); -*-text-*- +o [NSE] New script http-jsonp-detection Attempts to discover JSONP endpoints in + web servers. JSONP endpoints can be used to bypass Same-origin Policy + restrictions in web browsers. [Vinamra Bhatia] + o Nmap can now resolve and scan all IP addresses of a host. Instead of using the resolveall NSE script, append "*all" to a target hostname: nmap example.com*all another.example.com*all/24 diff --git a/scripts/http-jsonp-detection.nse b/scripts/http-jsonp-detection.nse new file mode 100755 index 000000000..b62ee2959 --- /dev/null +++ b/scripts/http-jsonp-detection.nse @@ -0,0 +1,189 @@ +local http = require "http" +local shortport = require "shortport" +local stdnse = require "stdnse" +local string = require "string" +local json = require "json" +local url = require "url" +local httpspider = require "httpspider" +local table = require "table" + +description = [[ +Attempts to discover JSONP endpoints in web servers. JSONP endpoints can be +used to bypass Same-origin Policy restrictions in web browsers. + +The script searches for callback functions in the response to detect JSONP +endpoints. It also tries to determine callback function through URL(callback +function may be fully or partially controllable from URL) and also tries to +bruteforce the most common callback variables through the URL. + +References : https://securitycafe.ro/2017/01/18/practical-jsonp-injection/ + +]] + +--- +-- @usage +-- nmap -p 80 --script http-jsonp-detection +-- +-- @output +-- 80/tcp open http syn-ack +-- | http-jsonp-detection: +-- | The following JSONP endpoints were detected: +-- |_/rest/contactsjp.php Completely controllable from URL +-- +-- +-- @xmloutput +-- +-- /rest/contactsjp.php +--
+-- +-- @args http-jsonp-detection.path The URL path to request. The default path is "/". +--- + +author = {"Vinamra Bhatia"} +license = "Same as Nmap--See https://nmap.org/book/man-legal.html" +categories = {"safe", "vuln", "discovery"} + +portrule = shortport.http + +local callbacks = {"callback", "cb", "jsonp", "jsonpcallback", "jcb", "call"} + +--Checks the body and returns if valid json data is present in callback function +local checkjson = function(body) + + local func, json_data + _, _, _, func, json_data = string.find(body, "^(%S-)([%w_]+)%((.*)%);?$") + + --Check if the json_data is valid + --If valid, we have a JSONP endpoint with func as the function name + + local status, json = json.parse(json_data) + return status, func + +end + +--Checks if the callback function is controllable from URL +local callback_url = function(host, port, target, callback_variable) + local path, response, report + local value = stdnse.generate_random_string(8) + if callback_variable == nil then + callback_variable = "callback" + end + path = target .. "?" .. callback_variable .. "=" .. value + response = http.get(host, port, path) + if response and response.body and response.status and response.status==200 then + + local status, func + status, func = checkjson(response.body) + + if status == true then + if func == value then + report = "Completely controllable from URL" + else + local p = string.find(func, value) + if p then + report = "Partially controllable from URL" + end + end + end + end + return report +end + +--The function tries to bruteforce through the most common callback variable +local callback_bruteforce = function(host, port, target) + local response, path, report + for _,p in ipairs(callbacks) do + path = target + path = path .. "?" .. p .. "=test" + response = http.get(host, port, path) + if response and response.body and response.status and response.status==200 then + + local status, func + status, func = checkjson(response.body) + + if status == true then + report = callback_url(host, port, target, p) + if report ~= nil then + report = string.format("%s\t%s", target, report) + else + report = target + end + break + end + end + end + return report +end + +action = function(host, port) + local path = stdnse.get_script_args(SCRIPT_NAME .. ".path") or "/" + local output_xml = stdnse.output_table() + output_xml = {} + output_xml['jsonp-endpoints'] = {} + local output_str = "\nThe following JSONP endpoints were detected: " + + -- crawl to find jsonp endpoints urls + local crawler = httpspider.Crawler:new(host, port, path, {scriptname = SCRIPT_NAME}) + + if (not(crawler)) then + return + end + + 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 target = tostring(r.url) + target = url.parse(target) + target = target.path + + -- First we try to get the response and look for jsonp endpoint there + if r.response and r.response.body and r.response.status and r.response.status==200 then + + local status, func, report + status, func = checkjson(r.response.body) + + if status == true then + --We have found JSONP endpoint + --Put it inside a returnable table. + output_str = string.format("%s\n%s", output_str, target) + table.insert(output_xml['jsonp-endpoints'], target) + + --Try if the callback function is controllable from URL. + report = callback_url(host, port, target) + if report ~= nil then + output_str = string.format("%s\t%s", output_str, report) + end + + else + + --Try to bruteforce through most comman callback URLs + report = callback_bruteforce(host, port, target) + if report ~= nil then + table.insert(output_xml['jsonp-endpoints'], target) + output_str = string.format("%s\n%s", output_str, report) + end + end + + end + + end + + --A way to print returnable + if next(output_xml['jsonp-endpoints']) then + return output_xml, output_str + else + if nmap.verbosity() > 1 then + return "Couldn't find any JSONP endpoints." + end + end + +end \ No newline at end of file diff --git a/scripts/script.db b/scripts/script.db index 8ddd83eda..9f1d45020 100644 --- a/scripts/script.db +++ b/scripts/script.db @@ -201,6 +201,7 @@ Entry { filename = "http-iis-short-name-brute.nse", categories = { "brute", "int Entry { filename = "http-iis-webdav-vuln.nse", categories = { "intrusive", "vuln", } } Entry { filename = "http-internal-ip-disclosure.nse", categories = { "discovery", "safe", "vuln", } } Entry { filename = "http-joomla-brute.nse", categories = { "brute", "intrusive", } } +Entry { filename = "http-jsonp-detection.nse", categories = { "discovery", "safe", "vuln", } } Entry { filename = "http-litespeed-sourcecode-download.nse", categories = { "exploit", "intrusive", "vuln", } } Entry { filename = "http-ls.nse", categories = { "default", "discovery", "safe", } } Entry { filename = "http-majordomo2-dir-traversal.nse", categories = { "exploit", "intrusive", "vuln", } }