rack-protection 2.1.0 → 2.2.4

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: a3268bb2b60f8095b38658717f5e267da2e1dfbee57f487baf39a185d3cf9266
4
- data.tar.gz: fc40122b95963a81333da038536782d85a9abdc92b823eb5d3044ef3c5c807c4
3
+ metadata.gz: 329a83327187635894cd3f8530c6e96b6cac29e8e28b1a8db0f1339cffa07a23
4
+ data.tar.gz: da8bea5ddeab7426d74fe977b7856a78c10f397da94b5769f5f72b83d5e0a1b1
5
5
  SHA512:
6
- metadata.gz: 7b381c903bb99d1e8cfcd00554642aafc2644f4432987be95e094a84b0f020648efb92b4a48e082cda5749045c44d14e5f08d97a1a733bf2b1e850eeaf69d67b
7
- data.tar.gz: bfb60cf9484528f0096cd68a6da55b66fa3471407a120caff83ee961b54043f3e4aca45a219273e7e48cc85ce70742ee75dab750609239fb887dfc35dfbcae59
6
+ metadata.gz: 6e4e35ae58ac6f131cff279bf7e68770eb87253d40c518980e1b44abbd9fbc7deb8952618e92289631d3641dd627ffb57f1963562536e8164aba2357544f36d6
7
+ data.tar.gz: 26b2c4e7413a6bf68386c474173ccad34ed7334064d4f262dfc0e04f546edc61b1de6ec95ca151544decbb1962957d85907734c38566b7e1b2501e6254d4956f
@@ -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
@@ -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,16 +198,42 @@ 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)
@@ -34,7 +34,7 @@ module Rack
34
34
  return true if options[:allow_if] && options[:allow_if].call(env)
35
35
 
36
36
  if options.key? :origin_whitelist
37
- warn "Rack::Protection origin_whitelist option is deprecated and will be removed, " \
37
+ warn env, "Rack::Protection origin_whitelist option is deprecated and will be removed, " \
38
38
  "use permitted_origins instead.\n"
39
39
  end
40
40
 
@@ -13,9 +13,11 @@ module Rack
13
13
 
14
14
  def accepts?(env)
15
15
  return true unless env.include? 'HTTP_X_FORWARDED_FOR'
16
- ips = env['HTTP_X_FORWARDED_FOR'].split(/\s*,\s*/)
17
- return false if env.include? 'HTTP_CLIENT_IP' and not ips.include? env['HTTP_CLIENT_IP']
18
- return false if env.include? 'HTTP_X_REAL_IP' and not ips.include? env['HTTP_X_REAL_IP']
16
+
17
+ ips = env['HTTP_X_FORWARDED_FOR'].split(',').map(&:strip)
18
+ return false if env.include?('HTTP_CLIENT_IP') && (!ips.include? env['HTTP_CLIENT_IP'])
19
+ return false if env.include?('HTTP_X_REAL_IP') && (!ips.include? env['HTTP_X_REAL_IP'])
20
+
19
21
  true
20
22
  end
21
23
  end
@@ -1,5 +1,5 @@
1
1
  module Rack
2
2
  module Protection
3
- VERSION = '2.1.0'
3
+ VERSION = '2.2.3'
4
4
  end
5
5
  end
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.1.0
4
+ version: 2.2.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - https://github.com/sinatra/sinatra/graphs/contributors
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-09-04 00:00:00.000000000 Z
11
+ date: 2022-12-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -91,7 +91,7 @@ metadata:
91
91
  source_code_uri: https://github.com/sinatra/sinatra/tree/master/rack-protection
92
92
  homepage_uri: http://sinatrarb.com/protection/
93
93
  documentation_uri: https://www.rubydoc.info/gems/rack-protection
94
- post_install_message:
94
+ post_install_message:
95
95
  rdoc_options: []
96
96
  require_paths:
97
97
  - lib
@@ -106,8 +106,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
106
106
  - !ruby/object:Gem::Version
107
107
  version: '0'
108
108
  requirements: []
109
- rubygems_version: 3.1.2
110
- signing_key:
109
+ rubyforge_project:
110
+ rubygems_version: 2.7.6.3
111
+ signing_key:
111
112
  specification_version: 4
112
113
  summary: Protect against typical web attacks, works with all Rack apps, including
113
114
  Rails.