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.
@@ -1,1306 +1,990 @@
1
- #!/usr/bin/env ruby
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
- # @note SSRFProxy::HTTP
13
- #
14
- class HTTP
15
- attr_accessor = :logger
16
-
17
- # @note output status messages
18
- def print_status(msg='')
19
- puts '[*] '.blue + msg
20
- end
21
-
22
- # @note output progress messages
23
- def print_good(msg='')
24
- puts '[+] '.green + msg
25
- end
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
- # url parsing
28
- require 'net/http'
29
- require 'uri'
30
- require 'cgi'
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
- # client http request parsing
33
- require 'webrick'
34
- require 'stringio'
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
- # rules
37
- require 'digest'
38
- require 'base64'
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
- # ip encoding
41
- require 'ipaddress'
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
- # @note logger
44
- def logger
45
- @logger || ::Logger.new(STDOUT).tap do |log|
46
- log.progname = 'ssrf-proxy'
47
- log.level = ::Logger::WARN
48
- log.datetime_format = '%Y-%m-%d %H:%M:%S '
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
- # @note SSRFProxy:HTTP errors
54
- #
55
- module Error
56
- # custom errors
57
- class Error < StandardError; end
58
- exceptions = %w(
59
- NoUrlPlaceholder
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
- # @note SSRFProxy::HTTP
71
- #
72
- # @options
73
- # - url - String - SSRF URL with 'xxURLxx' placeholder
74
- # - opts - Hash - SSRF and HTTP connection options:
75
- # - 'proxy' => String
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
- begin
98
- @ssrf_url = URI::parse(url.to_s)
99
- rescue URI::InvalidURIError
100
- raise SSRFProxy::HTTP::Error::InvalidSsrfRequest.new,
101
- "Invalid SSRF request specified."
261
+
262
+ #
263
+ # Host accessor
264
+ #
265
+ # @return [String] SSRF host
266
+ #
267
+ def host
268
+ @ssrf_url.host
102
269
  end
103
- if @ssrf_url.scheme.nil? || @ssrf_url.host.nil? || @ssrf_url.port.nil?
104
- raise SSRFProxy::HTTP::Error::InvalidSsrfRequest.new,
105
- "Invalid SSRF request specified."
270
+
271
+ #
272
+ # Port accessor
273
+ #
274
+ # @return [String] SSRF host port
275
+ #
276
+ def port
277
+ @ssrf_url.port
106
278
  end
107
- if @ssrf_url.scheme !~ /\Ahttps?\z/
108
- raise SSRFProxy::HTTP::Error::InvalidSsrfRequest.new,
109
- "Invalid SSRF request specified. Scheme must be http(s)."
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
- # SSRF options
113
- @upstream_proxy = nil
114
- @method = 'GET'
115
- @post_data = ''
116
- @ip_encoding = nil
117
- @rules = []
118
- @forward_cookies = false
119
- @body_to_uri = false
120
- @auth_to_uri = false
121
- opts.each do |option, value|
122
- next if value.eql?('')
123
- case option
124
- when 'proxy'
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
- @upstream_proxy = URI::parse(value)
127
- rescue URI::InvalidURIError => e
128
- raise SSRFProxy::HTTP::Error::InvalidUpstreamProxy.new,
129
- "Invalid upstream HTTP proxy specified."
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
- if @upstream_proxy.scheme !~ /\Ahttps?\z/
132
- raise SSRFProxy::HTTP::Error::InvalidUpstreamProxy.new,
133
- "Invalid upstream HTTP proxy specified."
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
- when 'method'
136
- if @method !~ /\A(get|post|head)+?\z/i
137
- raise SSRFProxy::HTTP::Error::InvalidRequestMethod.new,
138
- "Invalid SSRF request method specified. Method must be GET/POST/HEAD."
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
- @method = 'GET' if value =~ /\Aget\z/i
141
- @method = 'POST' if value =~ /\Apost\z/i
142
- @method = 'HEAD' if value =~ /\Ahead\z/i
143
- when 'post_data'
144
- @post_data = value.to_s
145
- when 'ip_encoding'
146
- if value.to_s !~ /\A[a-z0-9]+\z/i
147
- raise SSRFProxy::HTTP::Error::InvalidIpEncoding.new,
148
- "Invalid IP encoding method specified."
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
- end
161
- if @ssrf_url.request_uri !~ /xxURLxx/ && @post_data.to_s !~ /xxURLxx/
162
- raise SSRFProxy::HTTP::Error::NoUrlPlaceholder.new,
163
- "You must specify a URL placeholder with 'xxURLxx' in the SSRF request"
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
- # HTTP connection options
167
- @cookie = nil
168
- @timeout = 10
169
- @user_agent = 'Mozilla/5.0'
170
- opts.each do |option, value|
171
- next if value.eql?('')
172
- case option
173
- when 'cookie'
174
- @cookie = value.to_s
175
- when 'timeout'
176
- @timeout = value.to_i
177
- when 'user_agent'
178
- @user_agent = value.to_s
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
- # HTTP response modification options
183
- @match_regex = "\\A(.+)\\z"
184
- @strip = []
185
- @guess_status = false
186
- @guess_mime = false
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
- end
431
+ # run target url through rules
432
+ target_uri = run_rules(encoded_uri, @rules)
202
433
 
203
- #
204
- # @note URL accessor
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
- # @note Host accessor
214
- #
215
- # @returns - String - SSRF host
216
- #
217
- def host
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
- # @note Port accessor
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
- # @note Cookie accessor
232
- #
233
- # @returns - String - SSRF request cookie
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
- # @note Upstream proxy accessor
241
- #
242
- # @returns - String - Upstream proxy
243
- #
244
- def proxy
245
- @upstream_proxy
246
- end
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
- # @note Encode IP address of a given URL
250
- #
251
- # @options
252
- # - url - String - target url
253
- # - mode - String - encoding (int, ipv6, oct, hex)
254
- #
255
- # @returns - String - encoded ip address
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
- # @note Run a specified URL through SSRF rules
284
- #
285
- # @options
286
- # - url - String - request URL
287
- # - rules - String - comma separated list of rules
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
- # @note Format a HTTP request as a URL and request via SSRF
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
- logger.warn("No host specified")
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
- end
352
- opts = {}
353
- begin
354
- # change POST to GET if the request body is empty
355
- if request.to_s =~ /\APOST /
356
- request = request.gsub!(/\APOST /, 'GET ') if request.split(/\r?\n\r?\n/)[1].nil?
357
- end
358
- # parse request
359
- req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP)
360
- req.parse(StringIO.new(request))
361
- if req.to_s =~ /^Upgrade: WebSocket/
362
- logger.warn("WebSocket tunneling is not supported: #{req.host}:#{req.port}")
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
- # parse request body and move to uri
373
- if @body_to_uri && !req.body.nil?
374
- logger.debug "Parsing request body: #{req.body}"
375
- begin
376
- new_query = URI.decode_www_form(req.body)
377
- if req.query_string.nil?
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
- URI.decode_www_form(req.query_string).each { |p| new_query << p }
381
- uri = "#{uri}&#{URI.encode_www_form(new_query)}"
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
- # move basic authentication credentials to uri
389
- if @auth_to_uri && !req.header.nil?
390
- req.header['authorization'].each do |header|
391
- next unless header.split(' ').first =~ /^basic$/i
392
- begin
393
- creds = header.split(' ')[1]
394
- user = Base64.decode64(creds).chomp
395
- logger.info "Using basic authentication credentials: #{user}"
396
- uri = uri.to_s.gsub!(/:(\/\/)/, "://#{user}@")
397
- rescue
398
- logger.warn "Could not parse request authorization header: #{header}"
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
- # forward client cookies
405
- new_cookie = []
406
- new_cookie << @cookie unless @cookie.nil?
407
- if @forward_cookies
408
- req.cookies.each do |c|
409
- new_cookie << "#{c}"
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
- # @note Fetch a URI via SSRF
421
- #
422
- # @options
423
- # - uri - URI - URI to fetch
424
- # - opts - Hash - request options (keys: cookie)
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
- # convert ip
432
- if @ip_encoding
433
- encoded_url = encode_ip(uri, @ip_encoding)
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
- # send request
438
- status_msg = "Request -> #{@method}"
439
- status_msg << " -> PROXY[#{@upstream_proxy.host}:#{@upstream_proxy.port}]" unless @upstream_proxy.nil?
440
- status_msg << " -> SSRF[#{@ssrf_url.host}:#{@ssrf_url.port}] -> URI[#{uri}]"
441
- print_status(status_msg)
442
- response = send_http_request(uri, opts)
443
- response = parse_http_response(response) unless response.class == Hash
444
- body = response["body"]||''
445
- headers = response["headers"]
446
-
447
- # handle HTTP response
448
- if response["status"] == 'fail'
449
- status_msg = "Response <- #{response["code"]}"
450
- status_msg << " <- PROXY[#{@upstream_proxy.host}:#{@upstream_proxy.port}]" unless @upstream_proxy.nil?
451
- status_msg << " <- SSRF[#{@ssrf_url.host}:#{@ssrf_url.port}] <- URI[#{uri}]"
452
- print_status(status_msg)
453
- return "#{response['headers']}#{response['body']}"
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
- # guess mime type and add content-type header
457
- if @guess_mime
458
- content_type = guess_mime(File.extname(uri.to_s.split('?').first))
459
- unless content_type.nil?
460
- logger.info "Using content-type: #{content_type}"
461
- if headers =~ /^content\-type:.*$/i
462
- headers.gsub!(/^content\-type:.*$/i, "Content-Type: #{content_type}")
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
- headers.gsub!(/\n\n\z/, "\nContent-Type: #{content_type}\n\n")
651
+ logger.warn("Unknown rule: #{rule}")
465
652
  end
466
653
  end
654
+ str
467
655
  end
468
656
 
469
- # match response content
470
- unless @match_regex.nil?
471
- matches = body.scan(/#{@match_regex}/m)
472
- if matches.length
473
- body = matches.flatten.first.to_s
474
- logger.info "Response matches pattern '#{@match_regex}'"
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
- logger.warn "Response does not match pattern"
684
+ raise SSRFProxy::HTTP::Error::InvalidUpstreamProxy.new,
685
+ 'Unsupported upstream proxy specified. Scheme must be http(s) or socks.'
477
686
  end
478
- end
479
-
480
- # set content length
481
- content_length = body.to_s.length
482
- if headers =~ /^transfer\-encoding:.*$/i
483
- headers.gsub!(/^transfer\-encoding:.*$/i, "Content-Length: #{content_length}")
484
- elsif headers =~ /^content\-length:.*$/i
485
- headers.gsub!(/^content\-length:.*$/i, "Content-Length: #{content_length}")
486
- else
487
- headers.gsub!(/\n\n\z/, "\nContent-Length: #{content_length}\n\n")
488
- end
489
-
490
- # return HTTP response
491
- logger.debug("Response:\n#{headers}#{body}")
492
- status_msg = "Response <- #{response["code"]}"
493
- status_msg << " <- PROXY[#{@upstream_proxy.host}:#{@upstream_proxy.port}]" unless @upstream_proxy.nil?
494
- status_msg << " <- SSRF[#{@ssrf_url.host}:#{@ssrf_url.port}] <- URI[#{uri}]"
495
- status_msg << " -- TITLE[#{$1}]" if body[0..1024] =~ /<title>([^<]*)<\/title>/im
496
- status_msg << " -- SIZE[#{body.size} bytes]"
497
- print_good(status_msg)
498
- return "#{headers}#{body}"
499
- end
500
-
501
- #
502
- # @note Send HTTP request
503
- #
504
- # @options
505
- # - url - String - URI to fetch
506
- # - opts - Hash - request options (keys: cookie)
507
- #
508
- # @returns Hash of HTTP response (status, code, headers, body)
509
- #
510
- def send_http_request(url, opts={})
511
- # use upstream proxy
512
- if @upstream_proxy.nil?
513
- http = Net::HTTP.new(@ssrf_url.host, @ssrf_url.port)
514
- else
515
- http = Net::HTTP::Proxy(@upstream_proxy.host, @upstream_proxy.port).new(@ssrf_url.host, @ssrf_url.port)
516
- end
517
- # run target url through rules
518
- target = run_rules(url, @rules)
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
- rescue Timeout::Error,Errno::ETIMEDOUT
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
- # @note Parse HTTP response
571
- #
572
- # @options
573
- # - response - Net::HTTPResponse - HTTP response object
574
- #
575
- # @returns - Hash - HTTP response object
576
- #
577
- def parse_http_response(response)
578
- return response if response.class == Hash
579
- result = {}
580
- begin
581
- result["status"] = 'complete'
582
- result["http_version"] = response.http_version
583
- result["code"] = response.code
584
- result["message"] = response.message
585
- if @guess_status
586
- head = response.body[0..4096]
587
- # generic page titles containing HTTP status
588
- if head =~ />401 Unauthorized</
589
- result["code"] = 401
590
- result["message"] = 'Unauthorized'
591
- elsif head =~ />403 Forbidden</
592
- result["code"] = 403
593
- result["message"] = 'Forbidden'
594
- elsif head =~ />404 Not Found</
595
- result["code"] = 404
596
- result["message"] = 'Not Found'
597
- elsif head =~ />500 Internal Server Error</
598
- result["code"] = 500
599
- result["message"] = 'Internal Server Error'
600
- # getaddrinfo() errors
601
- elsif head =~ /getaddrinfo: /
602
- if head =~ /getaddrinfo: nodename nor servname provided/
603
- result["code"] = 502
604
- result["message"] = 'Bad Gateway'
605
- elsif head =~ /getaddrinfo: Name or service not known/
606
- result["code"] = 502
607
- result["message"] = 'Bad Gateway'
608
- end
609
- # getnameinfo() errors
610
- elsif head =~ /getnameinfo failed: /
611
- result["code"] = 502
612
- result["message"] = 'Bad Gateway'
613
- # PHP 'failed to open stream' errors
614
- elsif head =~ /failed to open stream: /
615
- # HTTP request failed! HTTP/[version] [code] [message]
616
- if head =~ /failed to open stream: HTTP request failed! HTTP\/(0\.9|1\.0|1\.1) ([\d]+) /
617
- result["code"] = "#{$2}"
618
- result["message"] = ''
619
- if head =~ /failed to open stream: HTTP request failed! HTTP\/(0\.9|1\.0|1\.1) [\d]+ ([a-zA-Z ]+)/
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
- elsif head =~ /(Connection refused|No route to host) - connect\(\d\)/
687
- # Connection refused
688
- if head =~ /Connection refused - connect\(\d\)/
689
- result["code"] = 502
690
- result["message"] = 'Bad Gateway'
691
- # No route to host
692
- elsif head =~ /No route to host - connect\(\d\)/
693
- result["code"] = 502
694
- result["message"] = 'Bad Gateway'
695
- # Connection timed out
696
- elsif head =~ /Connection timed out - connect\(\d\)/
697
- result["code"] = 504
698
- result["message"] = 'Timeout'
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
- logger.info "Using HTTP response status: #{result["code"]} #{result["message"]}"
702
- end
703
- result["headers"] = "HTTP\/#{result["http_version"]} #{result["code"]} #{result["message"]}\n"
704
- response.each_header do |header_name, header_value|
705
- if @strip.include?(header_name.downcase)
706
- logger.info "Removed response header: #{header_name}"
707
- next
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["headers"] << "\n"
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
- # @note Guess content type based on file extension
725
- # - List from: https://stackoverflow.com/questions/1029740/get-mime-type-from-filename-extension
726
- #
727
- # @options
728
- # - ext - String - File extension [with dots] (Example: '.png')
729
- #
730
- # @returns String - content-type value
731
- #
732
- def guess_mime(ext)
733
- content_types = %w(
734
- 323,text/h323,
735
- 3g2,video/3gpp2,
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
- private :parse_http_response,:send_http_request,:run_rules,:encode_ip,:guess_mime
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
- end
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