ssrf_filter 1.4.0 → 1.5.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 +4 -4
- data/lib/ssrf_filter/ssrf_filter.rb +34 -2
- data/lib/ssrf_filter/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d68f597eb7885a7c9941fc8f134f7a6b87eec03fc0c92b6b6684a00d1be00069
|
|
4
|
+
data.tar.gz: 2c934e958e0542c2ae1606b0657cb2096d54647969f291aa41579fd3f0363c67
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 231e5f001e5540067710bc8894364592707fde79adafcbed2d4ea019646eb4b71fe60fae76caa562e8161090cffcb49390521045f57d153d8569eeeeaef85e79
|
|
7
|
+
data.tar.gz: 6854c8b312de12da5ea27bf842533eb48719ed98668d83838b4e07c1408ccacb302cfa7830b23c810e27d8f46e0f95cfcabd9919c29c86b351b8f285dac25934
|
|
@@ -91,6 +91,8 @@ class SsrfFilter
|
|
|
91
91
|
|
|
92
92
|
DEFAULT_ALLOW_UNFOLLOWED_REDIRECTS = false
|
|
93
93
|
DEFAULT_MAX_REDIRECTS = 10
|
|
94
|
+
DEFAULT_SENSITIVE_HEADERS = %w[authorization cookie].freeze
|
|
95
|
+
DEFAULT_ON_CROSS_ORIGIN_REDIRECT = :strip
|
|
94
96
|
|
|
95
97
|
VERB_MAP = {
|
|
96
98
|
get: ::Net::HTTP::Get,
|
|
@@ -119,14 +121,19 @@ class SsrfFilter
|
|
|
119
121
|
class CRLFInjection < Error
|
|
120
122
|
end
|
|
121
123
|
|
|
124
|
+
class CredentialLeakage < Error
|
|
125
|
+
end
|
|
126
|
+
|
|
122
127
|
VERB_MAP.each_key do |method|
|
|
123
128
|
define_singleton_method(method) do |url, options = {}, &block|
|
|
129
|
+
url = url.to_s
|
|
124
130
|
original_url = url
|
|
131
|
+
original_uri = URI(url)
|
|
125
132
|
scheme_whitelist = options.fetch(:scheme_whitelist, DEFAULT_SCHEME_WHITELIST)
|
|
126
133
|
resolver = options.fetch(:resolver, DEFAULT_RESOLVER)
|
|
127
134
|
allow_unfollowed_redirects = options.fetch(:allow_unfollowed_redirects, DEFAULT_ALLOW_UNFOLLOWED_REDIRECTS)
|
|
128
135
|
max_redirects = options.fetch(:max_redirects, DEFAULT_MAX_REDIRECTS)
|
|
129
|
-
|
|
136
|
+
sensitive_headers = options.fetch(:sensitive_headers, DEFAULT_SENSITIVE_HEADERS)
|
|
130
137
|
|
|
131
138
|
response = nil
|
|
132
139
|
(max_redirects + 1).times do
|
|
@@ -143,7 +150,14 @@ class SsrfFilter
|
|
|
143
150
|
public_addresses = ip_addresses.reject(&method(:unsafe_ip_address?))
|
|
144
151
|
raise PrivateIPAddress, "Hostname '#{hostname}' has no public ip addresses" if public_addresses.empty?
|
|
145
152
|
|
|
146
|
-
|
|
153
|
+
headers_to_strip = if !sensitive_headers.empty? && different_origin?(original_uri, uri)
|
|
154
|
+
sensitive_headers
|
|
155
|
+
else
|
|
156
|
+
[]
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
response, url = fetch_once(uri, public_addresses.sample.to_s, method,
|
|
160
|
+
options.merge(headers_to_strip: headers_to_strip), &block)
|
|
147
161
|
return response if url.nil?
|
|
148
162
|
end
|
|
149
163
|
|
|
@@ -178,6 +192,10 @@ class SsrfFilter
|
|
|
178
192
|
range.first != range.last
|
|
179
193
|
end
|
|
180
194
|
|
|
195
|
+
private_class_method def self.different_origin?(uri1, uri2)
|
|
196
|
+
uri1.scheme != uri2.scheme || uri1.hostname != uri2.hostname || uri1.port != uri2.port
|
|
197
|
+
end
|
|
198
|
+
|
|
181
199
|
private_class_method def self.normalized_hostname(uri)
|
|
182
200
|
# Attach port for non-default as per RFC2616
|
|
183
201
|
if (uri.port == 80 && uri.scheme == 'http') ||
|
|
@@ -205,6 +223,20 @@ class SsrfFilter
|
|
|
205
223
|
request.body = options[:body] if options[:body]
|
|
206
224
|
|
|
207
225
|
options[:request_proc].call(request) if options[:request_proc].respond_to?(:call)
|
|
226
|
+
|
|
227
|
+
headers_to_strip = Array(options[:headers_to_strip])
|
|
228
|
+
unless headers_to_strip.empty?
|
|
229
|
+
if options[:on_cross_origin_redirect] == :raise
|
|
230
|
+
leaking = headers_to_strip.select { |h| request[h] }
|
|
231
|
+
unless leaking.empty?
|
|
232
|
+
raise CredentialLeakage,
|
|
233
|
+
"Cross-origin redirect would leak sensitive headers: #{leaking.join(', ')}"
|
|
234
|
+
end
|
|
235
|
+
else
|
|
236
|
+
headers_to_strip.each { |h| request.delete(h) }
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
|
|
208
240
|
validate_request(request)
|
|
209
241
|
|
|
210
242
|
http_options = (options[:http_options] || {}).merge(
|
data/lib/ssrf_filter/version.rb
CHANGED
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.
|
|
4
|
+
version: 1.5.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: 2026-03
|
|
11
|
+
date: 2026-04-03 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: base64
|