ssrf_filter 1.0.8 → 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 +4 -4
- data/lib/ssrf_filter/patch/ssl_socket.rb +2 -0
- data/lib/ssrf_filter/ssrf_filter.rb +20 -16
- data/lib/ssrf_filter/version.rb +1 -1
- data/lib/ssrf_filter.rb +0 -1
- metadata +46 -46
- data/lib/ssrf_filter/patch/http_generic_request.rb +0 -83
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 97b1c63593031040d2f23b6fbb055c44490fc2624a5b9901203053c971991ba6
|
|
4
|
+
data.tar.gz: ca816401abe0177feea5fd70d01afd19758598cd9864659a8eb97e7184367830
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 75ae3f265b486cb8768ef0c49d3dd0313a218b3863a2c1dfc8615727453aea45ab7ae5f71289e3748eecb9e17dc09443870413937c7fbad56a9a7a225b2fbb89
|
|
7
|
+
data.tar.gz: c881ea003dafd105bbbff4001646a879fc4cefb18d722fc60f21281c8ebaca8b19a324dccadb9acdf799e56501905addebcfe8f91253ee52136a9dab433335ad
|
|
@@ -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
|
-
|
|
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
|
|
@@ -104,7 +106,6 @@ class SsrfFilter
|
|
|
104
106
|
%i[get put post delete head patch].each do |method|
|
|
105
107
|
define_singleton_method(method) do |url, options = {}, &block|
|
|
106
108
|
::SsrfFilter::Patch::SSLSocket.apply!
|
|
107
|
-
::SsrfFilter::Patch::HTTPGenericRequest.apply!
|
|
108
109
|
|
|
109
110
|
original_url = url
|
|
110
111
|
scheme_whitelist = options[:scheme_whitelist] || DEFAULT_SCHEME_WHITELIST
|
|
@@ -126,16 +127,8 @@ class SsrfFilter
|
|
|
126
127
|
public_addresses = ip_addresses.reject(&method(:unsafe_ip_address?))
|
|
127
128
|
raise PrivateIPAddress, "Hostname '#{hostname}' has no public ip addresses" if public_addresses.empty?
|
|
128
129
|
|
|
129
|
-
response = fetch_once(uri, public_addresses.sample.to_s, method, options, &block)
|
|
130
|
-
|
|
131
|
-
case response
|
|
132
|
-
when ::Net::HTTPRedirection then
|
|
133
|
-
url = response['location']
|
|
134
|
-
# Handle relative redirects
|
|
135
|
-
url = "#{uri.scheme}://#{hostname}:#{uri.port}#{url}" if url.start_with?('/')
|
|
136
|
-
else
|
|
137
|
-
return response
|
|
138
|
-
end
|
|
130
|
+
response, url = fetch_once(uri, public_addresses.sample.to_s, method, options, &block)
|
|
131
|
+
return response if url.nil?
|
|
139
132
|
end
|
|
140
133
|
|
|
141
134
|
raise TooManyRedirects, "Got #{max_redirects} redirects fetching #{original_url}"
|
|
@@ -171,7 +164,7 @@ class SsrfFilter
|
|
|
171
164
|
|
|
172
165
|
def self.fetch_once(uri, ip, verb, options, &block)
|
|
173
166
|
if options[:params]
|
|
174
|
-
params = uri.query ? ::
|
|
167
|
+
params = uri.query ? ::URI.decode_www_form(uri.query).to_h : {}
|
|
175
168
|
params.merge!(options[:params])
|
|
176
169
|
uri.query = ::URI.encode_www_form(params)
|
|
177
170
|
end
|
|
@@ -188,15 +181,26 @@ class SsrfFilter
|
|
|
188
181
|
|
|
189
182
|
request.body = options[:body] if options[:body]
|
|
190
183
|
|
|
191
|
-
|
|
184
|
+
options[:request_proc].call(request) if options[:request_proc].respond_to?(:call)
|
|
192
185
|
validate_request(request)
|
|
193
186
|
|
|
194
187
|
http_options = options[:http_options] || {}
|
|
195
188
|
http_options[:use_ssl] = (uri.scheme == 'https')
|
|
196
189
|
|
|
197
190
|
with_forced_hostname(hostname) do
|
|
198
|
-
::Net::HTTP.start(uri.hostname, uri.port, http_options) do |http|
|
|
199
|
-
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
|
|
200
204
|
end
|
|
201
205
|
end
|
|
202
206
|
end
|
data/lib/ssrf_filter/version.rb
CHANGED
data/lib/ssrf_filter.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.0
|
|
4
|
+
version: 1.1.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: 2022-08-
|
|
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,126 +16,126 @@ dependencies:
|
|
|
16
16
|
requirements:
|
|
17
17
|
- - "~>"
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: 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.
|
|
26
|
+
version: 0.9.1
|
|
27
27
|
- !ruby/object:Gem::Dependency
|
|
28
|
-
name:
|
|
28
|
+
name: pry-byebug
|
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
|
30
30
|
requirements:
|
|
31
|
-
- - "
|
|
31
|
+
- - ">="
|
|
32
32
|
- !ruby/object:Gem::Version
|
|
33
|
-
version: 0
|
|
33
|
+
version: '0'
|
|
34
34
|
type: :development
|
|
35
35
|
prerelease: false
|
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
37
|
requirements:
|
|
38
|
-
- - "
|
|
38
|
+
- - ">="
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
|
-
version: 0
|
|
40
|
+
version: '0'
|
|
41
41
|
- !ruby/object:Gem::Dependency
|
|
42
|
-
name:
|
|
42
|
+
name: rspec
|
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
|
44
44
|
requirements:
|
|
45
|
-
- - "
|
|
45
|
+
- - "~>"
|
|
46
46
|
- !ruby/object:Gem::Version
|
|
47
|
-
version:
|
|
47
|
+
version: 3.11.0
|
|
48
48
|
type: :development
|
|
49
49
|
prerelease: false
|
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
|
51
51
|
requirements:
|
|
52
|
-
- - "
|
|
52
|
+
- - "~>"
|
|
53
53
|
- !ruby/object:Gem::Version
|
|
54
|
-
version:
|
|
54
|
+
version: 3.11.0
|
|
55
55
|
- !ruby/object:Gem::Dependency
|
|
56
|
-
name:
|
|
56
|
+
name: rubocop
|
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
|
58
58
|
requirements:
|
|
59
59
|
- - "~>"
|
|
60
60
|
- !ruby/object:Gem::Version
|
|
61
|
-
version:
|
|
61
|
+
version: 1.35.0
|
|
62
62
|
type: :development
|
|
63
63
|
prerelease: false
|
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
|
65
65
|
requirements:
|
|
66
66
|
- - "~>"
|
|
67
67
|
- !ruby/object:Gem::Version
|
|
68
|
-
version:
|
|
68
|
+
version: 1.35.0
|
|
69
69
|
- !ruby/object:Gem::Dependency
|
|
70
|
-
name:
|
|
70
|
+
name: rubocop-rspec
|
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
|
72
72
|
requirements:
|
|
73
|
-
- - "
|
|
73
|
+
- - "~>"
|
|
74
74
|
- !ruby/object:Gem::Version
|
|
75
|
-
version:
|
|
75
|
+
version: 2.12.1
|
|
76
76
|
type: :development
|
|
77
77
|
prerelease: false
|
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
|
79
79
|
requirements:
|
|
80
|
-
- - "
|
|
80
|
+
- - "~>"
|
|
81
81
|
- !ruby/object:Gem::Version
|
|
82
|
-
version:
|
|
82
|
+
version: 2.12.1
|
|
83
83
|
- !ruby/object:Gem::Dependency
|
|
84
|
-
name:
|
|
84
|
+
name: simplecov
|
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
|
86
86
|
requirements:
|
|
87
|
-
- - "
|
|
87
|
+
- - "~>"
|
|
88
88
|
- !ruby/object:Gem::Version
|
|
89
|
-
version:
|
|
89
|
+
version: 0.21.2
|
|
90
90
|
type: :development
|
|
91
91
|
prerelease: false
|
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
|
93
93
|
requirements:
|
|
94
|
-
- - "
|
|
94
|
+
- - "~>"
|
|
95
95
|
- !ruby/object:Gem::Version
|
|
96
|
-
version:
|
|
96
|
+
version: 0.21.2
|
|
97
97
|
- !ruby/object:Gem::Dependency
|
|
98
|
-
name:
|
|
98
|
+
name: simplecov-lcov
|
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
|
100
100
|
requirements:
|
|
101
|
-
- -
|
|
101
|
+
- - "~>"
|
|
102
102
|
- !ruby/object:Gem::Version
|
|
103
|
-
version:
|
|
103
|
+
version: 0.8.0
|
|
104
104
|
type: :development
|
|
105
105
|
prerelease: false
|
|
106
106
|
version_requirements: !ruby/object:Gem::Requirement
|
|
107
107
|
requirements:
|
|
108
|
-
- -
|
|
108
|
+
- - "~>"
|
|
109
109
|
- !ruby/object:Gem::Version
|
|
110
|
-
version:
|
|
110
|
+
version: 0.8.0
|
|
111
111
|
- !ruby/object:Gem::Dependency
|
|
112
|
-
name:
|
|
112
|
+
name: webmock
|
|
113
113
|
requirement: !ruby/object:Gem::Requirement
|
|
114
114
|
requirements:
|
|
115
|
-
- -
|
|
115
|
+
- - ">="
|
|
116
116
|
- !ruby/object:Gem::Version
|
|
117
|
-
version: 3.
|
|
117
|
+
version: 3.18.0
|
|
118
118
|
type: :development
|
|
119
119
|
prerelease: false
|
|
120
120
|
version_requirements: !ruby/object:Gem::Requirement
|
|
121
121
|
requirements:
|
|
122
|
-
- -
|
|
122
|
+
- - ">="
|
|
123
123
|
- !ruby/object:Gem::Version
|
|
124
|
-
version: 3.
|
|
124
|
+
version: 3.18.0
|
|
125
125
|
- !ruby/object:Gem::Dependency
|
|
126
|
-
name:
|
|
126
|
+
name: webrick
|
|
127
127
|
requirement: !ruby/object:Gem::Requirement
|
|
128
128
|
requirements:
|
|
129
|
-
- - "
|
|
129
|
+
- - ">="
|
|
130
130
|
- !ruby/object:Gem::Version
|
|
131
|
-
version: 0
|
|
131
|
+
version: '0'
|
|
132
132
|
type: :development
|
|
133
133
|
prerelease: false
|
|
134
134
|
version_requirements: !ruby/object:Gem::Requirement
|
|
135
135
|
requirements:
|
|
136
|
-
- - "
|
|
136
|
+
- - ">="
|
|
137
137
|
- !ruby/object:Gem::Version
|
|
138
|
-
version: 0
|
|
138
|
+
version: '0'
|
|
139
139
|
description: A gem that makes it easy to prevent server side request forgery (SSRF)
|
|
140
140
|
attacks
|
|
141
141
|
email:
|
|
@@ -144,14 +144,14 @@ extensions: []
|
|
|
144
144
|
extra_rdoc_files: []
|
|
145
145
|
files:
|
|
146
146
|
- lib/ssrf_filter.rb
|
|
147
|
-
- lib/ssrf_filter/patch/http_generic_request.rb
|
|
148
147
|
- lib/ssrf_filter/patch/ssl_socket.rb
|
|
149
148
|
- lib/ssrf_filter/ssrf_filter.rb
|
|
150
149
|
- lib/ssrf_filter/version.rb
|
|
151
150
|
homepage: https://github.com/arkadiyt/ssrf_filter
|
|
152
151
|
licenses:
|
|
153
152
|
- MIT
|
|
154
|
-
metadata:
|
|
153
|
+
metadata:
|
|
154
|
+
rubygems_mfa_required: 'true'
|
|
155
155
|
post_install_message:
|
|
156
156
|
rdoc_options: []
|
|
157
157
|
require_paths:
|
|
@@ -160,14 +160,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
160
160
|
requirements:
|
|
161
161
|
- - ">="
|
|
162
162
|
- !ruby/object:Gem::Version
|
|
163
|
-
version: 2.
|
|
163
|
+
version: 2.6.0
|
|
164
164
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
165
165
|
requirements:
|
|
166
166
|
- - ">="
|
|
167
167
|
- !ruby/object:Gem::Version
|
|
168
168
|
version: '0'
|
|
169
169
|
requirements: []
|
|
170
|
-
rubygems_version: 3.
|
|
170
|
+
rubygems_version: 3.0.3
|
|
171
171
|
signing_key:
|
|
172
172
|
specification_version: 4
|
|
173
173
|
summary: A gem that makes it easy to prevent server side request forgery (SSRF) attacks
|
|
@@ -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
|