1
2
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
|