ssrf_filter 1.0.6 → 1.1.0

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
- 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