ssrf_proxy 0.0.2 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +15 -0
- data/LICENSE.md +22 -0
- data/README.md +222 -0
- data/bin/console +24 -0
- data/bin/setup +6 -0
- data/bin/ssrf-proxy +170 -153
- data/lib/ssrf_proxy/http.rb +911 -1227
- data/lib/ssrf_proxy/server.rb +298 -118
- data/lib/ssrf_proxy/version.rb +12 -4
- data/lib/ssrf_proxy.rb +37 -10
- metadata +162 -39
- data/bin/ssrf-scan +0 -452
data/lib/ssrf_proxy/http.rb
CHANGED
@@ -1,1306 +1,990 @@
|
|
1
|
-
|
1
|
+
# coding: utf-8
|
2
2
|
#
|
3
|
-
# Copyright (c) 2015 Brendan Coles <bcoles@gmail.com>
|
3
|
+
# Copyright (c) 2015-2016 Brendan Coles <bcoles@gmail.com>
|
4
4
|
# SSRF Proxy - https://github.com/bcoles/ssrf_proxy
|
5
|
-
# See the file 'LICENSE' for copying permission
|
5
|
+
# See the file 'LICENSE.md' for copying permission
|
6
6
|
#
|
7
7
|
|
8
|
-
require "ssrf_proxy"
|
9
|
-
|
10
8
|
module SSRFProxy
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
#
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
#
|
23
|
-
|
24
|
-
|
25
|
-
|
9
|
+
#
|
10
|
+
# SSRFProxy::HTTP object takes information required to connect
|
11
|
+
# to a HTTP server vulnerable to SSRF and issue arbitrary HTTP
|
12
|
+
# requests via the SSRF.
|
13
|
+
#
|
14
|
+
# Once configured, the #send_uri method can be used to tunnel
|
15
|
+
# HTTP requests through the server.
|
16
|
+
#
|
17
|
+
# Several request modification options can be used to format
|
18
|
+
# the HTTP request appropriately for the SSRF vector and
|
19
|
+
# the destination web server accessed via the SSRF.
|
20
|
+
#
|
21
|
+
# Several response modification options can be used to infer
|
22
|
+
# information about the response from the destination server
|
23
|
+
# and format the response such that the vulnerable intermediary
|
24
|
+
# server is mostly transparent to the client initiating the
|
25
|
+
# HTTP request.
|
26
|
+
#
|
27
|
+
# Refer to the wiki for more information about configuring the
|
28
|
+
# SSRF, requestion modification, and response modification:
|
29
|
+
# https://github.com/bcoles/ssrf_proxy/wiki/Configuration
|
30
|
+
#
|
31
|
+
class HTTP
|
32
|
+
#
|
33
|
+
# SSRFProxy::HTTP errors
|
34
|
+
#
|
35
|
+
module Error
|
36
|
+
# SSRFProxy::HTTP custom errors
|
37
|
+
class Error < StandardError; end
|
38
|
+
exceptions = %w(
|
39
|
+
NoUrlPlaceholder
|
40
|
+
InvalidSsrfRequest
|
41
|
+
InvalidSsrfRequestMethod
|
42
|
+
InvalidUpstreamProxy
|
43
|
+
InvalidIpEncoding
|
44
|
+
InvalidClientRequest
|
45
|
+
ConnectionTimeout )
|
46
|
+
exceptions.each { |e| const_set(e, Class.new(Error)) }
|
47
|
+
end
|
26
48
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
49
|
+
#
|
50
|
+
# SSRFProxy::HTTP accepts SSRF connection information,
|
51
|
+
# and configuration options for request modificaiton
|
52
|
+
# and response modification.
|
53
|
+
#
|
54
|
+
# @param [String] url SSRF URL with 'xxURLxx' placeholder
|
55
|
+
# @param [Hash] opts SSRF and HTTP connection options:
|
56
|
+
# @option opts [String] proxy
|
57
|
+
# @option opts [String] method
|
58
|
+
# @option opts [String] post_data
|
59
|
+
# @option opts [String] rules
|
60
|
+
# @option opts [String] ip_encoding
|
61
|
+
# @option opts [Regex] match
|
62
|
+
# @option opts [String] strip
|
63
|
+
# @option opts [Boolean] decode_html
|
64
|
+
# @option opts [Boolean] guess_status
|
65
|
+
# @option opts [Boolean] guess_mime
|
66
|
+
# @option opts [Boolean] ask_password
|
67
|
+
# @option opts [Boolean] forward_cookies
|
68
|
+
# @option opts [Boolean] body_to_uri
|
69
|
+
# @option opts [Boolean] auth_to_uri
|
70
|
+
# @option opts [Boolean] cookies_to_uri
|
71
|
+
# @option opts [String] cookie
|
72
|
+
# @option opts [Integer] timeout
|
73
|
+
# @option opts [String] user_agent
|
74
|
+
# @option opts [Boolean] insecure
|
75
|
+
#
|
76
|
+
# @example SSRF with default options
|
77
|
+
# SSRFProxy::HTTP.new('http://example.local/index.php?url=xxURLxx')
|
78
|
+
#
|
79
|
+
# @raise [SSRFProxy::HTTP::Error::InvalidSsrfRequest]
|
80
|
+
# Invalid SSRF request specified.
|
81
|
+
#
|
82
|
+
def initialize(url = '', opts = {})
|
83
|
+
@detect_waf = true
|
84
|
+
@logger = ::Logger.new(STDOUT).tap do |log|
|
85
|
+
log.progname = 'ssrf-proxy'
|
86
|
+
log.level = ::Logger::WARN
|
87
|
+
log.datetime_format = '%Y-%m-%d %H:%M:%S '
|
88
|
+
end
|
89
|
+
begin
|
90
|
+
@ssrf_url = URI.parse(url.to_s)
|
91
|
+
rescue URI::InvalidURIError
|
92
|
+
raise SSRFProxy::HTTP::Error::InvalidSsrfRequest.new,
|
93
|
+
'Invalid SSRF request specified.'
|
94
|
+
end
|
95
|
+
if @ssrf_url.scheme.nil? || @ssrf_url.host.nil? || @ssrf_url.port.nil?
|
96
|
+
raise SSRFProxy::HTTP::Error::InvalidSsrfRequest.new,
|
97
|
+
'Invalid SSRF request specified.'
|
98
|
+
end
|
99
|
+
if @ssrf_url.scheme !~ /\Ahttps?\z/
|
100
|
+
raise SSRFProxy::HTTP::Error::InvalidSsrfRequest.new,
|
101
|
+
'Invalid SSRF request specified. Scheme must be http(s).'
|
102
|
+
end
|
103
|
+
parse_options(opts)
|
104
|
+
end
|
31
105
|
|
32
|
-
|
33
|
-
|
34
|
-
|
106
|
+
#
|
107
|
+
# Parse initialization configuration options
|
108
|
+
#
|
109
|
+
# @param [Hash] opts Options for SSRF and HTTP connection options
|
110
|
+
#
|
111
|
+
# @raise [SSRFProxy::HTTP::Error::InvalidUpstreamProxy]
|
112
|
+
# Invalid upstream proxy specified.
|
113
|
+
# @raise [SSRFProxy::HTTP::Error::InvalidSsrfRequestMethod]
|
114
|
+
# Invalid SSRF request method specified.
|
115
|
+
# Method must be GET/HEAD/DELETE/POST/PUT.
|
116
|
+
# @raise [SSRFProxy::HTTP::Error::NoUrlPlaceholder]
|
117
|
+
# 'xxURLxx' URL placeholder must be specified in the
|
118
|
+
# SSRF request URL or body.
|
119
|
+
# @raise [SSRFProxy::HTTP::Error::InvalidIpEncoding]
|
120
|
+
# Invalid IP encoding method specified.
|
121
|
+
#
|
122
|
+
def parse_options(opts = {})
|
123
|
+
# SSRF configuration options
|
124
|
+
@upstream_proxy = nil
|
125
|
+
@method = 'GET'
|
126
|
+
@post_data = ''
|
127
|
+
@rules = []
|
128
|
+
opts.each do |option, value|
|
129
|
+
next if value.eql?('')
|
130
|
+
case option
|
131
|
+
when 'proxy'
|
132
|
+
begin
|
133
|
+
@upstream_proxy = URI.parse(value)
|
134
|
+
rescue URI::InvalidURIError
|
135
|
+
raise SSRFProxy::HTTP::Error::InvalidUpstreamProxy.new,
|
136
|
+
'Invalid upstream proxy specified.'
|
137
|
+
end
|
138
|
+
if @upstream_proxy.host.nil? || @upstream_proxy.port.nil?
|
139
|
+
raise SSRFProxy::HTTP::Error::InvalidUpstreamProxy.new,
|
140
|
+
'Invalid upstream proxy specified.'
|
141
|
+
end
|
142
|
+
if @upstream_proxy.scheme !~ /\A(socks|https?)\z/
|
143
|
+
raise SSRFProxy::HTTP::Error::InvalidUpstreamProxy.new,
|
144
|
+
'Unsupported upstream proxy specified. Scheme must be http(s) or socks.'
|
145
|
+
end
|
146
|
+
when 'method'
|
147
|
+
case value.to_s.downcase
|
148
|
+
when 'get'
|
149
|
+
@method = 'GET'
|
150
|
+
when 'head'
|
151
|
+
@method = 'HEAD'
|
152
|
+
when 'delete'
|
153
|
+
@method = 'DELETE'
|
154
|
+
when 'post'
|
155
|
+
@method = 'POST'
|
156
|
+
when 'put'
|
157
|
+
@method = 'PUT'
|
158
|
+
else
|
159
|
+
raise SSRFProxy::HTTP::Error::InvalidSsrfRequestMethod.new,
|
160
|
+
'Invalid SSRF request method specified. Method must be GET/HEAD/DELETE/POST/PUT.'
|
161
|
+
end
|
162
|
+
when 'post_data'
|
163
|
+
@post_data = value.to_s
|
164
|
+
when 'rules'
|
165
|
+
@rules = value.to_s.split(/,/)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
if @ssrf_url.request_uri !~ /xxURLxx/ && @post_data.to_s !~ /xxURLxx/
|
169
|
+
raise SSRFProxy::HTTP::Error::NoUrlPlaceholder.new,
|
170
|
+
"You must specify a URL placeholder with 'xxURLxx' in the SSRF request"
|
171
|
+
end
|
35
172
|
|
36
|
-
|
37
|
-
|
38
|
-
|
173
|
+
# client request modification
|
174
|
+
@ip_encoding = nil
|
175
|
+
@forward_cookies = false
|
176
|
+
@body_to_uri = false
|
177
|
+
@auth_to_uri = false
|
178
|
+
@cookies_to_uri = false
|
179
|
+
opts.each do |option, value|
|
180
|
+
next if value.eql?('')
|
181
|
+
case option
|
182
|
+
when 'ip_encoding'
|
183
|
+
if value.to_s !~ /\A[a-z0-9_]+\z/i
|
184
|
+
raise SSRFProxy::HTTP::Error::InvalidIpEncoding.new,
|
185
|
+
'Invalid IP encoding method specified.'
|
186
|
+
end
|
187
|
+
@ip_encoding = value.to_s
|
188
|
+
when 'forward_cookies'
|
189
|
+
@forward_cookies = true if value
|
190
|
+
when 'body_to_uri'
|
191
|
+
@body_to_uri = true if value
|
192
|
+
when 'auth_to_uri'
|
193
|
+
@auth_to_uri = true if value
|
194
|
+
when 'cookies_to_uri'
|
195
|
+
@cookies_to_uri = true if value
|
196
|
+
end
|
197
|
+
end
|
39
198
|
|
40
|
-
|
41
|
-
|
199
|
+
# SSRF connection options
|
200
|
+
@cookie = nil
|
201
|
+
@timeout = 10
|
202
|
+
@user_agent = 'Mozilla/5.0'
|
203
|
+
@insecure = false
|
204
|
+
opts.each do |option, value|
|
205
|
+
next if value.eql?('')
|
206
|
+
case option
|
207
|
+
when 'cookie'
|
208
|
+
@cookie = value.to_s
|
209
|
+
when 'timeout'
|
210
|
+
@timeout = value.to_i
|
211
|
+
when 'user_agent'
|
212
|
+
@user_agent = value.to_s
|
213
|
+
when 'insecure'
|
214
|
+
@insecure = true if value
|
215
|
+
end
|
216
|
+
end
|
42
217
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
218
|
+
# HTTP response modification options
|
219
|
+
@match_regex = '\\A(.*)\\z'
|
220
|
+
@strip = []
|
221
|
+
@decode_html = false
|
222
|
+
@guess_status = false
|
223
|
+
@guess_mime = false
|
224
|
+
@ask_password = false
|
225
|
+
opts.each do |option, value|
|
226
|
+
next if value.eql?('')
|
227
|
+
case option
|
228
|
+
when 'match'
|
229
|
+
@match_regex = value.to_s
|
230
|
+
when 'strip'
|
231
|
+
@strip = value.to_s.split(/,/)
|
232
|
+
when 'decode_html'
|
233
|
+
@decode_html = true if value
|
234
|
+
when 'guess_status'
|
235
|
+
@guess_status = true if value
|
236
|
+
when 'guess_mime'
|
237
|
+
@guess_mime = true if value
|
238
|
+
when 'ask_password'
|
239
|
+
@ask_password = true if value
|
240
|
+
end
|
241
|
+
end
|
49
242
|
end
|
50
|
-
end
|
51
243
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
#
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
InvalidSsrfRequest
|
61
|
-
InvalidRequestMethod
|
62
|
-
InvalidUpstreamProxy
|
63
|
-
InvalidIpEncoding
|
64
|
-
InvalidHttpRequest
|
65
|
-
InvalidUriRequest )
|
66
|
-
exceptions.each { |e| const_set(e, Class.new(Error)) }
|
67
|
-
end
|
244
|
+
#
|
245
|
+
# Logger accessor
|
246
|
+
#
|
247
|
+
# @return [Logger] class logger object
|
248
|
+
#
|
249
|
+
def logger
|
250
|
+
@logger
|
251
|
+
end
|
68
252
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
# - 'method' => String
|
77
|
-
# - 'post_data' => String
|
78
|
-
# - 'rules' => String
|
79
|
-
# - 'ip_encoding' => String
|
80
|
-
# - 'match' => Regex
|
81
|
-
# - 'strip' => String
|
82
|
-
# - 'guess_status' => Boolean
|
83
|
-
# - 'guess_mime' => Boolean
|
84
|
-
# - 'forward_cookies'=> Boolean
|
85
|
-
# - 'body_to_uri' => Boolean
|
86
|
-
# - 'auth_to_uri' => Boolean
|
87
|
-
# - 'cookie' => String
|
88
|
-
# - 'timeout' => Integer
|
89
|
-
# - 'user_agent' => String
|
90
|
-
#
|
91
|
-
def initialize(url='', opts={})
|
92
|
-
@logger = ::Logger.new(STDOUT).tap do |log|
|
93
|
-
log.progname = 'ssrf-proxy'
|
94
|
-
log.level = ::Logger::WARN
|
95
|
-
log.datetime_format = '%Y-%m-%d %H:%M:%S '
|
253
|
+
#
|
254
|
+
# URL accessor
|
255
|
+
#
|
256
|
+
# @return [String] SSRF URL
|
257
|
+
#
|
258
|
+
def url
|
259
|
+
@ssrf_url
|
96
260
|
end
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
261
|
+
|
262
|
+
#
|
263
|
+
# Host accessor
|
264
|
+
#
|
265
|
+
# @return [String] SSRF host
|
266
|
+
#
|
267
|
+
def host
|
268
|
+
@ssrf_url.host
|
102
269
|
end
|
103
|
-
|
104
|
-
|
105
|
-
|
270
|
+
|
271
|
+
#
|
272
|
+
# Port accessor
|
273
|
+
#
|
274
|
+
# @return [String] SSRF host port
|
275
|
+
#
|
276
|
+
def port
|
277
|
+
@ssrf_url.port
|
106
278
|
end
|
107
|
-
|
108
|
-
|
109
|
-
|
279
|
+
|
280
|
+
#
|
281
|
+
# Upstream proxy accessor
|
282
|
+
#
|
283
|
+
# @return [URI] upstream HTTP proxy
|
284
|
+
#
|
285
|
+
def proxy
|
286
|
+
@upstream_proxy
|
110
287
|
end
|
111
288
|
|
112
|
-
#
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
@
|
117
|
-
|
118
|
-
@
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
289
|
+
#
|
290
|
+
# Parse a HTTP request as a string, then send the requested URL
|
291
|
+
# and HTTP headers to send_uri
|
292
|
+
#
|
293
|
+
# @param [String] request raw HTTP request
|
294
|
+
#
|
295
|
+
# @raise [SSRFProxy::HTTP::Error::InvalidClientRequest]
|
296
|
+
# An invalid client HTTP request was supplied.
|
297
|
+
#
|
298
|
+
# @return [Hash] HTTP response hash (version, code, message, headers, body, etc)
|
299
|
+
#
|
300
|
+
def send_request(request)
|
301
|
+
if request.to_s !~ /\A(GET|HEAD|DELETE|POST|PUT) /
|
302
|
+
logger.warn("Client request method is not supported")
|
303
|
+
raise SSRFProxy::HTTP::Error::InvalidClientRequest,
|
304
|
+
'Client request method is not supported'
|
305
|
+
end
|
306
|
+
if request.to_s !~ %r{\A(GET|HEAD|DELETE|POST|PUT) https?://}
|
307
|
+
if request.to_s =~ /^Host: ([^\s]+)\r?\n/
|
308
|
+
logger.info("Using host header: #{$1}")
|
309
|
+
else
|
310
|
+
logger.warn('No host specified')
|
311
|
+
raise SSRFProxy::HTTP::Error::InvalidClientRequest,
|
312
|
+
'No host specified'
|
313
|
+
end
|
314
|
+
end
|
315
|
+
# parse client request
|
316
|
+
begin
|
317
|
+
req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP)
|
318
|
+
req.parse(StringIO.new(request))
|
319
|
+
rescue
|
320
|
+
logger.info('Received malformed client HTTP request.')
|
321
|
+
raise SSRFProxy::HTTP::Error::InvalidClientRequest,
|
322
|
+
'Received malformed client HTTP request.'
|
323
|
+
end
|
324
|
+
if req.to_s =~ /^Upgrade: WebSocket/
|
325
|
+
logger.warn("WebSocket tunneling is not supported: #{req.host}:#{req.port}")
|
326
|
+
raise SSRFProxy::HTTP::Error::InvalidClientRequest,
|
327
|
+
"WebSocket tunneling is not supported: #{req.host}:#{req.port}"
|
328
|
+
end
|
329
|
+
uri = req.request_uri
|
330
|
+
if uri.nil?
|
331
|
+
raise SSRFProxy::HTTP::Error::InvalidClientRequest,
|
332
|
+
'URI is nil'
|
333
|
+
end
|
334
|
+
|
335
|
+
# parse request body and move to uri
|
336
|
+
if @body_to_uri && !req.body.nil?
|
337
|
+
logger.debug("Parsing request body: #{req.body}")
|
125
338
|
begin
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
339
|
+
if req.query_string.nil?
|
340
|
+
uri = "#{uri}?#{req.body}"
|
341
|
+
else
|
342
|
+
uri = "#{uri}&#{req.body}"
|
343
|
+
end
|
344
|
+
logger.info("Added request body to URI: #{req.body}")
|
345
|
+
rescue
|
346
|
+
logger.warn('Could not parse client request body')
|
130
347
|
end
|
131
|
-
|
132
|
-
|
133
|
-
|
348
|
+
end
|
349
|
+
|
350
|
+
# move basic authentication credentials to uri
|
351
|
+
if @auth_to_uri && !req.header.nil?
|
352
|
+
req.header['authorization'].each do |header|
|
353
|
+
logger.debug("Parsing basic authentication header: #{header}")
|
354
|
+
next unless header.split(' ').first =~ /^basic$/i
|
355
|
+
begin
|
356
|
+
creds = header.split(' ')[1]
|
357
|
+
user = Base64.decode64(creds).chomp
|
358
|
+
uri = uri.to_s.gsub!(%r{:(//)}, "://#{user}@")
|
359
|
+
logger.info("Using basic authentication credentials: #{user}")
|
360
|
+
rescue
|
361
|
+
logger.warn "Could not parse request authorization header: #{header}"
|
362
|
+
end
|
363
|
+
break
|
134
364
|
end
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
365
|
+
end
|
366
|
+
|
367
|
+
# copy cookies to uri
|
368
|
+
cookies = []
|
369
|
+
if @cookies_to_uri && !req.cookies.nil? && !req.cookies.empty?
|
370
|
+
logger.debug("Parsing request cookies: #{req.cookies.join('; ')}")
|
371
|
+
cookies = []
|
372
|
+
begin
|
373
|
+
req.cookies.each do |c|
|
374
|
+
cookies << c.to_s.gsub(/;\z/, '').to_s unless c.nil?
|
375
|
+
end
|
376
|
+
query_string = uri.to_s.split('?')[1..-1]
|
377
|
+
if query_string.empty?
|
378
|
+
s = '?'
|
379
|
+
else
|
380
|
+
s = '&'
|
381
|
+
end
|
382
|
+
uri = "#{uri}#{s}#{cookies.join('&')}"
|
383
|
+
logger.info("Added cookies to URI: #{cookies.join('&')}")
|
384
|
+
rescue => e
|
385
|
+
logger.warn "Could not parse request coookies: #{e}"
|
139
386
|
end
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
387
|
+
end
|
388
|
+
|
389
|
+
# HTTP request headers
|
390
|
+
headers = {}
|
391
|
+
|
392
|
+
# forward client cookies
|
393
|
+
new_cookie = []
|
394
|
+
new_cookie << @cookie unless @cookie.nil?
|
395
|
+
if @forward_cookies
|
396
|
+
req.cookies.each do |c|
|
397
|
+
new_cookie << c.to_s
|
149
398
|
end
|
150
|
-
@ip_encoding = value.to_s
|
151
|
-
when 'rules'
|
152
|
-
@rules = value.to_s.split(/,/)
|
153
|
-
when 'forward_cookies'
|
154
|
-
@forward_cookies = true if value
|
155
|
-
when 'body_to_uri'
|
156
|
-
@body_to_uri = true if value
|
157
|
-
when 'auth_to_uri'
|
158
|
-
@auth_to_uri = true if value
|
159
399
|
end
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
400
|
+
unless new_cookie.empty?
|
401
|
+
headers['cookie'] = new_cookie.uniq.join('; ').to_s
|
402
|
+
logger.info("Using cookie: #{headers['cookie']}")
|
403
|
+
end
|
404
|
+
send_uri(uri, headers)
|
164
405
|
end
|
165
406
|
|
166
|
-
#
|
167
|
-
|
168
|
-
|
169
|
-
@
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
407
|
+
#
|
408
|
+
# Fetch a URI via SSRF
|
409
|
+
#
|
410
|
+
# @param [String] uri URI to fetch
|
411
|
+
# @param [Hash] HTTP request headers
|
412
|
+
#
|
413
|
+
# @raise [SSRFProxy::HTTP::Error::InvalidClientRequest]
|
414
|
+
# An invalid client HTTP request was supplied.
|
415
|
+
#
|
416
|
+
# @return [Hash] HTTP response hash (version, code, message, headers, body, etc)
|
417
|
+
#
|
418
|
+
def send_uri(uri, headers = {})
|
419
|
+
if uri.nil?
|
420
|
+
raise SSRFProxy::HTTP::Error::InvalidClientRequest,
|
421
|
+
'Request URI is nil'
|
179
422
|
end
|
180
|
-
end
|
181
423
|
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
opts.each do |option, value|
|
188
|
-
next if value.eql?('')
|
189
|
-
case option
|
190
|
-
when 'match'
|
191
|
-
@match_regex = value.to_s
|
192
|
-
when 'strip'
|
193
|
-
@strip = value.to_s.split(/,/)
|
194
|
-
when 'guess_status'
|
195
|
-
@guess_status = true if value
|
196
|
-
when 'guess_mime'
|
197
|
-
@guess_mime = true if value
|
424
|
+
# encode target host ip
|
425
|
+
if @ip_encoding
|
426
|
+
encoded_uri = encode_ip(uri, @ip_encoding)
|
427
|
+
else
|
428
|
+
encoded_uri = uri
|
198
429
|
end
|
199
|
-
end
|
200
430
|
|
201
|
-
|
431
|
+
# run target url through rules
|
432
|
+
target_uri = run_rules(encoded_uri, @rules)
|
202
433
|
|
203
|
-
|
204
|
-
|
205
|
-
#
|
206
|
-
# @returns - String - SSRF URL
|
207
|
-
#
|
208
|
-
def url
|
209
|
-
@ssrf_url
|
210
|
-
end
|
434
|
+
# replace xxURLxx placeholder in SSRF request URL
|
435
|
+
ssrf_url = "#{@ssrf_url.path}?#{@ssrf_url.query}".gsub(/xxURLxx/, target_uri.to_s)
|
211
436
|
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
@ssrf_url.host
|
219
|
-
end
|
437
|
+
# replace xxURLxx placeholder in SSRF request body
|
438
|
+
if @post_data.nil?
|
439
|
+
body = ''
|
440
|
+
else
|
441
|
+
body = @post_data.gsub(/xxURLxx/, target_uri.to_s)
|
442
|
+
end
|
220
443
|
|
221
|
-
|
222
|
-
|
223
|
-
#
|
224
|
-
# @returns - String - SSRF port
|
225
|
-
#
|
226
|
-
def port
|
227
|
-
@ssrf_url.port
|
228
|
-
end
|
444
|
+
# set user agent
|
445
|
+
headers['User-Agent'] = @user_agent if headers['User-Agent'].nil?
|
229
446
|
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
#
|
235
|
-
def cookie
|
236
|
-
@cookie
|
237
|
-
end
|
447
|
+
# set content type
|
448
|
+
if headers['Content-Type'].nil? && @method.eql?('POST')
|
449
|
+
headers['Content-Type'] = 'application/x-www-form-urlencoded'
|
450
|
+
end
|
238
451
|
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
452
|
+
# send request
|
453
|
+
start_time = Time.now
|
454
|
+
response = send_http_request(ssrf_url, @method, headers, body)
|
455
|
+
end_time = Time.now
|
456
|
+
duration = ((end_time - start_time) * 1000).round(3)
|
457
|
+
result = {
|
458
|
+
'url' => uri,
|
459
|
+
'duration' => duration,
|
460
|
+
'http_version' => response.http_version,
|
461
|
+
'code' => response.code,
|
462
|
+
'message' => response.message,
|
463
|
+
'headers' => '',
|
464
|
+
'body' => response.body.to_s || '' }
|
465
|
+
logger.info("Received #{result['body'].length} bytes in #{duration} ms")
|
466
|
+
|
467
|
+
# guess HTTP response code and message
|
468
|
+
if @guess_status
|
469
|
+
head = result['body'][0..8192]
|
470
|
+
status = guess_status(head)
|
471
|
+
unless status.empty?
|
472
|
+
result['code'] = status['code']
|
473
|
+
result['message'] = status['message']
|
474
|
+
logger.info("Using HTTP response status: #{result['code']} #{result['message']}")
|
475
|
+
end
|
476
|
+
end
|
477
|
+
result['status_line'] = "HTTP/#{result['http_version']} #{result['code']} #{result['message']}"
|
247
478
|
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
#
|
257
|
-
def encode_ip(url, mode)
|
258
|
-
return if url.nil?
|
259
|
-
new_host = nil
|
260
|
-
host = URI::parse(url.to_s.split('?').first).host.to_s
|
261
|
-
begin
|
262
|
-
ip = IPAddress.parse(host)
|
263
|
-
rescue
|
264
|
-
logger.warn("Could not parse requested host as IP address: #{host}")
|
265
|
-
return
|
266
|
-
end
|
267
|
-
case mode
|
268
|
-
when 'int'
|
269
|
-
new_host = url.to_s.gsub!(host, "#{ip.to_u32}")
|
270
|
-
when 'ipv6'
|
271
|
-
new_host = url.to_s.gsub!(host, "#{ip.to_ipv6}")
|
272
|
-
when 'oct'
|
273
|
-
new_host = url.to_s.gsub!(host, "0#{ip.to_u32.to_s(8)}")
|
274
|
-
when 'hex'
|
275
|
-
new_host = url.to_s.gsub!(host, "0x#{ip.to_u32.to_s(16)}")
|
276
|
-
else
|
277
|
-
logger.warn("Invalid IP encoding: #{mode}")
|
278
|
-
end
|
279
|
-
new_host
|
280
|
-
end
|
479
|
+
# strip unwanted HTTP response headers
|
480
|
+
response.each_header do |header_name, header_value|
|
481
|
+
if @strip.include?(header_name.downcase)
|
482
|
+
logger.info("Removed response header: #{header_name}")
|
483
|
+
next
|
484
|
+
end
|
485
|
+
result['headers'] << "#{header_name}: #{header_value}\n"
|
486
|
+
end
|
281
487
|
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
# @returns - String - modified request URL
|
290
|
-
#
|
291
|
-
def run_rules(url, rules)
|
292
|
-
str = url.to_s
|
293
|
-
return str if rules.nil?
|
294
|
-
rules.each do |rule|
|
295
|
-
case rule
|
296
|
-
when 'noproto'
|
297
|
-
str = str.gsub(/^https?:\/\//, '')
|
298
|
-
when 'nossl', 'http'
|
299
|
-
str = str.gsub(/^https:\/\//, 'http://')
|
300
|
-
when 'ssl', 'https'
|
301
|
-
str = str.gsub(/^http:\/\//, 'https://')
|
302
|
-
when 'base64'
|
303
|
-
str = Base64.encode64(str).chomp
|
304
|
-
when 'md4'
|
305
|
-
str = OpenSSL::Digest::MD4.hexdigest(str)
|
306
|
-
when 'md5'
|
307
|
-
md5 = Digest::MD5.new
|
308
|
-
md5.update str
|
309
|
-
str = md5.hexdigest
|
310
|
-
when 'sha1'
|
311
|
-
str = Digest::SHA1.hexdigest(str)
|
312
|
-
when 'reverse'
|
313
|
-
str = str.reverse
|
314
|
-
when 'urlencode'
|
315
|
-
str = CGI.escape(str)
|
316
|
-
when 'urldecode'
|
317
|
-
str = CGI.unescape(str)
|
318
|
-
else
|
319
|
-
logger.warn("Unknown rule: #{rule}")
|
488
|
+
# detect WAF and SSRF protection libraries
|
489
|
+
if @detect_waf
|
490
|
+
head = result['body'][0..8192]
|
491
|
+
# SafeCurl (safe_curl) InvalidURLException
|
492
|
+
if head =~ /fin1te\\SafeCurl\\Exception\\InvalidURLException/
|
493
|
+
logger.info('SafeCurl protection mechanism appears to be in use')
|
494
|
+
end
|
320
495
|
end
|
321
|
-
end
|
322
|
-
str
|
323
|
-
end
|
324
496
|
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
# @options
|
329
|
-
# - request - String - raw HTTP request
|
330
|
-
#
|
331
|
-
# @returns String - raw HTTP response headers and body
|
332
|
-
#
|
333
|
-
def send_request(request)
|
334
|
-
if request.to_s !~ /\A[A-Z]{1,20} /
|
335
|
-
logger.warn("Received malformed client HTTP request.")
|
336
|
-
return "HTTP\/1.1 501 Error\nServer: SSRF Proxy\nContent-Length: 0\n\n"
|
337
|
-
elsif request.to_s =~ /\ACONNECT ([^\s]+) .*$/
|
338
|
-
logger.warn("CONNECT tunneling is not supported: #{$1}")
|
339
|
-
return "HTTP\/1.1 501 Error\nServer: SSRF Proxy\nContent-Length: 0\n\n"
|
340
|
-
elsif request.to_s =~ /\A(DEBUG|TRACE|TRACK|OPTIONS) /
|
341
|
-
logger.warn("Client request method is not supported: #{$1}")
|
342
|
-
return "HTTP\/1.1 501 Error\nServer: SSRF Proxy\nContent-Length: 0\n\n"
|
343
|
-
end
|
344
|
-
if request.to_s !~ /\A[A-Z]{1,20} https?:\/\//
|
345
|
-
if request.to_s =~ /^Host: ([^\s]+)\r?\n/
|
346
|
-
logger.info("Using host header: #{$1}")
|
497
|
+
# advise client to close HTTP connection
|
498
|
+
if result['headers'] =~ /^connection:.*$/i
|
499
|
+
result['headers'].gsub!(/^connection:.*$/i, 'Connection: close')
|
347
500
|
else
|
348
|
-
|
349
|
-
return "HTTP\/1.1 501 Error\nServer: SSRF Proxy\nContent-Length: 0\n\n"
|
501
|
+
result['headers'] << "Connection: close\n"
|
350
502
|
end
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
return "HTTP\/1.1 501 Error\nServer: SSRF Proxy\nContent-Length: 0\n\n"
|
503
|
+
|
504
|
+
# guess mime type and add content-type header
|
505
|
+
if @guess_mime
|
506
|
+
content_type = guess_mime(File.extname(uri.to_s.split('?').first))
|
507
|
+
unless content_type.nil?
|
508
|
+
logger.info("Using content-type: #{content_type}")
|
509
|
+
if result['headers'] =~ /^content\-type:.*$/i
|
510
|
+
result['headers'].gsub!(/^content\-type:.*$/i, "Content-Type: #{content_type}")
|
511
|
+
else
|
512
|
+
result['headers'] << "Content-Type: #{content_type}\n"
|
513
|
+
end
|
514
|
+
end
|
364
515
|
end
|
365
|
-
uri = req.request_uri
|
366
|
-
raise SSRFProxy::HTTP::Error::InvalidHttpRequest if uri.nil?
|
367
|
-
rescue => e
|
368
|
-
logger.info("Received malformed client HTTP request.")
|
369
|
-
return "HTTP\/1.1 501 Error\nServer: SSRF Proxy\nContent-Length: 0\n\n"
|
370
|
-
end
|
371
516
|
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
uri = "#{uri}?#{URI.encode_www_form(new_query)}"
|
517
|
+
# match response content
|
518
|
+
unless @match_regex.nil?
|
519
|
+
matches = result['body'].scan(/#{@match_regex}/m)
|
520
|
+
if matches.length > 0
|
521
|
+
result['body'] = matches.flatten.first.to_s
|
522
|
+
logger.info("Response body matches pattern '#{@match_regex}'")
|
379
523
|
else
|
380
|
-
|
381
|
-
|
524
|
+
result['body'] = ''
|
525
|
+
logger.warn("Response body does not match pattern '#{@match_regex}'")
|
382
526
|
end
|
383
|
-
rescue
|
384
|
-
logger.warn "Could not parse request POST data"
|
385
527
|
end
|
386
|
-
end
|
387
528
|
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
529
|
+
# decode HTML entities
|
530
|
+
if @decode_html
|
531
|
+
result['body'] = HTMLEntities.new.decode(
|
532
|
+
result['body'].encode(
|
533
|
+
'UTF-8',
|
534
|
+
:invalid => :replace,
|
535
|
+
:undef => :replace,
|
536
|
+
:replace => '?'))
|
537
|
+
end
|
538
|
+
|
539
|
+
# prompt for password
|
540
|
+
if @ask_password
|
541
|
+
if result['code'].to_i == 401
|
542
|
+
auth_uri = URI.parse(uri.to_s.split('?').first)
|
543
|
+
realm = "#{auth_uri.host}:#{auth_uri.port}"
|
544
|
+
result['headers'] << "WWW-Authenticate: Basic realm=\"#{realm}\"\n"
|
545
|
+
logger.info("Added WWW-Authenticate header for realm: #{realm}")
|
399
546
|
end
|
400
|
-
break
|
401
547
|
end
|
402
|
-
end
|
403
548
|
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
549
|
+
# set content length
|
550
|
+
content_length = result['body'].length
|
551
|
+
if result['headers'] =~ /^transfer\-encoding:.*$/i
|
552
|
+
result['headers'].gsub!(/^transfer\-encoding:.*$/i, "Content-Length: #{content_length}")
|
553
|
+
elsif result['headers'] =~ /^content\-length:.*$/i
|
554
|
+
result['headers'].gsub!(/^content\-length:.*$/i, "Content-Length: #{content_length}")
|
555
|
+
else
|
556
|
+
result['headers'] << "Content-Length: #{content_length}\n"
|
410
557
|
end
|
411
|
-
end
|
412
|
-
unless new_cookie.empty?
|
413
|
-
opts['cookie'] = new_cookie.uniq.join('; ').to_s
|
414
|
-
logger.info "Using cookie: #{opts['cookie']}"
|
415
|
-
end
|
416
|
-
send_uri(uri, opts)
|
417
|
-
end
|
418
558
|
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
#
|
426
|
-
# @returns String - raw HTTP response headers and body
|
427
|
-
#
|
428
|
-
def send_uri(uri, opts={})
|
429
|
-
raise SSRFProxy::HTTP::Error::InvalidUriRequest if uri.nil?
|
559
|
+
# set title
|
560
|
+
if result['body'][0..1024] =~ %r{<title>([^<]*)<\/title>}im
|
561
|
+
result['title'] = $1.to_s
|
562
|
+
else
|
563
|
+
result['title'] = ''
|
564
|
+
end
|
430
565
|
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
uri = encoded_url unless encoded_url.nil?
|
566
|
+
# return HTTP response
|
567
|
+
logger.debug("Response:\n#{result['status_line']}\n#{result['headers']}\n#{result['body']}")
|
568
|
+
result
|
435
569
|
end
|
436
570
|
|
437
|
-
#
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
571
|
+
#
|
572
|
+
# Encode IP address of a given URL
|
573
|
+
#
|
574
|
+
# @param [String] url target URL
|
575
|
+
# @param [String] mode encoding (int, ipv6, oct, hex, dotted_hex)
|
576
|
+
#
|
577
|
+
# @return [String] encoded IP address
|
578
|
+
#
|
579
|
+
def encode_ip(url, mode)
|
580
|
+
return if url.nil?
|
581
|
+
new_host = nil
|
582
|
+
host = URI.parse(url.to_s.split('?').first).host.to_s
|
583
|
+
begin
|
584
|
+
ip = IPAddress::IPv4.new(host)
|
585
|
+
rescue
|
586
|
+
logger.warn("Could not parse requested host as IPv4 address: #{host}")
|
587
|
+
return
|
588
|
+
end
|
589
|
+
case mode
|
590
|
+
when 'int'
|
591
|
+
new_host = url.to_s.gsub(host, ip.to_u32.to_s)
|
592
|
+
when 'ipv6'
|
593
|
+
new_host = url.to_s.gsub(host, "[#{ip.to_ipv6}]")
|
594
|
+
when 'oct'
|
595
|
+
new_host = url.to_s.gsub(host, "0#{ip.to_u32.to_s(8)}")
|
596
|
+
when 'hex'
|
597
|
+
new_host = url.to_s.gsub(host, "0x#{ip.to_u32.to_s(16)}")
|
598
|
+
when 'dotted_hex'
|
599
|
+
res = ip.octets.map { |i| "0x#{i.to_s(16).rjust(2, '0')}" }.join('.')
|
600
|
+
new_host = url.to_s.gsub(host, res.to_s) unless res.nil?
|
601
|
+
else
|
602
|
+
logger.warn("Invalid IP encoding: #{mode}")
|
603
|
+
end
|
604
|
+
new_host
|
454
605
|
end
|
455
606
|
|
456
|
-
#
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
607
|
+
#
|
608
|
+
# Run a specified URL through SSRF rules
|
609
|
+
#
|
610
|
+
# @param [String] url request URL
|
611
|
+
# @param [String] rules comma separated list of rules
|
612
|
+
#
|
613
|
+
# @return [String] modified request URL
|
614
|
+
#
|
615
|
+
def run_rules(url, rules)
|
616
|
+
str = url.to_s
|
617
|
+
return str if rules.nil?
|
618
|
+
rules.each do |rule|
|
619
|
+
case rule
|
620
|
+
when 'noproto'
|
621
|
+
str = str.gsub(%r{^https?://}, '')
|
622
|
+
when 'nossl', 'http'
|
623
|
+
str = str.gsub(%r{^https://}, 'http://')
|
624
|
+
when 'ssl', 'https'
|
625
|
+
str = str.gsub(%r{^http://}, 'https://')
|
626
|
+
when 'base32'
|
627
|
+
str = Base32.encode(str).to_s
|
628
|
+
when 'base64'
|
629
|
+
str = Base64.encode64(str).delete("\n")
|
630
|
+
when 'md4'
|
631
|
+
str = OpenSSL::Digest::MD4.hexdigest(str)
|
632
|
+
when 'md5'
|
633
|
+
md5 = Digest::MD5.new
|
634
|
+
md5.update str
|
635
|
+
str = md5.hexdigest
|
636
|
+
when 'sha1'
|
637
|
+
str = Digest::SHA1.hexdigest(str)
|
638
|
+
when 'reverse'
|
639
|
+
str = str.reverse
|
640
|
+
when 'upcase'
|
641
|
+
str = str.upcase
|
642
|
+
when 'downcase'
|
643
|
+
str = str.downcase
|
644
|
+
when 'rot13'
|
645
|
+
str = str.tr('A-Za-z', 'N-ZA-Mn-za-m')
|
646
|
+
when 'urlencode'
|
647
|
+
str = CGI.escape(str)
|
648
|
+
when 'urldecode'
|
649
|
+
str = CGI.unescape(str)
|
463
650
|
else
|
464
|
-
|
651
|
+
logger.warn("Unknown rule: #{rule}")
|
465
652
|
end
|
466
653
|
end
|
654
|
+
str
|
467
655
|
end
|
468
656
|
|
469
|
-
#
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
657
|
+
#
|
658
|
+
# Send HTTP request to the SSRF server
|
659
|
+
#
|
660
|
+
# @param [String] url URI to fetch
|
661
|
+
# @param [String] method HTTP request method
|
662
|
+
# @param [Hash] headers HTTP request headers
|
663
|
+
# @param [String] body HTTP request body
|
664
|
+
#
|
665
|
+
# @raise [SSRFProxy::HTTP::Error::InvalidSsrfRequestMethod]
|
666
|
+
# Invalid SSRF request method specified.
|
667
|
+
# Method must be GET/HEAD/DELETE/POST/PUT.
|
668
|
+
# @raise [SSRFProxy::HTTP::Error::ConnectionTimeout]
|
669
|
+
# The request to the remote host timed out.
|
670
|
+
# @raise [SSRFProxy::HTTP::Error::InvalidUpstreamProxy]
|
671
|
+
# Invalid upstream proxy specified.
|
672
|
+
#
|
673
|
+
# @return [Hash] Hash of the HTTP response (status, code, headers, body)
|
674
|
+
#
|
675
|
+
def send_http_request(url, method, headers, body)
|
676
|
+
# use upstream proxy
|
677
|
+
if @upstream_proxy.nil?
|
678
|
+
http = Net::HTTP.new(@ssrf_url.host, @ssrf_url.port)
|
679
|
+
elsif @upstream_proxy.scheme =~ /\Ahttps?\z/
|
680
|
+
http = Net::HTTP::Proxy(@upstream_proxy.host, @upstream_proxy.port).new(@ssrf_url.host, @ssrf_url.port)
|
681
|
+
elsif @upstream_proxy.scheme =~ /\Asocks\z/
|
682
|
+
http = Net::HTTP.SOCKSProxy(@upstream_proxy.host, @upstream_proxy.port).new(@ssrf_url.host, @ssrf_url.port)
|
475
683
|
else
|
476
|
-
|
684
|
+
raise SSRFProxy::HTTP::Error::InvalidUpstreamProxy.new,
|
685
|
+
'Unsupported upstream proxy specified. Scheme must be http(s) or socks.'
|
477
686
|
end
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
# replace xxURLxx placeholder in SSRF HTTP GET parameters
|
520
|
-
ssrf_url = "#{@ssrf_url.path}?#{@ssrf_url.query}".gsub(/xxURLxx/, "#{target}")
|
521
|
-
# replace xxURLxx placeholder in SSRF HTTP POST parameters
|
522
|
-
post_data = @post_data.gsub(/xxURLxx/, "#{target}") unless @post_data.nil?
|
523
|
-
if @ssrf_url.scheme == 'https'
|
524
|
-
http.use_ssl = true
|
525
|
-
http.verify_mode = OpenSSL::SSL::VERIFY_NONE #PEER
|
526
|
-
end
|
527
|
-
# set socket options
|
528
|
-
http.open_timeout = @timeout
|
529
|
-
http.read_timeout = @timeout
|
530
|
-
# set request headers
|
531
|
-
headers = {}
|
532
|
-
headers['User-Agent'] = @user_agent unless @user_agent.nil?
|
533
|
-
headers['Cookie'] = opts['cookie'].to_s unless opts['cookie'].nil?
|
534
|
-
headers['Content-Type'] = 'application/x-www-form-urlencoded' if @method == 'POST'
|
535
|
-
response = {}
|
536
|
-
# send http request
|
537
|
-
begin
|
538
|
-
if @method == 'GET'
|
539
|
-
response = http.request Net::HTTP::Get.new(ssrf_url, headers.to_hash)
|
540
|
-
elsif @method == 'HEAD'
|
541
|
-
response = http.request Net::HTTP::Head.new(ssrf_url, headers.to_hash)
|
542
|
-
elsif @method == 'POST'
|
543
|
-
request = Net::HTTP::Post.new(ssrf_url, headers.to_hash)
|
544
|
-
request.body = post_data
|
545
|
-
response = http.request(request)
|
546
|
-
else
|
547
|
-
logger.info("SSRF request method not implemented - METHOD[#{@method}]")
|
548
|
-
response["status"] = 'fail'
|
549
|
-
response["code"] = 501
|
550
|
-
response["message"] = 'Error'
|
551
|
-
response["headers"] = "HTTP\/1.1 501 Error\nServer: SSRF Proxy\nContent-Length: 0\n\n"
|
687
|
+
if @ssrf_url.scheme == 'https'
|
688
|
+
http.use_ssl = true
|
689
|
+
if @insecure
|
690
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
691
|
+
else
|
692
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
693
|
+
end
|
694
|
+
end
|
695
|
+
# set socket options
|
696
|
+
http.open_timeout = @timeout
|
697
|
+
http.read_timeout = @timeout
|
698
|
+
# send http request
|
699
|
+
response = {}
|
700
|
+
logger.info("Sending request: #{url}")
|
701
|
+
begin
|
702
|
+
if method == 'GET'
|
703
|
+
response = http.request Net::HTTP::Get.new(url, headers.to_hash)
|
704
|
+
elsif method == 'HEAD'
|
705
|
+
response = http.request Net::HTTP::Head.new(url, headers.to_hash)
|
706
|
+
elsif method == 'DELETE'
|
707
|
+
response = http.request Net::HTTP::Delete.new(url, headers.to_hash)
|
708
|
+
elsif method == 'POST'
|
709
|
+
request = Net::HTTP::Post.new(url, headers.to_hash)
|
710
|
+
request.body = body
|
711
|
+
response = http.request(request)
|
712
|
+
elsif method == 'PUT'
|
713
|
+
request = Net::HTTP::Put.new(url, headers.to_hash)
|
714
|
+
request.body = body
|
715
|
+
response = http.request(request)
|
716
|
+
else
|
717
|
+
logger.info("SSRF request method not implemented -- Method[#{method}]")
|
718
|
+
raise SSRFProxy::HTTP::Error::InvalidSsrfRequestMethod,
|
719
|
+
"Request method not implemented -- Method[#{method}]"
|
720
|
+
end
|
721
|
+
rescue Timeout::Error, Errno::ETIMEDOUT
|
722
|
+
logger.info("Connection timed out [#{@timeout}]")
|
723
|
+
raise SSRFProxy::HTTP::Error::ConnectionTimeout,
|
724
|
+
"Connection timed out [#{@timeout}]"
|
725
|
+
rescue => e
|
726
|
+
logger.error("Unhandled exception: #{e}")
|
727
|
+
raise e
|
552
728
|
end
|
553
|
-
|
554
|
-
logger.warn("Connection timeout - TIMEOUT[#{@timeout}] - URI[#{url}]\n")
|
555
|
-
response["status"] = 'fail'
|
556
|
-
response["code"] = 504
|
557
|
-
response["message"] = 'Timeout'
|
558
|
-
response["headers"] = "HTTP\/1.1 504 Error\nServer: SSRF Proxy\nContent-Length: 0\n\n"
|
559
|
-
rescue => e
|
560
|
-
response["status"] = 'fail'
|
561
|
-
response["code"] = 500
|
562
|
-
response["message"] = 'Error'
|
563
|
-
response["headers"] = "HTTP\/1.1 500 Error\nServer: SSRF Proxy\nContent-Length: 0\n\n"
|
564
|
-
logger.error("Unhandled exception: #{e.message}: #{e}")
|
729
|
+
response
|
565
730
|
end
|
566
|
-
return response
|
567
|
-
end
|
568
731
|
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
result["message"] = "#{$2}"
|
621
|
-
end
|
622
|
-
# No route to host
|
623
|
-
elsif head =~ /failed to open stream: No route to host in/
|
624
|
-
result["code"] = 502
|
625
|
-
result["message"] = 'Bad Gateway'
|
626
|
-
# Connection refused
|
627
|
-
elsif head =~ /failed to open stream: Connection refused in/
|
628
|
-
result["code"] = 502
|
629
|
-
result["message"] = 'Bad Gateway'
|
630
|
-
# Connection timed out
|
631
|
-
elsif head =~ /failed to open stream: Connection timed out/
|
632
|
-
result["code"] = 504
|
633
|
-
result["message"] = 'Timeout'
|
634
|
-
end
|
635
|
-
# Java 'java.net.ConnectException' errors
|
636
|
-
elsif head =~ /java\.net\.ConnectException: /
|
637
|
-
# No route to host
|
638
|
-
if head =~ /java\.net\.ConnectException: No route to host/
|
639
|
-
result["code"] = 502
|
640
|
-
result["message"] = 'Bad Gateway'
|
641
|
-
# Connection refused
|
642
|
-
elsif head =~ /java\.net\.ConnectException: Connection refused/
|
643
|
-
result["code"] = 502
|
644
|
-
result["message"] = 'Bad Gateway'
|
645
|
-
# Connection timed out
|
646
|
-
elsif head =~ /java\.net\.ConnectException: Connection timed out/
|
647
|
-
result["code"] = 504
|
648
|
-
result["message"] = 'Timeout'
|
649
|
-
end
|
650
|
-
# Java 'java.net.UnknownHostException' errors
|
651
|
-
elsif head =~ /java\.net\.UnknownHostException: /
|
652
|
-
if head =~ /java\.net\.UnknownHostException: Invalid hostname/
|
653
|
-
result["code"] = 502
|
654
|
-
result["message"] = 'Bad Gateway'
|
655
|
-
end
|
656
|
-
# Python errors
|
657
|
-
elsif head =~ /\[Errno -?[\d]{1,3}\]/
|
658
|
-
if head =~ /\[Errno 113\] No route to host/
|
659
|
-
result["code"] = 502
|
660
|
-
result["message"] = 'Bad Gateway'
|
661
|
-
elsif head =~ /\[Errno -2\] Name or service not known/
|
662
|
-
result["code"] = 502
|
663
|
-
result["message"] = 'Bad Gateway'
|
664
|
-
elsif head =~ /\[Errno 111\] Connection refused/
|
665
|
-
result["code"] = 502
|
666
|
-
result["message"] = 'Bad Gateway'
|
667
|
-
elsif head =~ /\[Errno 110\] Connection timed out/
|
668
|
-
result["code"] = 504
|
669
|
-
result["message"] = 'Timeout'
|
670
|
-
end
|
671
|
-
# Ruby errors
|
672
|
-
elsif head =~ /Errno::[A-Z]+/
|
673
|
-
# Connection refused
|
674
|
-
if head =~ /Errno::ECONNREFUSED/
|
675
|
-
result["code"] = 502
|
676
|
-
result["message"] = 'Bad Gateway'
|
677
|
-
# No route to host
|
678
|
-
elsif head =~ /Errno::EHOSTUNREACH/
|
679
|
-
result["code"] = 502
|
680
|
-
result["message"] = 'Bad Gateway'
|
681
|
-
# Connection timed out
|
682
|
-
elsif head =~ /Errno::ETIMEDOUT/
|
683
|
-
result["code"] = 504
|
684
|
-
result["message"] = 'Timeout'
|
732
|
+
#
|
733
|
+
# Guess HTTP response status code and message based
|
734
|
+
# on common strings in the response body such
|
735
|
+
# as a default title or exception error message
|
736
|
+
#
|
737
|
+
# @param [String] response HTTP response
|
738
|
+
#
|
739
|
+
# @return [Hash] includes HTTP response code and message
|
740
|
+
#
|
741
|
+
def guess_status(response)
|
742
|
+
result = {}
|
743
|
+
# generic page titles containing HTTP status
|
744
|
+
if response =~ />400 Bad Request</
|
745
|
+
result['code'] = 400
|
746
|
+
result['message'] = 'Bad Request'
|
747
|
+
elsif response =~ />401 Unauthorized</
|
748
|
+
result['code'] = 401
|
749
|
+
result['message'] = 'Unauthorized'
|
750
|
+
elsif response =~ />403 Forbidden</
|
751
|
+
result['code'] = 403
|
752
|
+
result['message'] = 'Forbidden'
|
753
|
+
elsif response =~ />404 Not Found</
|
754
|
+
result['code'] = 404
|
755
|
+
result['message'] = 'Not Found'
|
756
|
+
elsif response =~ />500 Internal Server Error</
|
757
|
+
result['code'] = 500
|
758
|
+
result['message'] = 'Internal Server Error'
|
759
|
+
elsif response =~ />503 Service Unavailable</
|
760
|
+
result['code'] = 503
|
761
|
+
result['message'] = 'Service Unavailable'
|
762
|
+
# getaddrinfo() errors
|
763
|
+
elsif response =~ /getaddrinfo: /
|
764
|
+
if response =~ /getaddrinfo: nodename nor servname provided/
|
765
|
+
result['code'] = 502
|
766
|
+
result['message'] = 'Bad Gateway'
|
767
|
+
elsif response =~ /getaddrinfo: Name or service not known/
|
768
|
+
result['code'] = 502
|
769
|
+
result['message'] = 'Bad Gateway'
|
770
|
+
end
|
771
|
+
# getnameinfo() errors
|
772
|
+
elsif response =~ /getnameinfo failed: /
|
773
|
+
result['code'] = 502
|
774
|
+
result['message'] = 'Bad Gateway'
|
775
|
+
# PHP 'failed to open stream' errors
|
776
|
+
elsif response =~ /failed to open stream: /
|
777
|
+
# HTTP request failed! HTTP/[version] [code] [message]
|
778
|
+
if response =~ %r{failed to open stream: HTTP request failed! HTTP\/(0\.9|1\.0|1\.1) ([\d]+) }
|
779
|
+
result['code'] = $2.to_s
|
780
|
+
result['message'] = ''
|
781
|
+
if response =~ %r{failed to open stream: HTTP request failed! HTTP/(0\.9|1\.0|1\.1) [\d]+ ([a-zA-Z ]+)}
|
782
|
+
result['message'] = $2.to_s
|
685
783
|
end
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
697
|
-
|
698
|
-
|
784
|
+
# No route to host
|
785
|
+
elsif response =~ /failed to open stream: No route to host in/
|
786
|
+
result['code'] = 502
|
787
|
+
result['message'] = 'Bad Gateway'
|
788
|
+
# Connection refused
|
789
|
+
elsif response =~ /failed to open stream: Connection refused in/
|
790
|
+
result['code'] = 502
|
791
|
+
result['message'] = 'Bad Gateway'
|
792
|
+
# Connection timed out
|
793
|
+
elsif response =~ /failed to open stream: Connection timed out/
|
794
|
+
result['code'] = 504
|
795
|
+
result['message'] = 'Timeout'
|
796
|
+
# Success - This likely indicates an SSL/TLS connection failure
|
797
|
+
elsif response =~ /failed to open stream: Success in/
|
798
|
+
result['code'] = 502
|
799
|
+
result['message'] = 'Bad Gateway'
|
800
|
+
end
|
801
|
+
# Java 'java.net' exceptions
|
802
|
+
elsif response =~ /java\.net\.[^\s]*Exception: /
|
803
|
+
if response =~ /java\.net\.ConnectException: No route to host/
|
804
|
+
result['code'] = 502
|
805
|
+
result['message'] = 'Bad Gateway'
|
806
|
+
elsif response =~ /java\.net\.ConnectException: Connection refused/
|
807
|
+
result['code'] = 502
|
808
|
+
result['message'] = 'Bad Gateway'
|
809
|
+
elsif response =~ /java\.net\.ConnectException: Connection timed out/
|
810
|
+
result['code'] = 504
|
811
|
+
result['message'] = 'Timeout'
|
812
|
+
elsif response =~ /java\.net\.UnknownHostException: Invalid hostname/
|
813
|
+
result['code'] = 502
|
814
|
+
result['message'] = 'Bad Gateway'
|
815
|
+
elsif response =~ /java\.net\.SocketException: Network is unreachable/
|
816
|
+
result['code'] = 502
|
817
|
+
result['message'] = 'Bad Gateway'
|
818
|
+
elsif response =~ /java\.net\.SocketException: Connection reset/
|
819
|
+
result['code'] = 502
|
820
|
+
result['message'] = 'Bad Gateway'
|
821
|
+
elsif response =~ /java\.net\.SocketTimeoutException: Connection timed out/
|
822
|
+
result['code'] = 504
|
823
|
+
result['message'] = 'Timeout'
|
824
|
+
end
|
825
|
+
# C errno
|
826
|
+
elsif response =~ /\[Errno -?[\d]{1,3}\]/
|
827
|
+
if response =~ /\[Errno -2\] Name or service not known/
|
828
|
+
result['code'] = 502
|
829
|
+
result['message'] = 'Bad Gateway'
|
830
|
+
elsif response =~ /\[Errno 101\] Network is unreachable/
|
831
|
+
result['code'] = 502
|
832
|
+
result['message'] = 'Bad Gateway'
|
833
|
+
elsif response =~ /\[Errno 104\] Connection reset by peer/
|
834
|
+
result['code'] = 502
|
835
|
+
result['message'] = 'Bad Gateway'
|
836
|
+
elsif response =~ /\[Errno 110\] Connection timed out/
|
837
|
+
result['code'] = 504
|
838
|
+
result['message'] = 'Timeout'
|
839
|
+
elsif response =~ /\[Errno 111\] Connection refused/
|
840
|
+
result['code'] = 502
|
841
|
+
result['message'] = 'Bad Gateway'
|
842
|
+
elsif response =~ /\[Errno 113\] No route to host/
|
843
|
+
result['code'] = 502
|
844
|
+
result['message'] = 'Bad Gateway'
|
845
|
+
end
|
846
|
+
# Python urllib errors
|
847
|
+
elsif response =~ /HTTPError: HTTP Error \d+/
|
848
|
+
if response =~ /HTTPError: HTTP Error 400: Bad Request/
|
849
|
+
result['code'] = 400
|
850
|
+
result['message'] = 'Bad Request'
|
851
|
+
elsif response =~ /HTTPError: HTTP Error 401: Unauthorized/
|
852
|
+
result['code'] = 401
|
853
|
+
result['message'] = 'Unauthorized'
|
854
|
+
elsif response =~ /HTTPError: HTTP Error 402: Payment Required/
|
855
|
+
result['code'] = 402
|
856
|
+
result['message'] = 'Payment Required'
|
857
|
+
elsif response =~ /HTTPError: HTTP Error 403: Forbidden/
|
858
|
+
result['code'] = 403
|
859
|
+
result['message'] = 'Forbidden'
|
860
|
+
elsif response =~ /HTTPError: HTTP Error 404: Not Found/
|
861
|
+
result['code'] = 404
|
862
|
+
result['message'] = 'Not Found'
|
863
|
+
elsif response =~ /HTTPError: HTTP Error 405: Method Not Allowed/
|
864
|
+
result['code'] = 405
|
865
|
+
result['message'] = 'Method Not Allowed'
|
866
|
+
elsif response =~ /HTTPError: HTTP Error 410: Gone/
|
867
|
+
result['code'] = 410
|
868
|
+
result['message'] = 'Gone'
|
869
|
+
elsif response =~ /HTTPError: HTTP Error 500: Internal Server Error/
|
870
|
+
result['code'] = 500
|
871
|
+
result['message'] = 'Internal Server Error'
|
872
|
+
elsif response =~ /HTTPError: HTTP Error 502: Bad Gateway/
|
873
|
+
result['code'] = 502
|
874
|
+
result['message'] = 'Bad Gateway'
|
875
|
+
elsif response =~ /HTTPError: HTTP Error 503: Service Unavailable/
|
876
|
+
result['code'] = 503
|
877
|
+
result['message'] = 'Service Unavailable'
|
878
|
+
elsif response =~ /HTTPError: HTTP Error 504: Gateway Time-?out/
|
879
|
+
result['code'] = 504
|
880
|
+
result['message'] = 'Timeout'
|
881
|
+
end
|
882
|
+
# Ruby exceptions
|
883
|
+
elsif response =~ /Errno::[A-Z]+/
|
884
|
+
# Connection refused
|
885
|
+
if response =~ /Errno::ECONNREFUSED/
|
886
|
+
result['code'] = 502
|
887
|
+
result['message'] = 'Bad Gateway'
|
888
|
+
# No route to host
|
889
|
+
elsif response =~ /Errno::EHOSTUNREACH/
|
890
|
+
result['code'] = 502
|
891
|
+
result['message'] = 'Bad Gateway'
|
892
|
+
# Connection timed out
|
893
|
+
elsif response =~ /Errno::ETIMEDOUT/
|
894
|
+
result['code'] = 504
|
895
|
+
result['message'] = 'Timeout'
|
896
|
+
end
|
897
|
+
# ASP.NET System.Net.WebClient errors
|
898
|
+
elsif response =~ /System\.Net\.WebClient/
|
899
|
+
# The remote server returned an error: ([code]) [message].
|
900
|
+
if response =~ /WebException: The remote server returned an error: \(([\d+])\) /
|
901
|
+
result['code'] = $1.to_s
|
902
|
+
result['message'] = ''
|
903
|
+
if response =~ /WebException: The remote server returned an error: \(([\d+])\) ([a-zA-Z ]+)\./
|
904
|
+
result['message'] = $2.to_s
|
699
905
|
end
|
906
|
+
# Could not resolve hostname
|
907
|
+
elsif response =~ /WebException: The remote name could not be resolved/
|
908
|
+
result['code'] = 502
|
909
|
+
result['message'] = 'Bad Gateway'
|
910
|
+
# Remote server denied connection (port closed)
|
911
|
+
elsif response =~ /WebException: Unable to connect to the remote server/
|
912
|
+
result['code'] = 502
|
913
|
+
result['message'] = 'Bad Gateway'
|
914
|
+
# This likely indicates a plain-text connection to a HTTPS or non-HTTP service
|
915
|
+
elsif response =~ /WebException: The underlying connection was closed: An unexpected error occurred on a receive/
|
916
|
+
result['code'] = 502
|
917
|
+
result['message'] = 'Bad Gateway'
|
918
|
+
# This likely indicates a HTTPS connection to a plain-text HTTP or non-HTTP service
|
919
|
+
elsif response =~ /WebException: The underlying connection was closed: An unexpected error occurred on a send/
|
920
|
+
result['code'] = 502
|
921
|
+
result['message'] = 'Bad Gateway'
|
922
|
+
# The operation has timed out
|
923
|
+
elsif response =~ /WebException: The operation has timed out/
|
924
|
+
result['code'] = 504
|
925
|
+
result['message'] = 'Timeout'
|
700
926
|
end
|
701
|
-
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
|
927
|
+
# Generic error messages
|
928
|
+
elsif response =~ /(Connection refused|No route to host|Connection timed out) - connect\(\d\)/
|
929
|
+
# Connection refused
|
930
|
+
if response =~ /Connection refused - connect\(\d\)/
|
931
|
+
result['code'] = 502
|
932
|
+
result['message'] = 'Bad Gateway'
|
933
|
+
# No route to host
|
934
|
+
elsif response =~ /No route to host - connect\(\d\)/
|
935
|
+
result['code'] = 502
|
936
|
+
result['message'] = 'Bad Gateway'
|
937
|
+
# Connection timed out
|
938
|
+
elsif response =~ /Connection timed out - connect\(\d\)/
|
939
|
+
result['code'] = 504
|
940
|
+
result['message'] = 'Timeout'
|
708
941
|
end
|
709
|
-
result["headers"] << "#{header_name}: #{header_value}\n"
|
710
942
|
end
|
711
|
-
result
|
712
|
-
result["body"] = "#{response.body}" unless response.body.nil?
|
713
|
-
rescue => e
|
714
|
-
logger.info("Malformed HTTP response from server")
|
715
|
-
result["status"] = 'fail'
|
716
|
-
result["code"] = 502
|
717
|
-
result["message"] = 'Error'
|
718
|
-
result["headers"] = "HTTP\/1.1 502 Error\nServer: SSRF Proxy\nContent-Length: 0\n\n"
|
943
|
+
result
|
719
944
|
end
|
720
|
-
return result
|
721
|
-
end
|
722
945
|
|
723
|
-
|
724
|
-
|
725
|
-
|
726
|
-
|
727
|
-
|
728
|
-
|
729
|
-
|
730
|
-
|
731
|
-
|
732
|
-
|
733
|
-
|
734
|
-
|
735
|
-
|
736
|
-
3gp,video/3gpp,
|
737
|
-
3gp2,video/3gpp2,
|
738
|
-
3gpp,video/3gpp,
|
739
|
-
7z,application/x-7z-compressed,
|
740
|
-
aa,audio/audible,
|
741
|
-
AAC,audio/aac,
|
742
|
-
aaf,application/octet-stream,
|
743
|
-
aax,audio/vnd.audible.aax,
|
744
|
-
ac3,audio/ac3,
|
745
|
-
aca,application/octet-stream,
|
746
|
-
accda,application/msaccess.addin,
|
747
|
-
accdb,application/msaccess,
|
748
|
-
accdc,application/msaccess.cab,
|
749
|
-
accde,application/msaccess,
|
750
|
-
accdr,application/msaccess.runtime,
|
751
|
-
accdt,application/msaccess,
|
752
|
-
accdw,application/msaccess.webapplication,
|
753
|
-
accft,application/msaccess.ftemplate,
|
754
|
-
acx,application/internet-property-stream,
|
755
|
-
AddIn,text/xml,
|
756
|
-
ade,application/msaccess,
|
757
|
-
adobebridge,application/x-bridge-url,
|
758
|
-
adp,application/msaccess,
|
759
|
-
ADT,audio/vnd.dlna.adts,
|
760
|
-
ADTS,audio/aac,
|
761
|
-
afm,application/octet-stream,
|
762
|
-
ai,application/postscript,
|
763
|
-
aif,audio/x-aiff,
|
764
|
-
aifc,audio/aiff,
|
765
|
-
aiff,audio/aiff,
|
766
|
-
air,application/vnd.adobe.air-application-installer-package+zip,
|
767
|
-
amc,application/x-mpeg,
|
768
|
-
application,application/x-ms-application,
|
769
|
-
art,image/x-jg,
|
770
|
-
asa,application/xml,
|
771
|
-
asax,application/xml,
|
772
|
-
ascx,application/xml,
|
773
|
-
asd,application/octet-stream,
|
774
|
-
asf,video/x-ms-asf,
|
775
|
-
ashx,application/xml,
|
776
|
-
asi,application/octet-stream,
|
777
|
-
asm,text/plain,
|
778
|
-
asmx,application/xml,
|
779
|
-
aspx,application/xml,
|
780
|
-
asr,video/x-ms-asf,
|
781
|
-
asx,video/x-ms-asf,
|
782
|
-
atom,application/atom+xml,
|
783
|
-
au,audio/basic,
|
784
|
-
avi,video/x-msvideo,
|
785
|
-
axs,application/olescript,
|
786
|
-
bas,text/plain,
|
787
|
-
bcpio,application/x-bcpio,
|
788
|
-
bin,application/octet-stream,
|
789
|
-
bmp,image/bmp,
|
790
|
-
c,text/plain,
|
791
|
-
cab,application/octet-stream,
|
792
|
-
caf,audio/x-caf,
|
793
|
-
calx,application/vnd.ms-office.calx,
|
794
|
-
cat,application/vnd.ms-pki.seccat,
|
795
|
-
cc,text/plain,
|
796
|
-
cd,text/plain,
|
797
|
-
cdda,audio/aiff,
|
798
|
-
cdf,application/x-cdf,
|
799
|
-
cer,application/x-x509-ca-cert,
|
800
|
-
chm,application/octet-stream,
|
801
|
-
class,application/x-java-applet,
|
802
|
-
clp,application/x-msclip,
|
803
|
-
cmx,image/x-cmx,
|
804
|
-
cnf,text/plain,
|
805
|
-
cod,image/cis-cod,
|
806
|
-
config,application/xml,
|
807
|
-
contact,text/x-ms-contact,
|
808
|
-
coverage,application/xml,
|
809
|
-
cpio,application/x-cpio,
|
810
|
-
cpp,text/plain,
|
811
|
-
crd,application/x-mscardfile,
|
812
|
-
crl,application/pkix-crl,
|
813
|
-
crt,application/x-x509-ca-cert,
|
814
|
-
cs,text/plain,
|
815
|
-
csdproj,text/plain,
|
816
|
-
csh,application/x-csh,
|
817
|
-
csproj,text/plain,
|
818
|
-
css,text/css,
|
819
|
-
csv,text/csv,
|
820
|
-
cur,application/octet-stream,
|
821
|
-
cxx,text/plain,
|
822
|
-
dat,application/octet-stream,
|
823
|
-
datasource,application/xml,
|
824
|
-
dbproj,text/plain,
|
825
|
-
dcr,application/x-director,
|
826
|
-
def,text/plain,
|
827
|
-
deploy,application/octet-stream,
|
828
|
-
der,application/x-x509-ca-cert,
|
829
|
-
dgml,application/xml,
|
830
|
-
dib,image/bmp,
|
831
|
-
dif,video/x-dv,
|
832
|
-
dir,application/x-director,
|
833
|
-
disco,text/xml,
|
834
|
-
dll,application/x-msdownload,
|
835
|
-
dll.config,text/xml,
|
836
|
-
dlm,text/dlm,
|
837
|
-
doc,application/msword,
|
838
|
-
docm,application/vnd.ms-word.document.macroEnabled.12,
|
839
|
-
docx,application/vnd.openxmlformats-officedocument.wordprocessingml.document,
|
840
|
-
dot,application/msword,
|
841
|
-
dotm,application/vnd.ms-word.template.macroEnabled.12,
|
842
|
-
dotx,application/vnd.openxmlformats-officedocument.wordprocessingml.template,
|
843
|
-
dsp,application/octet-stream,
|
844
|
-
dsw,text/plain,
|
845
|
-
dtd,text/xml,
|
846
|
-
dtsConfig,text/xml,
|
847
|
-
dv,video/x-dv,
|
848
|
-
dvi,application/x-dvi,
|
849
|
-
dwf,drawing/x-dwf,
|
850
|
-
dwp,application/octet-stream,
|
851
|
-
dxr,application/x-director,
|
852
|
-
eml,message/rfc822,
|
853
|
-
emz,application/octet-stream,
|
854
|
-
eot,application/octet-stream,
|
855
|
-
eps,application/postscript,
|
856
|
-
etl,application/etl,
|
857
|
-
etx,text/x-setext,
|
858
|
-
evy,application/envoy,
|
859
|
-
exe,application/octet-stream,
|
860
|
-
exe.config,text/xml,
|
861
|
-
fdf,application/vnd.fdf,
|
862
|
-
fif,application/fractals,
|
863
|
-
filters,Application/xml,
|
864
|
-
fla,application/octet-stream,
|
865
|
-
flr,x-world/x-vrml,
|
866
|
-
flv,video/x-flv,
|
867
|
-
fsscript,application/fsharp-script,
|
868
|
-
fsx,application/fsharp-script,
|
869
|
-
generictest,application/xml,
|
870
|
-
gif,image/gif,
|
871
|
-
group,text/x-ms-group,
|
872
|
-
gsm,audio/x-gsm,
|
873
|
-
gtar,application/x-gtar,
|
874
|
-
gz,application/x-gzip,
|
875
|
-
h,text/plain,
|
876
|
-
hdf,application/x-hdf,
|
877
|
-
hdml,text/x-hdml,
|
878
|
-
hhc,application/x-oleobject,
|
879
|
-
hhk,application/octet-stream,
|
880
|
-
hhp,application/octet-stream,
|
881
|
-
hlp,application/winhlp,
|
882
|
-
hpp,text/plain,
|
883
|
-
hqx,application/mac-binhex40,
|
884
|
-
hta,application/hta,
|
885
|
-
htc,text/x-component,
|
886
|
-
htm,text/html,
|
887
|
-
html,text/html,
|
888
|
-
htt,text/webviewhtml,
|
889
|
-
hxa,application/xml,
|
890
|
-
hxc,application/xml,
|
891
|
-
hxd,application/octet-stream,
|
892
|
-
hxe,application/xml,
|
893
|
-
hxf,application/xml,
|
894
|
-
hxh,application/octet-stream,
|
895
|
-
hxi,application/octet-stream,
|
896
|
-
hxk,application/xml,
|
897
|
-
hxq,application/octet-stream,
|
898
|
-
hxr,application/octet-stream,
|
899
|
-
hxs,application/octet-stream,
|
900
|
-
hxt,text/html,
|
901
|
-
hxv,application/xml,
|
902
|
-
hxw,application/octet-stream,
|
903
|
-
hxx,text/plain,
|
904
|
-
i,text/plain,
|
905
|
-
ico,image/x-icon,
|
906
|
-
ics,application/octet-stream,
|
907
|
-
idl,text/plain,
|
908
|
-
ief,image/ief,
|
909
|
-
iii,application/x-iphone,
|
910
|
-
inc,text/plain,
|
911
|
-
inf,application/octet-stream,
|
912
|
-
inl,text/plain,
|
913
|
-
ins,application/x-internet-signup,
|
914
|
-
ipa,application/x-itunes-ipa,
|
915
|
-
ipg,application/x-itunes-ipg,
|
916
|
-
ipproj,text/plain,
|
917
|
-
ipsw,application/x-itunes-ipsw,
|
918
|
-
iqy,text/x-ms-iqy,
|
919
|
-
isp,application/x-internet-signup,
|
920
|
-
ite,application/x-itunes-ite,
|
921
|
-
itlp,application/x-itunes-itlp,
|
922
|
-
itms,application/x-itunes-itms,
|
923
|
-
itpc,application/x-itunes-itpc,
|
924
|
-
IVF,video/x-ivf,
|
925
|
-
jar,application/java-archive,
|
926
|
-
java,application/octet-stream,
|
927
|
-
jck,application/liquidmotion,
|
928
|
-
jcz,application/liquidmotion,
|
929
|
-
jfif,image/pjpeg,
|
930
|
-
jnlp,application/x-java-jnlp-file,
|
931
|
-
jpb,application/octet-stream,
|
932
|
-
jpe,image/jpeg,
|
933
|
-
jpeg,image/jpeg,
|
934
|
-
jpg,image/jpeg,
|
935
|
-
js,application/x-javascript,
|
936
|
-
json,application/json,
|
937
|
-
jsx,text/jscript,
|
938
|
-
jsxbin,text/plain,
|
939
|
-
latex,application/x-latex,
|
940
|
-
library-ms,application/windows-library+xml,
|
941
|
-
lit,application/x-ms-reader,
|
942
|
-
loadtest,application/xml,
|
943
|
-
lpk,application/octet-stream,
|
944
|
-
lsf,video/x-la-asf,
|
945
|
-
lst,text/plain,
|
946
|
-
lsx,video/x-la-asf,
|
947
|
-
lzh,application/octet-stream,
|
948
|
-
m13,application/x-msmediaview,
|
949
|
-
m14,application/x-msmediaview,
|
950
|
-
m1v,video/mpeg,
|
951
|
-
m2t,video/vnd.dlna.mpeg-tts,
|
952
|
-
m2ts,video/vnd.dlna.mpeg-tts,
|
953
|
-
m2v,video/mpeg,
|
954
|
-
m3u,audio/x-mpegurl,
|
955
|
-
m3u8,audio/x-mpegurl,
|
956
|
-
m4a,audio/m4a,
|
957
|
-
m4b,audio/m4b,
|
958
|
-
m4p,audio/m4p,
|
959
|
-
m4r,audio/x-m4r,
|
960
|
-
m4v,video/x-m4v,
|
961
|
-
mac,image/x-macpaint,
|
962
|
-
mak,text/plain,
|
963
|
-
man,application/x-troff-man,
|
964
|
-
manifest,application/x-ms-manifest,
|
965
|
-
map,text/plain,
|
966
|
-
master,application/xml,
|
967
|
-
mda,application/msaccess,
|
968
|
-
mdb,application/x-msaccess,
|
969
|
-
mde,application/msaccess,
|
970
|
-
mdp,application/octet-stream,
|
971
|
-
me,application/x-troff-me,
|
972
|
-
mfp,application/x-shockwave-flash,
|
973
|
-
mht,message/rfc822,
|
974
|
-
mhtml,message/rfc822,
|
975
|
-
mid,audio/mid,
|
976
|
-
midi,audio/mid,
|
977
|
-
mix,application/octet-stream,
|
978
|
-
mk,text/plain,
|
979
|
-
mmf,application/x-smaf,
|
980
|
-
mno,text/xml,
|
981
|
-
mny,application/x-msmoney,
|
982
|
-
mod,video/mpeg,
|
983
|
-
mov,video/quicktime,
|
984
|
-
movie,video/x-sgi-movie,
|
985
|
-
mp2,video/mpeg,
|
986
|
-
mp2v,video/mpeg,
|
987
|
-
mp3,audio/mpeg,
|
988
|
-
mp4,video/mp4,
|
989
|
-
mp4v,video/mp4,
|
990
|
-
mpa,video/mpeg,
|
991
|
-
mpe,video/mpeg,
|
992
|
-
mpeg,video/mpeg,
|
993
|
-
mpf,application/vnd.ms-mediapackage,
|
994
|
-
mpg,video/mpeg,
|
995
|
-
mpp,application/vnd.ms-project,
|
996
|
-
mpv2,video/mpeg,
|
997
|
-
mqv,video/quicktime,
|
998
|
-
ms,application/x-troff-ms,
|
999
|
-
msi,application/octet-stream,
|
1000
|
-
mso,application/octet-stream,
|
1001
|
-
mts,video/vnd.dlna.mpeg-tts,
|
1002
|
-
mtx,application/xml,
|
1003
|
-
mvb,application/x-msmediaview,
|
1004
|
-
mvc,application/x-miva-compiled,
|
1005
|
-
mxp,application/x-mmxp,
|
1006
|
-
nc,application/x-netcdf,
|
1007
|
-
nsc,video/x-ms-asf,
|
1008
|
-
nws,message/rfc822,
|
1009
|
-
ocx,application/octet-stream,
|
1010
|
-
oda,application/oda,
|
1011
|
-
odc,text/x-ms-odc,
|
1012
|
-
odh,text/plain,
|
1013
|
-
odl,text/plain,
|
1014
|
-
odp,application/vnd.oasis.opendocument.presentation,
|
1015
|
-
ods,application/oleobject,
|
1016
|
-
odt,application/vnd.oasis.opendocument.text,
|
1017
|
-
one,application/onenote,
|
1018
|
-
onea,application/onenote,
|
1019
|
-
onepkg,application/onenote,
|
1020
|
-
onetmp,application/onenote,
|
1021
|
-
onetoc,application/onenote,
|
1022
|
-
onetoc2,application/onenote,
|
1023
|
-
orderedtest,application/xml,
|
1024
|
-
osdx,application/opensearchdescription+xml,
|
1025
|
-
p10,application/pkcs10,
|
1026
|
-
p12,application/x-pkcs12,
|
1027
|
-
p7b,application/x-pkcs7-certificates,
|
1028
|
-
p7c,application/pkcs7-mime,
|
1029
|
-
p7m,application/pkcs7-mime,
|
1030
|
-
p7r,application/x-pkcs7-certreqresp,
|
1031
|
-
p7s,application/pkcs7-signature,
|
1032
|
-
pbm,image/x-portable-bitmap,
|
1033
|
-
pcast,application/x-podcast,
|
1034
|
-
pct,image/pict,
|
1035
|
-
pcx,application/octet-stream,
|
1036
|
-
pcz,application/octet-stream,
|
1037
|
-
pdf,application/pdf,
|
1038
|
-
pfb,application/octet-stream,
|
1039
|
-
pfm,application/octet-stream,
|
1040
|
-
pfx,application/x-pkcs12,
|
1041
|
-
pgm,image/x-portable-graymap,
|
1042
|
-
pic,image/pict,
|
1043
|
-
pict,image/pict,
|
1044
|
-
pkgdef,text/plain,
|
1045
|
-
pkgundef,text/plain,
|
1046
|
-
pko,application/vnd.ms-pki.pko,
|
1047
|
-
pls,audio/scpls,
|
1048
|
-
pma,application/x-perfmon,
|
1049
|
-
pmc,application/x-perfmon,
|
1050
|
-
pml,application/x-perfmon,
|
1051
|
-
pmr,application/x-perfmon,
|
1052
|
-
pmw,application/x-perfmon,
|
1053
|
-
png,image/png,
|
1054
|
-
pnm,image/x-portable-anymap,
|
1055
|
-
pnt,image/x-macpaint,
|
1056
|
-
pntg,image/x-macpaint,
|
1057
|
-
pnz,image/png,
|
1058
|
-
pot,application/vnd.ms-powerpoint,
|
1059
|
-
potm,application/vnd.ms-powerpoint.template.macroEnabled.12,
|
1060
|
-
potx,application/vnd.openxmlformats-officedocument.presentationml.template,
|
1061
|
-
ppa,application/vnd.ms-powerpoint,
|
1062
|
-
ppam,application/vnd.ms-powerpoint.addin.macroEnabled.12,
|
1063
|
-
ppm,image/x-portable-pixmap,
|
1064
|
-
pps,application/vnd.ms-powerpoint,
|
1065
|
-
ppsm,application/vnd.ms-powerpoint.slideshow.macroEnabled.12,
|
1066
|
-
ppsx,application/vnd.openxmlformats-officedocument.presentationml.slideshow,
|
1067
|
-
ppt,application/vnd.ms-powerpoint,
|
1068
|
-
pptm,application/vnd.ms-powerpoint.presentation.macroEnabled.12,
|
1069
|
-
pptx,application/vnd.openxmlformats-officedocument.presentationml.presentation,
|
1070
|
-
prf,application/pics-rules,
|
1071
|
-
prm,application/octet-stream,
|
1072
|
-
prx,application/octet-stream,
|
1073
|
-
ps,application/postscript,
|
1074
|
-
psc1,application/PowerShell,
|
1075
|
-
psd,application/octet-stream,
|
1076
|
-
psess,application/xml,
|
1077
|
-
psm,application/octet-stream,
|
1078
|
-
psp,application/octet-stream,
|
1079
|
-
pub,application/x-mspublisher,
|
1080
|
-
pwz,application/vnd.ms-powerpoint,
|
1081
|
-
qht,text/x-html-insertion,
|
1082
|
-
qhtm,text/x-html-insertion,
|
1083
|
-
qt,video/quicktime,
|
1084
|
-
qti,image/x-quicktime,
|
1085
|
-
qtif,image/x-quicktime,
|
1086
|
-
qtl,application/x-quicktimeplayer,
|
1087
|
-
qxd,application/octet-stream,
|
1088
|
-
ra,audio/x-pn-realaudio,
|
1089
|
-
ram,audio/x-pn-realaudio,
|
1090
|
-
rar,application/octet-stream,
|
1091
|
-
ras,image/x-cmu-raster,
|
1092
|
-
rat,application/rat-file,
|
1093
|
-
rc,text/plain,
|
1094
|
-
rc2,text/plain,
|
1095
|
-
rct,text/plain,
|
1096
|
-
rdlc,application/xml,
|
1097
|
-
resx,application/xml,
|
1098
|
-
rf,image/vnd.rn-realflash,
|
1099
|
-
rgb,image/x-rgb,
|
1100
|
-
rgs,text/plain,
|
1101
|
-
rm,application/vnd.rn-realmedia,
|
1102
|
-
rmi,audio/mid,
|
1103
|
-
rmp,application/vnd.rn-rn_music_package,
|
1104
|
-
roff,application/x-troff,
|
1105
|
-
rpm,audio/x-pn-realaudio-plugin,
|
1106
|
-
rqy,text/x-ms-rqy,
|
1107
|
-
rtf,application/rtf,
|
1108
|
-
rtx,text/richtext,
|
1109
|
-
ruleset,application/xml,
|
1110
|
-
s,text/plain,
|
1111
|
-
safariextz,application/x-safari-safariextz,
|
1112
|
-
scd,application/x-msschedule,
|
1113
|
-
sct,text/scriptlet,
|
1114
|
-
sd2,audio/x-sd2,
|
1115
|
-
sdp,application/sdp,
|
1116
|
-
sea,application/octet-stream,
|
1117
|
-
searchConnector-ms,application/windows-search-connector+xml,
|
1118
|
-
setpay,application/set-payment-initiation,
|
1119
|
-
setreg,application/set-registration-initiation,
|
1120
|
-
settings,application/xml,
|
1121
|
-
sgimb,application/x-sgimb,
|
1122
|
-
sgml,text/sgml,
|
1123
|
-
sh,application/x-sh,
|
1124
|
-
shar,application/x-shar,
|
1125
|
-
shtml,text/html,
|
1126
|
-
sit,application/x-stuffit,
|
1127
|
-
sitemap,application/xml,
|
1128
|
-
skin,application/xml,
|
1129
|
-
sldm,application/vnd.ms-powerpoint.slide.macroEnabled.12,
|
1130
|
-
sldx,application/vnd.openxmlformats-officedocument.presentationml.slide,
|
1131
|
-
slk,application/vnd.ms-excel,
|
1132
|
-
sln,text/plain,
|
1133
|
-
slupkg-ms,application/x-ms-license,
|
1134
|
-
smd,audio/x-smd,
|
1135
|
-
smi,application/octet-stream,
|
1136
|
-
smx,audio/x-smd,
|
1137
|
-
smz,audio/x-smd,
|
1138
|
-
snd,audio/basic,
|
1139
|
-
snippet,application/xml,
|
1140
|
-
snp,application/octet-stream,
|
1141
|
-
sol,text/plain,
|
1142
|
-
sor,text/plain,
|
1143
|
-
spc,application/x-pkcs7-certificates,
|
1144
|
-
spl,application/futuresplash,
|
1145
|
-
src,application/x-wais-source,
|
1146
|
-
srf,text/plain,
|
1147
|
-
SSISDeploymentManifest,text/xml,
|
1148
|
-
ssm,application/streamingmedia,
|
1149
|
-
sst,application/vnd.ms-pki.certstore,
|
1150
|
-
stl,application/vnd.ms-pki.stl,
|
1151
|
-
sv4cpio,application/x-sv4cpio,
|
1152
|
-
sv4crc,application/x-sv4crc,
|
1153
|
-
svc,application/xml,
|
1154
|
-
swf,application/x-shockwave-flash,
|
1155
|
-
t,application/x-troff,
|
1156
|
-
tar,application/x-tar,
|
1157
|
-
tcl,application/x-tcl,
|
1158
|
-
testrunconfig,application/xml,
|
1159
|
-
testsettings,application/xml,
|
1160
|
-
tex,application/x-tex,
|
1161
|
-
texi,application/x-texinfo,
|
1162
|
-
texinfo,application/x-texinfo,
|
1163
|
-
tgz,application/x-compressed,
|
1164
|
-
thmx,application/vnd.ms-officetheme,
|
1165
|
-
thn,application/octet-stream,
|
1166
|
-
tif,image/tiff,
|
1167
|
-
tiff,image/tiff,
|
1168
|
-
tlh,text/plain,
|
1169
|
-
tli,text/plain,
|
1170
|
-
toc,application/octet-stream,
|
1171
|
-
tr,application/x-troff,
|
1172
|
-
trm,application/x-msterminal,
|
1173
|
-
trx,application/xml,
|
1174
|
-
ts,video/vnd.dlna.mpeg-tts,
|
1175
|
-
tsv,text/tab-separated-values,
|
1176
|
-
ttf,application/octet-stream,
|
1177
|
-
tts,video/vnd.dlna.mpeg-tts,
|
1178
|
-
txt,text/plain,
|
1179
|
-
u32,application/octet-stream,
|
1180
|
-
uls,text/iuls,
|
1181
|
-
user,text/plain,
|
1182
|
-
ustar,application/x-ustar,
|
1183
|
-
vb,text/plain,
|
1184
|
-
vbdproj,text/plain,
|
1185
|
-
vbk,video/mpeg,
|
1186
|
-
vbproj,text/plain,
|
1187
|
-
vbs,text/vbscript,
|
1188
|
-
vcf,text/x-vcard,
|
1189
|
-
vcproj,Application/xml,
|
1190
|
-
vcs,text/plain,
|
1191
|
-
vcxproj,Application/xml,
|
1192
|
-
vddproj,text/plain,
|
1193
|
-
vdp,text/plain,
|
1194
|
-
vdproj,text/plain,
|
1195
|
-
vdx,application/vnd.ms-visio.viewer,
|
1196
|
-
vml,text/xml,
|
1197
|
-
vscontent,application/xml,
|
1198
|
-
vsct,text/xml,
|
1199
|
-
vsd,application/vnd.visio,
|
1200
|
-
vsi,application/ms-vsi,
|
1201
|
-
vsix,application/vsix,
|
1202
|
-
vsixlangpack,text/xml,
|
1203
|
-
vsixmanifest,text/xml,
|
1204
|
-
vsmdi,application/xml,
|
1205
|
-
vspscc,text/plain,
|
1206
|
-
vss,application/vnd.visio,
|
1207
|
-
vsscc,text/plain,
|
1208
|
-
vssettings,text/xml,
|
1209
|
-
vssscc,text/plain,
|
1210
|
-
vst,application/vnd.visio,
|
1211
|
-
vstemplate,text/xml,
|
1212
|
-
vsto,application/x-ms-vsto,
|
1213
|
-
vsw,application/vnd.visio,
|
1214
|
-
vsx,application/vnd.visio,
|
1215
|
-
vtx,application/vnd.visio,
|
1216
|
-
wav,audio/wav,
|
1217
|
-
wave,audio/wav,
|
1218
|
-
wax,audio/x-ms-wax,
|
1219
|
-
wbk,application/msword,
|
1220
|
-
wbmp,image/vnd.wap.wbmp,
|
1221
|
-
wcm,application/vnd.ms-works,
|
1222
|
-
wdb,application/vnd.ms-works,
|
1223
|
-
wdp,image/vnd.ms-photo,
|
1224
|
-
webarchive,application/x-safari-webarchive,
|
1225
|
-
webtest,application/xml,
|
1226
|
-
wiq,application/xml,
|
1227
|
-
wiz,application/msword,
|
1228
|
-
wks,application/vnd.ms-works,
|
1229
|
-
WLMP,application/wlmoviemaker,
|
1230
|
-
wlpginstall,application/x-wlpg-detect,
|
1231
|
-
wlpginstall3,application/x-wlpg3-detect,
|
1232
|
-
wm,video/x-ms-wm,
|
1233
|
-
wma,audio/x-ms-wma,
|
1234
|
-
wmd,application/x-ms-wmd,
|
1235
|
-
wmf,application/x-msmetafile,
|
1236
|
-
wml,text/vnd.wap.wml,
|
1237
|
-
wmlc,application/vnd.wap.wmlc,
|
1238
|
-
wmls,text/vnd.wap.wmlscript,
|
1239
|
-
wmlsc,application/vnd.wap.wmlscriptc,
|
1240
|
-
wmp,video/x-ms-wmp,
|
1241
|
-
wmv,video/x-ms-wmv,
|
1242
|
-
wmx,video/x-ms-wmx,
|
1243
|
-
wmz,application/x-ms-wmz,
|
1244
|
-
wpl,application/vnd.ms-wpl,
|
1245
|
-
wps,application/vnd.ms-works,
|
1246
|
-
wri,application/x-mswrite,
|
1247
|
-
wrl,x-world/x-vrml,
|
1248
|
-
wrz,x-world/x-vrml,
|
1249
|
-
wsc,text/scriptlet,
|
1250
|
-
wsdl,text/xml,
|
1251
|
-
wvx,video/x-ms-wvx,
|
1252
|
-
x,application/directx,
|
1253
|
-
xaf,x-world/x-vrml,
|
1254
|
-
xaml,application/xaml+xml,
|
1255
|
-
xap,application/x-silverlight-app,
|
1256
|
-
xbap,application/x-ms-xbap,
|
1257
|
-
xbm,image/x-xbitmap,
|
1258
|
-
xdr,text/plain,
|
1259
|
-
xht,application/xhtml+xml,
|
1260
|
-
xhtml,application/xhtml+xml,
|
1261
|
-
xla,application/vnd.ms-excel,
|
1262
|
-
xlam,application/vnd.ms-excel.addin.macroEnabled.12,
|
1263
|
-
xlc,application/vnd.ms-excel,
|
1264
|
-
xld,application/vnd.ms-excel,
|
1265
|
-
xlk,application/vnd.ms-excel,
|
1266
|
-
xll,application/vnd.ms-excel,
|
1267
|
-
xlm,application/vnd.ms-excel,
|
1268
|
-
xls,application/vnd.ms-excel,
|
1269
|
-
xlsb,application/vnd.ms-excel.sheet.binary.macroEnabled.12,
|
1270
|
-
xlsm,application/vnd.ms-excel.sheet.macroEnabled.12,
|
1271
|
-
xlsx,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,
|
1272
|
-
xlt,application/vnd.ms-excel,
|
1273
|
-
xltm,application/vnd.ms-excel.template.macroEnabled.12,
|
1274
|
-
xltx,application/vnd.openxmlformats-officedocument.spreadsheetml.template,
|
1275
|
-
xlw,application/vnd.ms-excel,
|
1276
|
-
xml,text/xml,
|
1277
|
-
xmta,application/xml,
|
1278
|
-
xof,x-world/x-vrml,
|
1279
|
-
XOML,text/plain,
|
1280
|
-
xpm,image/x-xpixmap,
|
1281
|
-
xps,application/vnd.ms-xpsdocument,
|
1282
|
-
xrm-ms,text/xml,
|
1283
|
-
xsc,application/xml,
|
1284
|
-
xsd,text/xml,
|
1285
|
-
xsf,text/xml,
|
1286
|
-
xsl,text/xml,
|
1287
|
-
xslt,text/xml,
|
1288
|
-
xsn,application/octet-stream,
|
1289
|
-
xss,application/xml,
|
1290
|
-
xtp,application/octet-stream,
|
1291
|
-
xwd,image/x-xwindowdump,
|
1292
|
-
z,application/x-compress,
|
1293
|
-
zip,application/x-zip-compressed )
|
1294
|
-
content_types.each do |type_info|
|
1295
|
-
if ext == ".#{type_info.split(',').first}"
|
1296
|
-
content_type = type_info.split(',')[1]
|
1297
|
-
return content_type
|
946
|
+
#
|
947
|
+
# Detect WAF and SSRF protection libraries based on common strings in the response body
|
948
|
+
#
|
949
|
+
# @param [String] response HTTP response
|
950
|
+
#
|
951
|
+
# @return [Boolean] true if WAF detected
|
952
|
+
#
|
953
|
+
def detect_waf(response)
|
954
|
+
detected = false
|
955
|
+
# SafeCurl (safe_curl) InvalidURLException
|
956
|
+
if response =~ /fin1te\\SafeCurl\\Exception\\InvalidURLException/
|
957
|
+
logger.info('SafeCurl protection mechanism appears to be in use')
|
958
|
+
detected = true
|
1298
959
|
end
|
960
|
+
detected
|
1299
961
|
end
|
1300
|
-
nil
|
1301
|
-
end
|
1302
962
|
|
1303
|
-
|
963
|
+
#
|
964
|
+
# Guess content type based on file extension
|
965
|
+
#
|
966
|
+
# @param [String] ext File extension [with dots] (Example: '.png')
|
967
|
+
#
|
968
|
+
# @return [String] content-type value
|
969
|
+
#
|
970
|
+
def guess_mime(ext)
|
971
|
+
content_types = WEBrick::HTTPUtils::DefaultMimeTypes
|
972
|
+
common_content_types = {
|
973
|
+
'ico' => 'image/x-icon' }
|
974
|
+
content_types.merge!(common_content_types)
|
975
|
+
content_types.each do |k, v|
|
976
|
+
return v.to_s if ext == ".#{k}"
|
977
|
+
end
|
978
|
+
nil
|
979
|
+
end
|
1304
980
|
|
1305
|
-
|
981
|
+
# private methods
|
982
|
+
private :parse_options,
|
983
|
+
:send_http_request,
|
984
|
+
:run_rules,
|
985
|
+
:encode_ip,
|
986
|
+
:guess_mime,
|
987
|
+
:guess_status,
|
988
|
+
:detect_waf
|
989
|
+
end
|
1306
990
|
end
|