ssrf_filter 1.0.6 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 2e7c6b603a075892984767c0e84bdc31acef8a94
4
- data.tar.gz: ad328c0f06c37ab4ce7f33610e5f6b16ee0a3537
2
+ SHA256:
3
+ metadata.gz: 97b1c63593031040d2f23b6fbb055c44490fc2624a5b9901203053c971991ba6
4
+ data.tar.gz: ca816401abe0177feea5fd70d01afd19758598cd9864659a8eb97e7184367830
5
5
  SHA512:
6
- metadata.gz: 553c787de96785842f54c22f4ca9ae85b417ce38139ee93daa95deea1f0ca39383150bc242099d63fa4cea9161508cc4f58e27e06bcc5958bca1d58e7fed1744
7
- data.tar.gz: f010ffafa1cdf23f45426f33b37eda10df5ad4ac27bef1e4d79851728c2ce3a24e52b62cb8b2c03a11400a0b7db29329672d6be101562d03152df3f0a1641eb5
6
+ metadata.gz: 75ae3f265b486cb8768ef0c49d3dd0313a218b3863a2c1dfc8615727453aea45ab7ae5f71289e3748eecb9e17dc09443870413937c7fbad56a9a7a225b2fbb89
7
+ data.tar.gz: c881ea003dafd105bbbff4001646a879fc4cefb18d722fc60f21281c8ebaca8b19a324dccadb9acdf799e56501905addebcfe8f91253ee52136a9dab433335ad
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class SsrfFilter
2
4
  module Patch
3
5
  module SSLSocket
@@ -27,6 +29,7 @@ class SsrfFilter
27
29
 
28
30
  def self.apply!
29
31
  return if instance_variable_defined?(:@patched_ssl_socket)
32
+
30
33
  @patched_ssl_socket = true
31
34
 
32
35
  ::OpenSSL::SSL::SSLSocket.class_eval do
@@ -10,7 +10,9 @@ class SsrfFilter
10
10
  mask_addr = ipaddr.instance_variable_get('@mask_addr')
11
11
  raise ArgumentError, 'Invalid mask' if mask_addr.zero?
12
12
 
13
- mask_addr >>= 1 while (mask_addr & 0x1).zero?
13
+ while (mask_addr & 0x1).zero?
14
+ mask_addr >>= 1
15
+ end
14
16
 
15
17
  length = 0
16
18
  while mask_addr & 0x1 == 0x1
@@ -53,11 +55,12 @@ class SsrfFilter
53
55
  ::IPAddr.new('2002::/16'), # 6to4
54
56
  ::IPAddr.new('fc00::/7'), # Unique local address
55
57
  ::IPAddr.new('fe80::/10'), # Link-local address
56
- ::IPAddr.new('ff00::/8'), # Multicast
58
+ ::IPAddr.new('ff00::/8') # Multicast
57
59
  ] + IPV4_BLACKLIST.flat_map do |ipaddr|
58
60
  prefixlen = prefixlen_from_ipaddr(ipaddr)
59
61
 
60
- ipv4_compatible = ipaddr.ipv4_compat.mask(96 + prefixlen)
62
+ # Don't call ipaddr.ipv4_compat because it prints out a deprecation warning on ruby 2.5+
63
+ ipv4_compatible = IPAddr.new(ipaddr.to_i, Socket::AF_INET6).mask(96 + prefixlen)
61
64
  ipv4_mapped = ipaddr.ipv4_mapped.mask(80 + prefixlen)
62
65
 
63
66
  [ipv4_compatible, ipv4_mapped]
@@ -75,7 +78,9 @@ class SsrfFilter
75
78
  get: ::Net::HTTP::Get,
76
79
  put: ::Net::HTTP::Put,
77
80
  post: ::Net::HTTP::Post,
78
- delete: ::Net::HTTP::Delete
81
+ delete: ::Net::HTTP::Delete,
82
+ head: ::Net::HTTP::Head,
83
+ patch: ::Net::HTTP::Patch
79
84
  }.freeze
80
85
 
81
86
  FIBER_LOCAL_KEY = :__ssrf_filter_hostname
@@ -98,10 +103,9 @@ class SsrfFilter
98
103
  class CRLFInjection < Error
99
104
  end
100
105
 
101
- %i[get put post delete].each do |method|
106
+ %i[get put post delete head patch].each do |method|
102
107
  define_singleton_method(method) do |url, options = {}, &block|
103
108
  ::SsrfFilter::Patch::SSLSocket.apply!
104
- ::SsrfFilter::Patch::HTTPGenericRequest.apply!
105
109
 
106
110
  original_url = url
107
111
  scheme_whitelist = options[:scheme_whitelist] || DEFAULT_SCHEME_WHITELIST
@@ -123,16 +127,8 @@ class SsrfFilter
123
127
  public_addresses = ip_addresses.reject(&method(:unsafe_ip_address?))
124
128
  raise PrivateIPAddress, "Hostname '#{hostname}' has no public ip addresses" if public_addresses.empty?
125
129
 
126
- response = fetch_once(uri, public_addresses.sample.to_s, method, options, &block)
127
-
128
- case response
129
- when ::Net::HTTPRedirection then
130
- url = response['location']
131
- # Handle relative redirects
132
- url = "#{uri.scheme}://#{hostname}:#{uri.port}#{url}" if url.start_with?('/')
133
- else
134
- return response
135
- end
130
+ response, url = fetch_once(uri, public_addresses.sample.to_s, method, options, &block)
131
+ return response if url.nil?
136
132
  end
137
133
 
138
134
  raise TooManyRedirects, "Got #{max_redirects} redirects fetching #{original_url}"
@@ -168,7 +164,7 @@ class SsrfFilter
168
164
 
169
165
  def self.fetch_once(uri, ip, verb, options, &block)
170
166
  if options[:params]
171
- params = uri.query ? ::Hash[::URI.decode_www_form(uri.query)] : {}
167
+ params = uri.query ? ::URI.decode_www_form(uri.query).to_h : {}
172
168
  params.merge!(options[:params])
173
169
  uri.query = ::URI.encode_www_form(params)
174
170
  end
@@ -185,13 +181,26 @@ class SsrfFilter
185
181
 
186
182
  request.body = options[:body] if options[:body]
187
183
 
188
- block.call(request) if block_given?
184
+ options[:request_proc].call(request) if options[:request_proc].respond_to?(:call)
189
185
  validate_request(request)
190
186
 
191
- use_ssl = uri.scheme == 'https'
187
+ http_options = options[:http_options] || {}
188
+ http_options[:use_ssl] = (uri.scheme == 'https')
189
+
192
190
  with_forced_hostname(hostname) do
193
- ::Net::HTTP.start(uri.hostname, uri.port, use_ssl: use_ssl) do |http|
194
- http.request(request)
191
+ ::Net::HTTP.start(uri.hostname, uri.port, **http_options) do |http|
192
+ http.request(request) do |response|
193
+ case response
194
+ when ::Net::HTTPRedirection
195
+ url = response['location']
196
+ # Handle relative redirects
197
+ url = "#{uri.scheme}://#{hostname}:#{uri.port}#{url}" if url.start_with?('/')
198
+ return nil, url
199
+ else
200
+ block&.call(response)
201
+ return response, nil
202
+ end
203
+ end
195
204
  end
196
205
  end
197
206
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class SsrfFilter
4
- VERSION = '1.0.6'.freeze
4
+ VERSION = '1.1.0'
5
5
  end
data/lib/ssrf_filter.rb CHANGED
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'ssrf_filter/patch/http_generic_request'
4
3
  require 'ssrf_filter/patch/ssl_socket'
5
4
  require 'ssrf_filter/ssrf_filter'
6
5
  require 'ssrf_filter/version'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ssrf_filter
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.6
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Arkadiy Tetelman
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-02-25 00:00:00.000000000 Z
11
+ date: 2022-08-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler-audit
@@ -16,87 +16,143 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 0.6.0
19
+ version: 0.9.1
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 0.6.0
26
+ version: 0.9.1
27
27
  - !ruby/object:Gem::Dependency
28
- name: coveralls
28
+ name: pry-byebug
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
29
43
  requirement: !ruby/object:Gem::Requirement
30
44
  requirements:
31
45
  - - "~>"
32
46
  - !ruby/object:Gem::Version
33
- version: 0.8.0
47
+ version: 3.11.0
34
48
  type: :development
35
49
  prerelease: false
36
50
  version_requirements: !ruby/object:Gem::Requirement
37
51
  requirements:
38
52
  - - "~>"
39
53
  - !ruby/object:Gem::Version
40
- version: 0.8.0
54
+ version: 3.11.0
41
55
  - !ruby/object:Gem::Dependency
42
- name: rspec
56
+ name: rubocop
43
57
  requirement: !ruby/object:Gem::Requirement
44
58
  requirements:
45
59
  - - "~>"
46
60
  - !ruby/object:Gem::Version
47
- version: 3.7.0
61
+ version: 1.35.0
48
62
  type: :development
49
63
  prerelease: false
50
64
  version_requirements: !ruby/object:Gem::Requirement
51
65
  requirements:
52
66
  - - "~>"
53
67
  - !ruby/object:Gem::Version
54
- version: 3.7.0
68
+ version: 1.35.0
55
69
  - !ruby/object:Gem::Dependency
56
- name: webmock
70
+ name: rubocop-rspec
57
71
  requirement: !ruby/object:Gem::Requirement
58
72
  requirements:
59
73
  - - "~>"
60
74
  - !ruby/object:Gem::Version
61
- version: 3.3.0
75
+ version: 2.12.1
62
76
  type: :development
63
77
  prerelease: false
64
78
  version_requirements: !ruby/object:Gem::Requirement
65
79
  requirements:
66
80
  - - "~>"
67
81
  - !ruby/object:Gem::Version
68
- version: 3.3.0
82
+ version: 2.12.1
69
83
  - !ruby/object:Gem::Dependency
70
- name: rubocop
84
+ name: simplecov
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 0.21.2
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 0.21.2
97
+ - !ruby/object:Gem::Dependency
98
+ name: simplecov-lcov
71
99
  requirement: !ruby/object:Gem::Requirement
72
100
  requirements:
73
101
  - - "~>"
74
102
  - !ruby/object:Gem::Version
75
- version: 0.52.0
103
+ version: 0.8.0
76
104
  type: :development
77
105
  prerelease: false
78
106
  version_requirements: !ruby/object:Gem::Requirement
79
107
  requirements:
80
108
  - - "~>"
81
109
  - !ruby/object:Gem::Version
82
- version: 0.52.0
110
+ version: 0.8.0
111
+ - !ruby/object:Gem::Dependency
112
+ name: webmock
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: 3.18.0
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: 3.18.0
125
+ - !ruby/object:Gem::Dependency
126
+ name: webrick
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
83
139
  description: A gem that makes it easy to prevent server side request forgery (SSRF)
84
140
  attacks
85
- email:
141
+ email:
86
142
  executables: []
87
143
  extensions: []
88
144
  extra_rdoc_files: []
89
145
  files:
90
146
  - lib/ssrf_filter.rb
91
- - lib/ssrf_filter/patch/http_generic_request.rb
92
147
  - lib/ssrf_filter/patch/ssl_socket.rb
93
148
  - lib/ssrf_filter/ssrf_filter.rb
94
149
  - lib/ssrf_filter/version.rb
95
150
  homepage: https://github.com/arkadiyt/ssrf_filter
96
151
  licenses:
97
152
  - MIT
98
- metadata: {}
99
- post_install_message:
153
+ metadata:
154
+ rubygems_mfa_required: 'true'
155
+ post_install_message:
100
156
  rdoc_options: []
101
157
  require_paths:
102
158
  - lib
@@ -104,16 +160,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
104
160
  requirements:
105
161
  - - ">="
106
162
  - !ruby/object:Gem::Version
107
- version: 2.0.0
163
+ version: 2.6.0
108
164
  required_rubygems_version: !ruby/object:Gem::Requirement
109
165
  requirements:
110
166
  - - ">="
111
167
  - !ruby/object:Gem::Version
112
168
  version: '0'
113
169
  requirements: []
114
- rubyforge_project:
115
- rubygems_version: 2.6.14
116
- signing_key:
170
+ rubygems_version: 3.0.3
171
+ signing_key:
117
172
  specification_version: 4
118
173
  summary: A gem that makes it easy to prevent server side request forgery (SSRF) attacks
119
174
  test_files: []
@@ -1,83 +0,0 @@
1
- require 'stringio'
2
-
3
- class SsrfFilter
4
- module Patch
5
- module HTTPGenericRequest
6
- # Ruby had a bug in its Http library where if you set a custom `Host` header on a request it would get
7
- # overwritten. This was tracked in:
8
- # https://bugs.ruby-lang.org/issues/10054
9
- # and resolved with the commit:
10
- # https://github.com/ruby/ruby/commit/70a2eb63999265ff7e8d46d1f5b410c8ee3d30d7#diff-5c08b4ae27d2294a8294a27ff9af4a85
11
- # Versions of Ruby that don't have this fix applied will fail to connect to certain hosts via SsrfFilter. The
12
- # patch below backports the fix from the linked commit.
13
-
14
- def self.should_apply?
15
- # Check if the patch needs to be applied:
16
- # The Ruby bug was that HTTPGenericRequest#exec overwrote the Host header, so this snippet checks
17
- # if we can reproduce that behavior. It does not actually open any network connections.
18
-
19
- uri = URI('https://www.example.com')
20
- request = ::Net::HTTPGenericRequest.new('HEAD', false, false, uri)
21
- request['host'] = '127.0.0.1'
22
- request.exec(StringIO.new, '1.1', '/')
23
- request['host'] == uri.hostname
24
- end
25
-
26
- # Apply the patch from the linked commit onto ::Net::HTTPGenericRequest. Since this is 3rd party code,
27
- # disable code coverage and rubocop linting for this section.
28
- # :nocov:
29
- # rubocop:disable all
30
- def self.apply!
31
- return if instance_variable_defined?(:@checked_http_generic_request)
32
- @checked_http_generic_request = true
33
- return unless should_apply?
34
-
35
- ::Net::HTTPGenericRequest.class_eval do
36
- def exec(sock, ver, path)
37
- if @body
38
- send_request_with_body sock, ver, path, @body
39
- elsif @body_stream
40
- send_request_with_body_stream sock, ver, path, @body_stream
41
- elsif @body_data
42
- send_request_with_body_data sock, ver, path, @body_data
43
- else
44
- write_header sock, ver, path
45
- end
46
- end
47
-
48
- def update_uri(addr, port, ssl)
49
- # reflect the connection and @path to @uri
50
- return unless @uri
51
-
52
- if ssl
53
- scheme = 'https'.freeze
54
- klass = URI::HTTPS
55
- else
56
- scheme = 'http'.freeze
57
- klass = URI::HTTP
58
- end
59
-
60
- if host = self['host']
61
- host.sub!(/:.*/s, ''.freeze)
62
- elsif host = @uri.host
63
- else
64
- host = addr
65
- end
66
- # convert the class of the URI
67
- if @uri.is_a?(klass)
68
- @uri.host = host
69
- @uri.port = port
70
- else
71
- @uri = klass.new(
72
- scheme, @uri.userinfo,
73
- host, port, nil,
74
- @uri.path, nil, @uri.query, nil)
75
- end
76
- end
77
- end
78
- end
79
- # rubocop:enable all
80
- # :nocov:
81
- end
82
- end
83
- end