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
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
|
local http = require "http"
local httpspider = require "httpspider"
local io = require "io"
local lfs = require "lfs"
local nmap = require "nmap"
local shortport = require "shortport"
local stdnse = require "stdnse"
local string = require "string"
local stringaux = require "stringaux"
local table = require "table"
description = [[The script is used to fetch files from servers.
The script supports three different use cases:
* The paths argument isn't provided, the script spiders the host
and downloads files in their respective folders relative to
the one provided using "destination".
* The paths argument(a single item or list) is provided and the path starts
with "/", the script tries to fetch the path relative to the url
provided via the argument "url".
* The paths argument(a single item or list) is provided and the path doesn't
start with "/". Then the script spiders the host and tries to find
files which contain the path(now treated as a pattern).
]]
---
-- @usage nmap --script http-fetch --script-args destination=/tmp/mirror <target>
-- nmap --script http-fetch --script-args 'paths={/robots.txt,/favicon.ico}' <target>
-- nmap --script http-fetch --script-args 'paths=.html' <target>
-- nmap --script http-fetch --script-args 'url=/images,paths={.jpg,.png,.gif}' <target>
--
-- @args http-fetch.destination - The full path of the directory to save the file(s) to preferably with the trailing slash.
-- @args http-fetch.files - The name of the file(s) to be fetched.
-- @args http-fetch.url The base URL to start fetching. Default: "/"
-- @args http-fetch.paths A list of paths to fetch. If relative, then the site will be spidered to find matching filenames.
-- Otherwise, they will be fetched relative to the url script-arg.
-- @args http-fetch.maxdepth The maximum amount of directories beneath
-- the initial url to spider. A negative value disables the limit.
-- (default: 3)
-- @args http-fetch.maxpagecount The maximum amount of pages to fetch.
-- @args http-fetch.noblacklist By default files like jpg, rar, png are blocked. To
-- fetch such files set noblacklist to true.
-- @args http-fetch.withinhost The default behavior is to fetch files from the same host. Set to False
-- to do otherwise.
-- @args http-fetch.withindomain If set to true then the crawling would be restricted to the domain provided
-- by the user.
--
-- @output
-- | http-fetch:
-- | Successfully Downloaded:
-- | http://scanme.nmap.org:80/ as /tmp/mirror/45.33.32.156/80/index.html
-- |_ http://scanme.nmap.org/shared/css/insecdb.css as /tmp/mirror/45.33.32.156/80/shared/css/insecdb.css
--
-- @xmloutput
-- <table key="Successfully Downloaded">
-- <elem>http://scanme.nmap.org:80/ as /tmp/mirror/45.33.32.156/80/index.html</elem>
-- <elem>http://scanme.nmap.org/shared/css/insecdb.css as /tmp/mirror/45.33.32.156/80/shared/css/insecdb.css</elem>
-- </table>
-- <elem key="result">Successfully Downloaded Everything At: /tmp/mirror/45.33.32.156/80/</elem>
author = "Gyanendra Mishra"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"safe"}
portrule = shortport.http
local SEPARATOR = lfs.get_path_separator()
local function build_path(file, url)
local path = '/' .. url .. file
return path:gsub('//', '/')
end
local function create_directory(path)
local status, err = lfs.mkdir(path)
if status then
stdnse.debug2("Created path %s", path)
return true
elseif err == "No such file or directory" then
stdnse.debug2("Parent directory doesn't exist %s", path)
local index = string.find(path:sub(1, path:len() -1), SEPARATOR .. "[^" .. SEPARATOR .. "]*$")
local sub_path = path:sub(1, index)
stdnse.debug2("Trying path...%s", sub_path)
create_directory(sub_path)
lfs.mkdir(path)
end
end
local function save_file(content, file_name, destination, url)
local file_path
if file_name then
file_path = destination .. file_name
else
file_path = destination .. url:getDir()
create_directory(file_path)
if url:getDir() == url:getFile() then
file_path = file_path .. "index.html"
else
file_path = file_path .. stringaux.filename_escape(url:getFile():gsub(url:getDir(),""))
end
end
file_path = file_path:gsub("//", "/")
file_path = file_path:gsub("\\/", "\\")
local file,err = io.open(file_path,"r")
if not err then
stdnse.debug1("File Already Exists")
return true, file_path
end
file, err = io.open(file_path,"w")
if file then
stdnse.debug1("Saving to ...%s",file_path)
file:write(content)
file:close()
return true, file_path
else
stdnse.debug1("Error encountered in writing file.. %s",err)
return false, err
end
end
local function fetch_recursively(host, port, url, destination, patterns, output)
local crawler = httpspider.Crawler:new(host, port, url, { scriptname = SCRIPT_NAME })
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 body = r.response.body
local url_string = tostring(r.url)
local file = r.url:getFile():gsub(r.url:getDir(),"")
if body and r.response.status == 200 and patterns then
for _, pattern in pairs(patterns) do
if file:find(pattern, nil, true) then
local status, err_message = save_file(r.response.body, nil, destination, r.url)
if status then
output['Matches'] = output['Matches'] or {}
output['Matches'][pattern] = output['Matches'][pattern] or {}
table.insert(output['Matches'][pattern], string.format("%s as %s",r.url:getFile()),err_message)
else
output['ERROR'] = output['ERROR'] or {}
output['ERROR'][url_string] = err_message
end
break
end
end
elseif body and r.response.status == 200 then
stdnse.debug1("Processing url.......%s",url_string)
local stat, path_or_err = save_file(body, nil, destination, r.url)
if stat then
output['Successfully Downloaded'] = output['Successfully Downloaded'] or {}
table.insert(output['Successfully Downloaded'], string.format("%s as %s", url_string, path_or_err))
else
output['ERROR'] = output['ERROR'] or {}
output['ERROR'][url_string] = path_or_err
end
else
if not r.response.body then
stdnse.debug1("No Body For: %s",url_string)
elseif r.response and r.response.status ~= 200 then
stdnse.debug1("Status not 200 For: %s",url_string)
else
stdnse.debug1("False URL picked by spider!: %s",url_string)
end
end
end
end
local function fetch(host, port, url, destination, path, output)
local response = http.get(host, port, build_path(path, url), nil)
if response and response.status and response.status == 200 then
local file = path:sub(path:find("/[^/]*$") + 1)
local save_as = (host.targetname or host.ip) .. SEPARATOR .. tostring(port.number) .. "-" .. file
local status, err_message = save_file(response.body, save_as, destination)
if status then
output['Successfully Downloaded'] = output['Successfully Downloaded'] or {}
table.insert(output['Successfully Downloaded'], string.format("%s as %s", path, save_as))
else
output['ERROR'] = output['ERROR'] or {}
output['ERROR'][path] = err_message
end
else
stdnse.debug1("%s doesn't exist on server at %s.", path, url)
end
end
action = function(host, port)
local destination = stdnse.get_script_args(SCRIPT_NAME..".destination") or false
local url = stdnse.get_script_args(SCRIPT_NAME..".url") or "/"
local paths = stdnse.get_script_args(SCRIPT_NAME..'.paths') or nil
local output = stdnse.output_table()
local patterns = {}
if not destination then
output.ERROR = "Please enter the complete path of the directory to save data in."
return output, output.ERROR
end
local sub_directory = tostring(host.ip) .. SEPARATOR .. tostring(port.number) .. SEPARATOR
if destination:sub(-1) == '\\' or destination:sub(-1) == '/' then
destination = destination .. sub_directory
else
destination = destination .. SEPARATOR .. sub_directory
end
if paths then
if type(paths) ~= 'table' then
paths = {paths}
end
for _, path in pairs(paths) do
if path:sub(1, 1) == "/" then
fetch(host, port, url, destination, path, output)
else
table.insert(patterns, path)
end
end
if #patterns > 0 then
fetch_recursively(host, port, url, destination, patterns, output)
end
else
fetch_recursively(host, port, url, destination, nil, output)
end
if #output > 0 then
if paths then
return output
else
if nmap.verbosity() > 1 then
return output
else
output.result = "Successfully Downloaded Everything At: " .. destination
return output, output.result
end
end
end
end
|