ssrf_filter 1.3.0 → 1.4.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
2
  SHA256:
3
- metadata.gz: 82909e9f3a32689d3a678e5474d09fbcfb8eae84308dd1ae13d622c8d1e6f6cd
4
- data.tar.gz: 6c349dc635f346cb0204d1a238ab02e4f6adcc6e9d9cd4268404195dfa9a66b2
3
+ metadata.gz: 8044ad57435e5aa1af62c8defb3087ee7dce64fdfb9530b8d5d73a0793607f6e
4
+ data.tar.gz: 914546724fc94a99915d4c710c21e6ec89f24f5aa3b32cca8a2ec4613719a616
5
5
  SHA512:
6
- metadata.gz: 602e11a98e4da6eaa0f869dc5f64aea715038b3a705b9354e25b7378dd96a893070bf4eb31d5c26c642b474d7891320c2749f7d9fc600e015c0e200cb04c3575
7
- data.tar.gz: 4cd5d4382850e12b23784e807dc4728c8e67a7c214fa1aa8637fb10d49a570a1ca94ecacf9a2ef41d3c7ed983e2569545eacacdc80030dabbb99a6ef033e0e8c
6
+ metadata.gz: 9b5842cc8b3a2b4bef7f82cd3956def19d1d96e7cb85c6524a36bffe4f04cfaf58a01630203d0ab824063507802c4a681503199accc3154e684b0f2bbd594b47
7
+ data.tar.gz: 494fff3091b5deb2aa76507a482114a55375322395f9e9fb4cf4139b1fabc5415d8ffa5a31497eb05217b70d8793960c4a808967536c64605f9787b776c07d92
@@ -6,7 +6,7 @@ require 'resolv'
6
6
  require 'uri'
7
7
 
8
8
  class SsrfFilter
9
- def self.prefixlen_from_ipaddr(ipaddr)
9
+ private_class_method def self.prefixlen_from_ipaddr(ipaddr)
10
10
  mask_addr = ipaddr.instance_variable_get('@mask_addr')
11
11
  raise ArgumentError, 'Invalid mask' if mask_addr.zero?
12
12
 
@@ -22,7 +22,19 @@ class SsrfFilter
22
22
 
23
23
  length
24
24
  end
25
- private_class_method :prefixlen_from_ipaddr
25
+
26
+ private_class_method def self.ipv4_from_rfc6052(ipv6_addr, prefix_len)
27
+ n = ipv6_addr.to_i
28
+ ipv4_int = case prefix_len
29
+ when 32 then (n >> 64) & 0xFFFF_FFFF
30
+ when 40 then (((n >> 64) & 0xFFFFFF) << 8) | ((n >> 48) & 0xFF)
31
+ when 48 then (((n >> 64) & 0xFFFF) << 16) | ((n >> 40) & 0xFFFF)
32
+ when 56 then (((n >> 64) & 0xFF) << 24) | ((n >> 32) & 0xFFFFFF)
33
+ when 64 then (n >> 24) & 0xFFFF_FFFF
34
+ when 96 then n & 0xFFFF_FFFF
35
+ end
36
+ ::IPAddr.new(ipv4_int, Socket::AF_INET) if ipv4_int
37
+ end
26
38
 
27
39
  # https://en.wikipedia.org/wiki/Reserved_IP_addresses
28
40
  IPV4_BLACKLIST = [
@@ -34,7 +46,6 @@ class SsrfFilter
34
46
  ::IPAddr.new('172.16.0.0/12'), # Private network
35
47
  ::IPAddr.new('192.0.0.0/24'), # IETF Protocol Assignments
36
48
  ::IPAddr.new('192.0.2.0/24'), # TEST-NET-1, documentation and examples
37
- ::IPAddr.new('192.88.99.0/24'), # IPv6 to IPv4 relay (includes 2002::/16)
38
49
  ::IPAddr.new('192.168.0.0/16'), # Private network
39
50
  ::IPAddr.new('198.18.0.0/15'), # Network benchmark tests
40
51
  ::IPAddr.new('198.51.100.0/24'), # TEST-NET-2, documentation and examples
@@ -44,9 +55,11 @@ class SsrfFilter
44
55
  ::IPAddr.new('255.255.255.255') # Broadcast
45
56
  ].freeze
46
57
 
58
+ # NAT64 local-use prefix (RFC 8215), uses RFC 6052 /48 encoding (checked at runtime).
59
+ NAT64_LOCAL_PREFIX = ::IPAddr.new('64:ff9b:1::/48').freeze
60
+
47
61
  IPV6_BLACKLIST = ([
48
62
  ::IPAddr.new('::1/128'), # Loopback
49
- ::IPAddr.new('64:ff9b::/96'), # IPv4/IPv6 translation (RFC 6052)
50
63
  ::IPAddr.new('100::/64'), # Discard prefix (RFC 6666)
51
64
  ::IPAddr.new('2001::/32'), # Teredo tunneling
52
65
  ::IPAddr.new('2001:10::/28'), # Deprecated (previously ORCHID)
@@ -60,10 +73,14 @@ class SsrfFilter
60
73
  prefixlen = prefixlen_from_ipaddr(ipaddr)
61
74
 
62
75
  # 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)
64
- ipv4_mapped = ipaddr.ipv4_mapped.mask(80 + prefixlen)
65
-
66
- [ipv4_compatible, ipv4_mapped]
76
+ ipv4_compatible = IPAddr.new(ipaddr.to_i, Socket::AF_INET6).mask(96 + prefixlen)
77
+ ipv4_mapped = ipaddr.ipv4_mapped.mask(80 + prefixlen)
78
+ # IPv4-translated (RFC 2765): ::ffff:0:x.x.x.x/96+n
79
+ ipv4_translated = IPAddr.new("::ffff:0:#{ipaddr}").mask(96 + prefixlen)
80
+ # NAT64 well-known prefix (RFC 6052): 64:ff9b::x.x.x.x/96+n
81
+ nat64_well_known = IPAddr.new("64:ff9b::#{ipaddr}").mask(96 + prefixlen)
82
+
83
+ [ipv4_compatible, ipv4_mapped, ipv4_translated, nat64_well_known]
67
84
  end).freeze
68
85
 
69
86
  DEFAULT_SCHEME_WHITELIST = %w[http https].freeze
@@ -102,7 +119,7 @@ class SsrfFilter
102
119
  class CRLFInjection < Error
103
120
  end
104
121
 
105
- %i[get put post delete head patch].each do |method|
122
+ VERB_MAP.each_key do |method|
106
123
  define_singleton_method(method) do |url, options = {}, &block|
107
124
  original_url = url
108
125
  scheme_whitelist = options.fetch(:scheme_whitelist, DEFAULT_SCHEME_WHITELIST)
@@ -136,23 +153,32 @@ class SsrfFilter
136
153
  end
137
154
  end
138
155
 
139
- def self.unsafe_ip_address?(ip_address)
156
+ private_class_method def self.unsafe_ip_address?(ip_address)
140
157
  return true if ipaddr_has_mask?(ip_address)
141
158
 
142
159
  return IPV4_BLACKLIST.any? { |range| range.include?(ip_address) } if ip_address.ipv4?
143
- return IPV6_BLACKLIST.any? { |range| range.include?(ip_address) } if ip_address.ipv6?
160
+
161
+ if ip_address.ipv6?
162
+ return true if IPV6_BLACKLIST.any? { |range| range.include?(ip_address) }
163
+
164
+ # RFC 6052 /48 encoding for NAT64 local-use prefix (RFC 8215): 64:ff9b:1::/48
165
+ # IPv4 is split around u-bits at positions 64-71, so must be decoded at runtime
166
+ if NAT64_LOCAL_PREFIX.dup.include?(ip_address)
167
+ ipv4 = ipv4_from_rfc6052(ip_address, 48)
168
+ return unsafe_ip_address?(ipv4)
169
+ end
170
+ return false
171
+ end
144
172
 
145
173
  true
146
174
  end
147
- private_class_method :unsafe_ip_address?
148
175
 
149
- def self.ipaddr_has_mask?(ipaddr)
176
+ private_class_method def self.ipaddr_has_mask?(ipaddr)
150
177
  range = ipaddr.to_range
151
178
  range.first != range.last
152
179
  end
153
- private_class_method :ipaddr_has_mask?
154
180
 
155
- def self.normalized_hostname(uri)
181
+ private_class_method def self.normalized_hostname(uri)
156
182
  # Attach port for non-default as per RFC2616
157
183
  if (uri.port == 80 && uri.scheme == 'http') ||
158
184
  (uri.port == 443 && uri.scheme == 'https')
@@ -161,9 +187,8 @@ class SsrfFilter
161
187
  "#{uri.hostname}:#{uri.port}"
162
188
  end
163
189
  end
164
- private_class_method :normalized_hostname
165
190
 
166
- def self.fetch_once(uri, ip, verb, options, &block)
191
+ private_class_method def self.fetch_once(uri, ip, verb, options, &block)
167
192
  if options[:params]
168
193
  params = uri.query ? ::URI.decode_www_form(uri.query).to_h : {}
169
194
  params.merge!(options[:params])
@@ -202,9 +227,8 @@ class SsrfFilter
202
227
  return response, url
203
228
  end
204
229
  end
205
- private_class_method :fetch_once
206
230
 
207
- def self.validate_request(request)
231
+ private_class_method def self.validate_request(request)
208
232
  # RFC822 allows multiline "folded" headers:
209
233
  # https://tools.ietf.org/html/rfc822#section-3.1
210
234
  # In practice if any user input is ever supplied as a header key/value, they'll get
@@ -215,5 +239,4 @@ class SsrfFilter
215
239
  end
216
240
  end
217
241
  end
218
- private_class_method :validate_request
219
242
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class SsrfFilter
4
- VERSION = '1.3.0'
4
+ VERSION = '1.4.0'
5
5
  end
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.3.0
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Arkadiy Tetelman
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-05-10 00:00:00.000000000 Z
11
+ date: 2026-03-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: base64
@@ -108,6 +108,20 @@ dependencies:
108
108
  - - "~>"
109
109
  - !ruby/object:Gem::Version
110
110
  version: 0.8.0
111
+ - !ruby/object:Gem::Dependency
112
+ name: tsort
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - '='
116
+ - !ruby/object:Gem::Version
117
+ version: 0.1.0
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - '='
123
+ - !ruby/object:Gem::Version
124
+ version: 0.1.0
111
125
  - !ruby/object:Gem::Dependency
112
126
  name: webmock
113
127
  requirement: !ruby/object:Gem::Requirement
@@ -167,7 +181,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
167
181
  - !ruby/object:Gem::Version
168
182
  version: '0'
169
183
  requirements: []
170
- rubygems_version: 3.3.7
184
+ rubygems_version: 3.5.16
171
185
  signing_key:
172
186
  specification_version: 4
173
187
  summary: A gem that makes it easy to prevent server side request forgery (SSRF) attacks