ssrf_filter 1.1.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 97b1c63593031040d2f23b6fbb055c44490fc2624a5b9901203053c971991ba6
4
- data.tar.gz: ca816401abe0177feea5fd70d01afd19758598cd9864659a8eb97e7184367830
3
+ metadata.gz: f0230e20c7cd24b24dad277ee01042f3a4885fa68a8ea593fe0607de92023026
4
+ data.tar.gz: 6ce71a83907b1acfc382acc5d859a26fc1471dc3cd2dbc48d14b3e1dfca12051
5
5
  SHA512:
6
- metadata.gz: 75ae3f265b486cb8768ef0c49d3dd0313a218b3863a2c1dfc8615727453aea45ab7ae5f71289e3748eecb9e17dc09443870413937c7fbad56a9a7a225b2fbb89
7
- data.tar.gz: c881ea003dafd105bbbff4001646a879fc4cefb18d722fc60f21281c8ebaca8b19a324dccadb9acdf799e56501905addebcfe8f91253ee52136a9dab433335ad
6
+ metadata.gz: cb56468fb4bdcf10168efbc18f004d996363b9dcc8877a1fab1eff508ef1519b86c7eeb750fedd8b565a162804746f9a036b54e584520137c1c2b1a86d5c9421
7
+ data.tar.gz: 9d5e0ea62956d551caa2474a384fa90e827c6d133bbfe04831f77dd49cabe5bcb3abc7008eefc63bccedf1634a33bcc27f8178253ec26f7e051075dcfba7066e
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'resolv'
4
+
5
+ class SsrfFilter
6
+ module Patch
7
+ module Resolv
8
+ # As described in ssl_socket.rb, we want to patch ruby's http connection code to allow us to make outbound network
9
+ # requests while ensuring that both:
10
+ # 1) we're connecting to a public / non-private ip address
11
+ # 2) https connections continue to work
12
+ #
13
+ # This used to work fine prior to this change in ruby's net/http library:
14
+ # https://github.com/ruby/net-http/pull/36
15
+ # After this changed was introduced our patch no longer works - we need to set the hostname to the correct
16
+ # value on the SSLSocket (`s.hostname = ssl_host_address`), but that code path no longer executes due to the
17
+ # modification in the linked pull request.
18
+ #
19
+ # To work around this we introduce the patch below, which forces our ip address string to not match against the
20
+ # Resolv IPv4/IPv6 regular expressions. This is ugly and cumbersome but I didn't see any better path.
21
+ class PatchedRegexp < Regexp
22
+ def ===(other)
23
+ if ::Thread.current.key?(::SsrfFilter::FIBER_ADDRESS_KEY) &&
24
+ other.object_id.equal?(::Thread.current[::SsrfFilter::FIBER_ADDRESS_KEY].object_id)
25
+ false
26
+ else
27
+ super(other)
28
+ end
29
+ end
30
+ end
31
+
32
+ def self.apply!
33
+ return if instance_variable_defined?(:@patched_resolv)
34
+
35
+ @patched_resolv = true
36
+
37
+ old_ipv4 = ::Resolv::IPv4.send(:remove_const, :Regex)
38
+ old_ipv6 = ::Resolv::IPv6.send(:remove_const, :Regex)
39
+ ::Resolv::IPv4.const_set(:Regex, PatchedRegexp.new(old_ipv4))
40
+ ::Resolv::IPv6.const_set(:Regex, PatchedRegexp.new(old_ipv6))
41
+ end
42
+ end
43
+ end
44
+ end
@@ -35,13 +35,14 @@ class SsrfFilter
35
35
  ::OpenSSL::SSL::SSLSocket.class_eval do
36
36
  original_post_connection_check = instance_method(:post_connection_check)
37
37
  define_method(:post_connection_check) do |hostname|
38
- original_post_connection_check.bind(self).call(::Thread.current[::SsrfFilter::FIBER_LOCAL_KEY] || hostname)
38
+ original_post_connection_check.bind(self).call(::Thread.current[::SsrfFilter::FIBER_HOSTNAME_KEY] ||
39
+ hostname)
39
40
  end
40
41
 
41
42
  if method_defined?(:hostname=)
42
43
  original_hostname = instance_method(:hostname=)
43
44
  define_method(:hostname=) do |hostname|
44
- original_hostname.bind(self).call(::Thread.current[::SsrfFilter::FIBER_LOCAL_KEY] || hostname)
45
+ original_hostname.bind(self).call(::Thread.current[::SsrfFilter::FIBER_HOSTNAME_KEY] || hostname)
45
46
  end
46
47
  end
47
48
  end
@@ -83,7 +83,8 @@ class SsrfFilter
83
83
  patch: ::Net::HTTP::Patch
84
84
  }.freeze
85
85
 
86
- FIBER_LOCAL_KEY = :__ssrf_filter_hostname
86
+ FIBER_HOSTNAME_KEY = :__ssrf_filter_hostname
87
+ FIBER_ADDRESS_KEY = :__ssrf_filter_address
87
88
 
88
89
  class Error < ::StandardError
89
90
  end
@@ -106,6 +107,7 @@ class SsrfFilter
106
107
  %i[get put post delete head patch].each do |method|
107
108
  define_singleton_method(method) do |url, options = {}, &block|
108
109
  ::SsrfFilter::Patch::SSLSocket.apply!
110
+ ::SsrfFilter::Patch::Resolv.apply!
109
111
 
110
112
  original_url = url
111
113
  scheme_whitelist = options[:scheme_whitelist] || DEFAULT_SCHEME_WHITELIST
@@ -187,7 +189,7 @@ class SsrfFilter
187
189
  http_options = options[:http_options] || {}
188
190
  http_options[:use_ssl] = (uri.scheme == 'https')
189
191
 
190
- with_forced_hostname(hostname) do
192
+ with_forced_hostname(hostname, ip) do
191
193
  ::Net::HTTP.start(uri.hostname, uri.port, **http_options) do |http|
192
194
  http.request(request) do |response|
193
195
  case response
@@ -219,11 +221,13 @@ class SsrfFilter
219
221
  end
220
222
  private_class_method :validate_request
221
223
 
222
- def self.with_forced_hostname(hostname, &_block)
223
- ::Thread.current[FIBER_LOCAL_KEY] = hostname
224
+ def self.with_forced_hostname(hostname, ip, &_block)
225
+ ::Thread.current[FIBER_HOSTNAME_KEY] = hostname
226
+ ::Thread.current[FIBER_ADDRESS_KEY] = ip
224
227
  yield
225
228
  ensure
226
- ::Thread.current[FIBER_LOCAL_KEY] = nil
229
+ ::Thread.current[FIBER_HOSTNAME_KEY] = nil
230
+ ::Thread.current[FIBER_ADDRESS_KEY] = nil
227
231
  end
228
232
  private_class_method :with_forced_hostname
229
233
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class SsrfFilter
4
- VERSION = '1.1.0'
4
+ VERSION = '1.1.1'
5
5
  end
data/lib/ssrf_filter.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'ssrf_filter/patch/resolv'
3
4
  require 'ssrf_filter/patch/ssl_socket'
4
5
  require 'ssrf_filter/ssrf_filter'
5
6
  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.1.0
4
+ version: 1.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Arkadiy Tetelman
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-08-28 00:00:00.000000000 Z
11
+ date: 2022-09-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler-audit
@@ -144,6 +144,7 @@ extensions: []
144
144
  extra_rdoc_files: []
145
145
  files:
146
146
  - lib/ssrf_filter.rb
147
+ - lib/ssrf_filter/patch/resolv.rb
147
148
  - lib/ssrf_filter/patch/ssl_socket.rb
148
149
  - lib/ssrf_filter/ssrf_filter.rb
149
150
  - lib/ssrf_filter/version.rb