mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Merge pull request #39076 from etiennebarrie/upgrade-safe-urlsafe-csrf-tokens
Upgrade-safe URL-safe CSRF tokens
This commit is contained in:
commit
98a4c0c769
6 changed files with 71 additions and 8 deletions
|
@ -83,7 +83,7 @@
|
|||
out of the box: the value has to be url-encoded and decoded to survive transport.
|
||||
|
||||
Now, we generate Base64 urlsafe-encoded CSRF tokens, which are inherently safe
|
||||
to transport. Validation accepts both urlsafe tokens, and strict-encoded tokens
|
||||
to transport. Validation accepts both urlsafe tokens, and strict-encoded tokens
|
||||
for backwards compatibility.
|
||||
|
||||
*Scott Blum*
|
||||
|
|
|
@ -90,6 +90,10 @@ module ActionController #:nodoc:
|
|||
config_accessor :default_protect_from_forgery
|
||||
self.default_protect_from_forgery = false
|
||||
|
||||
# Controls whether URL-safe CSRF tokens are generated.
|
||||
config_accessor :urlsafe_csrf_tokens, instance_writer: false
|
||||
self.urlsafe_csrf_tokens = false
|
||||
|
||||
helper_method :form_authenticity_token
|
||||
helper_method :protect_against_forgery?
|
||||
end
|
||||
|
@ -329,7 +333,7 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
begin
|
||||
masked_token = Base64.urlsafe_decode64(encoded_masked_token)
|
||||
masked_token = decode_csrf_token(encoded_masked_token)
|
||||
rescue ArgumentError # encoded_masked_token is invalid Base64
|
||||
return false
|
||||
end
|
||||
|
@ -367,7 +371,7 @@ module ActionController #:nodoc:
|
|||
one_time_pad = SecureRandom.random_bytes(AUTHENTICITY_TOKEN_LENGTH)
|
||||
encrypted_csrf_token = xor_byte_strings(one_time_pad, raw_token)
|
||||
masked_token = one_time_pad + encrypted_csrf_token
|
||||
Base64.urlsafe_encode64(masked_token, padding: false)
|
||||
encode_csrf_token(masked_token)
|
||||
end
|
||||
|
||||
def compare_with_real_token(token, session) # :doc:
|
||||
|
@ -393,8 +397,8 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
def real_csrf_token(session) # :doc:
|
||||
session[:_csrf_token] ||= SecureRandom.urlsafe_base64(AUTHENTICITY_TOKEN_LENGTH, padding: false)
|
||||
Base64.urlsafe_decode64(session[:_csrf_token])
|
||||
session[:_csrf_token] ||= generate_csrf_token
|
||||
decode_csrf_token(session[:_csrf_token])
|
||||
end
|
||||
|
||||
def per_form_csrf_token(session, action_path, method) # :doc:
|
||||
|
@ -462,5 +466,33 @@ module ActionController #:nodoc:
|
|||
uri = URI.parse(action_path)
|
||||
uri.path.chomp("/")
|
||||
end
|
||||
|
||||
def generate_csrf_token # :nodoc:
|
||||
if urlsafe_csrf_tokens
|
||||
SecureRandom.urlsafe_base64(AUTHENTICITY_TOKEN_LENGTH, padding: false)
|
||||
else
|
||||
SecureRandom.base64(AUTHENTICITY_TOKEN_LENGTH)
|
||||
end
|
||||
end
|
||||
|
||||
def encode_csrf_token(csrf_token) # :nodoc:
|
||||
if urlsafe_csrf_tokens
|
||||
Base64.urlsafe_encode64(csrf_token, padding: false)
|
||||
else
|
||||
Base64.strict_encode64(csrf_token)
|
||||
end
|
||||
end
|
||||
|
||||
def decode_csrf_token(encoded_csrf_token) # :nodoc:
|
||||
if urlsafe_csrf_tokens
|
||||
Base64.urlsafe_decode64(encoded_csrf_token)
|
||||
else
|
||||
begin
|
||||
Base64.strict_decode64(encoded_csrf_token)
|
||||
rescue ArgumentError
|
||||
Base64.urlsafe_decode64(encoded_csrf_token)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -175,12 +175,15 @@ end
|
|||
# common test methods
|
||||
module RequestForgeryProtectionTests
|
||||
def setup
|
||||
@old_urlsafe_csrf_tokens = ActionController::Base.urlsafe_csrf_tokens
|
||||
ActionController::Base.urlsafe_csrf_tokens = true
|
||||
@token = Base64.urlsafe_encode64("railstestrailstestrailstestrails")
|
||||
@old_request_forgery_protection_token = ActionController::Base.request_forgery_protection_token
|
||||
ActionController::Base.request_forgery_protection_token = :custom_authenticity_token
|
||||
end
|
||||
|
||||
def teardown
|
||||
ActionController::Base.urlsafe_csrf_tokens = @old_urlsafe_csrf_tokens
|
||||
ActionController::Base.request_forgery_protection_token = @old_request_forgery_protection_token
|
||||
end
|
||||
|
||||
|
@ -378,12 +381,27 @@ module RequestForgeryProtectionTests
|
|||
end
|
||||
|
||||
def test_should_allow_post_with_strict_encoded_token
|
||||
session[:_csrf_token] = Base64.strict_encode64("railstestrailstestrailstestrails")
|
||||
@controller.stub :form_authenticity_token, @token do
|
||||
assert_not_blocked { post :index, params: { custom_authenticity_token: @token } }
|
||||
token_length = (ActionController::RequestForgeryProtection::AUTHENTICITY_TOKEN_LENGTH * 4.0 / 3).ceil
|
||||
token_including_url_unsafe_chars = "+/".ljust(token_length, "A")
|
||||
session[:_csrf_token] = token_including_url_unsafe_chars
|
||||
@controller.stub :form_authenticity_token, token_including_url_unsafe_chars do
|
||||
assert_not_blocked { post :index, params: { custom_authenticity_token: token_including_url_unsafe_chars } }
|
||||
end
|
||||
end
|
||||
|
||||
def test_should_allow_post_with_urlsafe_token_when_migrating
|
||||
config_before = ActionController::Base.urlsafe_csrf_tokens
|
||||
ActionController::Base.urlsafe_csrf_tokens = false
|
||||
token_length = (ActionController::RequestForgeryProtection::AUTHENTICITY_TOKEN_LENGTH * 4.0 / 3).ceil
|
||||
token_including_url_safe_chars = "-_".ljust(token_length, "A")
|
||||
session[:_csrf_token] = token_including_url_safe_chars
|
||||
@controller.stub :form_authenticity_token, token_including_url_safe_chars do
|
||||
assert_not_blocked { post :index, params: { custom_authenticity_token: token_including_url_safe_chars } }
|
||||
end
|
||||
ensure
|
||||
ActionController::Base.urlsafe_csrf_tokens = config_before
|
||||
end
|
||||
|
||||
def test_should_allow_patch_with_token
|
||||
session[:_csrf_token] = @token
|
||||
@controller.stub :form_authenticity_token, @token do
|
||||
|
|
|
@ -505,6 +505,8 @@ The schema dumper adds two additional configuration options:
|
|||
|
||||
* `config.action_controller.default_protect_from_forgery` determines whether forgery protection is added on `ActionController::Base`. This is false by default.
|
||||
|
||||
* `config.action_controller.urlsafe_csrf_tokens` configures whether generated CSRF tokens are URL-safe. Defaults to `false`.
|
||||
|
||||
* `config.action_controller.relative_url_root` can be used to tell Rails that you are [deploying to a subdirectory](configuring.html#deploy-to-a-subdirectory-relative-url-root). The default is `ENV['RAILS_RELATIVE_URL_ROOT']`.
|
||||
|
||||
* `config.action_controller.permit_all_parameters` sets all the parameters for mass assignment to be permitted by default. The default value is `false`.
|
||||
|
@ -994,6 +996,7 @@ text/javascript image/svg+xml application/postscript application/x-shockwave-fla
|
|||
- `config.active_job.skip_after_callbacks_if_terminated`: `true`
|
||||
- `config.action_dispatch.cookies_same_site_protection`: `:lax`
|
||||
- `ActiveSupport.utc_to_local_returns_utc_offset_times`: `true`
|
||||
- `config.action_controller.urlsafe_csrf_tokens`: `true`
|
||||
|
||||
#### For '6.0', new defaults from previous versions below and:
|
||||
|
||||
|
|
|
@ -177,6 +177,10 @@ module Rails
|
|||
action_dispatch.cookies_same_site_protection = :lax
|
||||
end
|
||||
|
||||
if respond_to?(:action_controller)
|
||||
action_controller.urlsafe_csrf_tokens = true
|
||||
end
|
||||
|
||||
ActiveSupport.utc_to_local_returns_utc_offset_times = true
|
||||
else
|
||||
raise "Unknown version #{target_version.to_s.inspect}"
|
||||
|
|
|
@ -25,6 +25,12 @@
|
|||
# It's best enabled when your entire app is migrated and stable on 6.1.
|
||||
# Rails.application.config.action_dispatch.cookies_same_site_protection = :lax
|
||||
|
||||
# Generate CSRF tokens that are encoded in URL-safe Base64.
|
||||
#
|
||||
# This change is not backwards compatible with earlier Rails versions.
|
||||
# It's best enabled when your entire app is migrated and stable on 6.1.
|
||||
# Rails.application.config.action_controller.urlsafe_csrf_tokens = true
|
||||
|
||||
# Specify whether `ActiveSupport::TimeZone.utc_to_local` returns a time with an
|
||||
# UTC offset or a UTC time.
|
||||
# ActiveSupport.utc_to_local_returns_utc_offset_times = true
|
||||
|
|
Loading…
Reference in a new issue