| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
 100
 101
 102
 103
 104
 105
 106
 107
 108
 109
 110
 111
 112
 113
 114
 115
 116
 117
 118
 119
 120
 121
 122
 123
 124
 125
 126
 127
 128
 129
 130
 131
 132
 133
 134
 135
 136
 137
 138
 139
 140
 141
 142
 143
 144
 145
 146
 147
 148
 149
 150
 151
 152
 153
 154
 155
 156
 157
 158
 159
 160
 161
 162
 163
 164
 165
 166
 167
 168
 169
 170
 171
 172
 173
 174
 175
 176
 177
 178
 179
 180
 181
 182
 183
 184
 185
 186
 187
 188
 189
 190
 
 | local nmap = require "nmap"
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"
local rand = require "rand"
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 <target>
--
-- @output
-- 80/tcp open  http    syn-ack
-- | http-jsonp-detection:
-- | The following JSONP endpoints were detected:
-- |_/rest/contactsjp.php  Completely controllable from URL
--
--
-- @xmloutput
-- <table key='jsonp_endpoints'>
-- <elem>/rest/contactsjp.php</elem>
-- </table>
--
-- @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 = 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 = rand.random_alpha(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
 |