ssrf_proxy 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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