rack-protection 2.0.8.1 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ca62cac87c00c55967df5d002e0244e1eabddcdf973ea007556b102760c66a74
4
- data.tar.gz: ee1ed6aed9320fee7d1e85fdb708bf34226cfb18815220f8f8ab0bafe87f4629
3
+ metadata.gz: 5ed141bb1184b6760816e1768b5d91246d91a01ef56cb17a163f5370df604026
4
+ data.tar.gz: f3ac32c5d25123bb9f5843a47ca17dc796a9e3469a997f32b772ab72200cff58
5
5
  SHA512:
6
- metadata.gz: c2493f8c141991bbba4697f60f3a84b361a5472d4fc8f370974eba485f2f910aaf51ab6b3a9246479a41ffc72f619d2587e0f987fdfb75536f0cb41115bc2b95
7
- data.tar.gz: ddb4160be67e1f13f16514d0a3959c39e417232cb708a84c9be99985c3502178fb718d4218a3b651c54d932582fb370628f0a5ed5815aa7ed39765cbf24c8de1
6
+ metadata.gz: 9d78cedbd03ffe9dba1c6bca4e4b3cf2b165b12e9befb1136bda35651e99b88ecd575a44e3f394aee8e0191f7080706ebc1a2462dc5504b19d41a3b7bb47e80b
7
+ data.tar.gz: 00466e987ae67ebd7b76be1099a99cba4852987b88dbbccf4f230b1d3ee8dfa40f627cc0463aec39d64788566e08cb71117a5c33688edbb30540949436245f56
@@ -1,5 +1,6 @@
1
1
  require 'rack/protection'
2
2
  require 'securerandom'
3
+ require 'openssl'
3
4
  require 'base64'
4
5
 
5
6
  module Rack
@@ -24,6 +25,13 @@ module Rack
24
25
  # the token on a request. Default value:
25
26
  # <tt>"authenticity_token"</tt>
26
27
  #
28
+ # [<tt>:key</tt>] the name of the param that should contain
29
+ # the token in the session. Default value:
30
+ # <tt>:csrf</tt>
31
+ #
32
+ # [<tt>:allow_if</tt>] a proc for custom allow/deny logic. Default value:
33
+ # <tt>nil</tt>
34
+ #
27
35
  # == Example: Forms application
28
36
  #
29
37
  # To show what the AuthenticityToken does, this section includes a sample
@@ -63,7 +71,7 @@ module Rack
63
71
  # <h1>With Authenticity Token</h1>
64
72
  # <p>This successfully takes you to back to this form.</p>
65
73
  # <form action="" method="post">
66
- # <input type="hidden" name="authenticity_token" value="#{env['rack.session'][:csrf]}" />
74
+ # <input type="hidden" name="authenticity_token" value="#{Rack::Protection::AuthenticityToken.token(env['rack.session'])}" />
67
75
  # <input type="text" name="foo" />
68
76
  # <input type="submit" />
69
77
  # </form>
@@ -85,42 +93,57 @@ module Rack
85
93
  TOKEN_LENGTH = 32
86
94
 
87
95
  default_options :authenticity_param => 'authenticity_token',
96
+ :key => :csrf,
88
97
  :allow_if => nil
89
98
 
90
- def self.token(session)
91
- self.new(nil).mask_authenticity_token(session)
99
+ def self.token(session, path: nil, method: :post)
100
+ self.new(nil).mask_authenticity_token(session, path: path, method: method)
92
101
  end
93
102
 
94
103
  def self.random_token
95
- SecureRandom.base64(TOKEN_LENGTH)
104
+ SecureRandom.urlsafe_base64(TOKEN_LENGTH, padding: false)
96
105
  end
97
106
 
98
107
  def accepts?(env)
99
- session = session env
108
+ session = session(env)
100
109
  set_token(session)
101
110
 
102
111
  safe?(env) ||
103
- valid_token?(session, env['HTTP_X_CSRF_TOKEN']) ||
104
- valid_token?(session, Request.new(env).params[options[:authenticity_param]]) ||
112
+ valid_token?(env, env['HTTP_X_CSRF_TOKEN']) ||
113
+ valid_token?(env, Request.new(env).params[options[:authenticity_param]]) ||
105
114
  ( options[:allow_if] && options[:allow_if].call(env) )
115
+ rescue
116
+ false
106
117
  end
107
118
 
108
- def mask_authenticity_token(session)
109
- token = set_token(session)
119
+ def mask_authenticity_token(session, path: nil, method: :post)
120
+ set_token(session)
121
+
122
+ token = if path && method
123
+ per_form_token(session, path, method)
124
+ else
125
+ global_token(session)
126
+ end
127
+
110
128
  mask_token(token)
111
129
  end
112
130
 
131
+ GLOBAL_TOKEN_IDENTIFIER = '!real_csrf_token'
132
+ private_constant :GLOBAL_TOKEN_IDENTIFIER
133
+
113
134
  private
114
135
 
115
136
  def set_token(session)
116
- session[:csrf] ||= self.class.random_token
137
+ session[options[:key]] ||= self.class.random_token
117
138
  end
118
139
 
119
140
  # Checks the client's masked token to see if it matches the
120
141
  # session token.
121
- def valid_token?(session, token)
142
+ def valid_token?(env, token)
122
143
  return false if token.nil? || token.empty?
123
144
 
145
+ session = session(env)
146
+
124
147
  begin
125
148
  token = decode_token(token)
126
149
  rescue ArgumentError # encoded_masked_token is invalid Base64
@@ -131,13 +154,13 @@ module Rack
131
154
  # to handle any unmasked tokens that we've issued without error.
132
155
 
133
156
  if unmasked_token?(token)
134
- compare_with_real_token token, session
135
-
157
+ compare_with_real_token(token, session)
136
158
  elsif masked_token?(token)
137
159
  token = unmask_token(token)
138
160
 
139
- compare_with_real_token token, session
140
-
161
+ compare_with_global_token(token, session) ||
162
+ compare_with_real_token(token, session) ||
163
+ compare_with_per_form_token(token, session, Request.new(env))
141
164
  else
142
165
  false # Token is malformed
143
166
  end
@@ -147,7 +170,6 @@ module Rack
147
170
  # on each request. The masking is used to mitigate SSL attacks
148
171
  # like BREACH.
149
172
  def mask_token(token)
150
- token = decode_token(token)
151
173
  one_time_pad = SecureRandom.random_bytes(token.length)
152
174
  encrypted_token = xor_byte_strings(one_time_pad, token)
153
175
  masked_token = one_time_pad + encrypted_token
@@ -176,20 +198,53 @@ module Rack
176
198
  secure_compare(token, real_token(session))
177
199
  end
178
200
 
201
+ def compare_with_global_token(token, session)
202
+ secure_compare(token, global_token(session))
203
+ end
204
+
205
+ def compare_with_per_form_token(token, session, request)
206
+ secure_compare(token,
207
+ per_form_token(session, request.path.chomp('/'), request.request_method)
208
+ )
209
+ end
210
+
179
211
  def real_token(session)
180
- decode_token(session[:csrf])
212
+ decode_token(session[options[:key]])
213
+ end
214
+
215
+ def global_token(session)
216
+ token_hmac(session, GLOBAL_TOKEN_IDENTIFIER)
217
+ end
218
+
219
+ def per_form_token(session, path, method)
220
+ token_hmac(session, "#{path}##{method.downcase}")
181
221
  end
182
222
 
183
223
  def encode_token(token)
184
- Base64.strict_encode64(token)
224
+ Base64.urlsafe_encode64(token)
185
225
  end
186
226
 
187
227
  def decode_token(token)
188
- Base64.strict_decode64(token)
228
+ Base64.urlsafe_decode64(token)
229
+ end
230
+
231
+ def token_hmac(session, identifier)
232
+ OpenSSL::HMAC.digest(
233
+ OpenSSL::Digest::SHA256.new,
234
+ real_token(session),
235
+ identifier
236
+ )
189
237
  end
190
238
 
191
239
  def xor_byte_strings(s1, s2)
192
- s1.bytes.zip(s2.bytes).map { |(c1,c2)| c1 ^ c2 }.pack('c*')
240
+ s2 = s2.dup
241
+ size = s1.bytesize
242
+ i = 0
243
+ while i < size
244
+ s2.setbyte(i, s1.getbyte(i) ^ s2.getbyte(i))
245
+ i += 1
246
+ end
247
+ s2
193
248
  end
194
249
  end
195
250
  end
@@ -36,16 +36,15 @@ module Rack
36
36
  # to be used in a policy.
37
37
  #
38
38
  class ContentSecurityPolicy < Base
39
- default_options default_src: :none, script_src: "'self'",
40
- img_src: "'self'", style_src: "'self'",
41
- connect_src: "'self'", report_only: false
39
+ default_options default_src: "'self'", report_only: false
42
40
 
43
41
  DIRECTIVES = %i(base_uri child_src connect_src default_src
44
42
  font_src form_action frame_ancestors frame_src
45
43
  img_src manifest_src media_src object_src
46
44
  plugin_types referrer reflected_xss report_to
47
45
  report_uri require_sri_for sandbox script_src
48
- style_src worker_src).freeze
46
+ style_src worker_src webrtc_src navigate_to
47
+ prefetch_src).freeze
49
48
 
50
49
  NO_ARG_DIRECTIVES = %i(block_all_mixed_content disown_opener
51
50
  upgrade_insecure_requests).freeze
@@ -62,7 +61,7 @@ module Rack
62
61
  # Set these key values to boolean 'true' to include in policy
63
62
  NO_ARG_DIRECTIVES.each do |d|
64
63
  if options.key?(d) && options[d].is_a?(TrueClass)
65
- directives << d.to_s.sub(/_/, '-')
64
+ directives << d.to_s.tr('_', '-')
66
65
  end
67
66
  end
68
67
 
@@ -9,11 +9,11 @@ module Rack
9
9
  # http://tools.ietf.org/html/draft-abarth-origin
10
10
  #
11
11
  # Does not accept unsafe HTTP requests when value of Origin HTTP request header
12
- # does not match default or whitelisted URIs.
12
+ # does not match default or permitted URIs.
13
13
  #
14
- # If you want to whitelist a specific domain, you can pass in as the `:origin_whitelist` option:
14
+ # If you want to permit a specific domain, you can pass in as the `:permitted_origins` option:
15
15
  #
16
- # use Rack::Protection, origin_whitelist: ["http://localhost:3000", "http://127.0.01:3000"]
16
+ # use Rack::Protection, permitted_origins: ["http://localhost:3000", "http://127.0.01:3000"]
17
17
  #
18
18
  # The `:allow_if` option can also be set to a proc to use custom allow/deny logic.
19
19
  class HttpOrigin < Base
@@ -32,7 +32,14 @@ module Rack
32
32
  return true unless origin = env['HTTP_ORIGIN']
33
33
  return true if base_url(env) == origin
34
34
  return true if options[:allow_if] && options[:allow_if].call(env)
35
- Array(options[:origin_whitelist]).include? origin
35
+
36
+ if options.key? :origin_whitelist
37
+ warn env, "Rack::Protection origin_whitelist option is deprecated and will be removed, " \
38
+ "use permitted_origins instead.\n"
39
+ end
40
+
41
+ permitted_origins = options[:permitted_origins] || options[:origin_whitelist]
42
+ Array(permitted_origins).include? origin
36
43
  end
37
44
 
38
45
  end
@@ -0,0 +1,25 @@
1
+ require 'rack/protection'
2
+
3
+ module Rack
4
+ module Protection
5
+ ##
6
+ # Prevented attack:: Secret leakage, third party tracking
7
+ # Supported browsers:: mixed support
8
+ # More infos:: https://www.w3.org/TR/referrer-policy/
9
+ # https://caniuse.com/#search=referrer-policy
10
+ #
11
+ # Sets Referrer-Policy header to tell the browser to limit the Referer header.
12
+ #
13
+ # Options:
14
+ # referrer_policy:: The policy to use (default: 'strict-origin-when-cross-origin')
15
+ class ReferrerPolicy < Base
16
+ default_options :referrer_policy => 'strict-origin-when-cross-origin'
17
+
18
+ def call(env)
19
+ status, headers, body = @app.call(env)
20
+ headers['Referrer-Policy'] ||= options[:referrer_policy]
21
+ [status, headers, body]
22
+ end
23
+ end
24
+ end
25
+ end
@@ -1,5 +1,5 @@
1
1
  module Rack
2
2
  module Protection
3
- VERSION = '2.0.8.1'
3
+ VERSION = '2.2.0'
4
4
  end
5
5
  end
@@ -14,6 +14,7 @@ module Rack
14
14
  autoload :IPSpoofing, 'rack/protection/ip_spoofing'
15
15
  autoload :JsonCsrf, 'rack/protection/json_csrf'
16
16
  autoload :PathTraversal, 'rack/protection/path_traversal'
17
+ autoload :ReferrerPolicy, 'rack/protection/referrer_policy'
17
18
  autoload :RemoteReferrer, 'rack/protection/remote_referrer'
18
19
  autoload :RemoteToken, 'rack/protection/remote_token'
19
20
  autoload :SessionHijacking, 'rack/protection/session_hijacking'
@@ -32,9 +33,11 @@ module Rack
32
33
  Rack::Builder.new do
33
34
  # Off by default, unless added
34
35
  use ::Rack::Protection::AuthenticityToken, options if use_these.include? :authenticity_token
35
- use ::Rack::Protection::CookieTossing, options if use_these.include? :cookie_tossing
36
36
  use ::Rack::Protection::ContentSecurityPolicy, options if use_these.include? :content_security_policy
37
+ use ::Rack::Protection::CookieTossing, options if use_these.include? :cookie_tossing
38
+ use ::Rack::Protection::EscapedParams, options if use_these.include? :escaped_params
37
39
  use ::Rack::Protection::FormToken, options if use_these.include? :form_token
40
+ use ::Rack::Protection::ReferrerPolicy, options if use_these.include? :referrer_policy
38
41
  use ::Rack::Protection::RemoteReferrer, options if use_these.include? :remote_referrer
39
42
  use ::Rack::Protection::StrictTransport, options if use_these.include? :strict_transport
40
43
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack-protection
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.8.1
4
+ version: 2.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - https://github.com/sinatra/sinatra/graphs/contributors
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-01-01 00:00:00.000000000 Z
11
+ date: 2022-02-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -76,6 +76,7 @@ files:
76
76
  - lib/rack/protection/ip_spoofing.rb
77
77
  - lib/rack/protection/json_csrf.rb
78
78
  - lib/rack/protection/path_traversal.rb
79
+ - lib/rack/protection/referrer_policy.rb
79
80
  - lib/rack/protection/remote_referrer.rb
80
81
  - lib/rack/protection/remote_token.rb
81
82
  - lib/rack/protection/session_hijacking.rb
@@ -105,8 +106,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
105
106
  - !ruby/object:Gem::Version
106
107
  version: '0'
107
108
  requirements: []
108
- rubyforge_project:
109
- rubygems_version: 2.7.3
109
+ rubygems_version: 3.1.2
110
110
  signing_key:
111
111
  specification_version: 4
112
112
  summary: Protect against typical web attacks, works with all Rack apps, including