From 4c743587ad6a31908503ab317e37d70361d49e66 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Sun, 10 Mar 2019 16:37:46 -0700 Subject: [PATCH] Fix possible dev mode RCE If the secret_key_base is nil in dev or test generate a key from random bytes and store it in a tmp file. This prevents the app developers from having to share / checkin the secret key for dev / test but also maintains a key between app restarts in dev/test. [CVE-2019-5420] Co-Authored-By: eileencodes Co-Authored-By: John Hawthorn --- .../middleware/session/cookie_store.rb | 7 +++--- railties/lib/rails/application.rb | 19 ++++++++++++++-- .../test/application/configuration_test.rb | 22 ++++++++++++++++++- railties/test/isolation/abstract_unit.rb | 1 + 4 files changed, 43 insertions(+), 6 deletions(-) diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb index 02ccfbc81a..7c43c781c7 100644 --- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb @@ -24,9 +24,10 @@ # # Rails.application.config.session_store :cookie_store, key: '_your_app_session' # - # By default, your secret key base is derived from your application name in - # the test and development environments. In all other environments, it is stored - # encrypted in the config/credentials.yml.enc file. + # In the development and test environments your application's secret key base is + # generated by Rails and stored in a temporary file in tmp/development_secret.txt. + # In all other environments, it is stored encrypted in the + # config/credentials.yml.enc file. # # If your application was not updated to Rails 5.2 defaults, the secret_key_base # will be found in the old config/secrets.yml file. diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index fbad3e5db3..558a4d1f57 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -415,8 +415,8 @@ def secrets # then credentials.secret_key_base, and finally secrets.secret_key_base. For most applications, # the correct place to store it is in the encrypted credentials file. def secret_key_base - if Rails.env.test? || Rails.env.development? - secrets.secret_key_base || Digest::MD5.hexdigest(self.class.name) + if Rails.env.development? || Rails.env.test? + secrets.secret_key_base ||= generate_development_secret else validate_secret_key_base( ENV["SECRET_KEY_BASE"] || credentials.secret_key_base || secrets.secret_key_base @@ -581,6 +581,21 @@ def validate_secret_key_base(secret_key_base) private + def generate_development_secret + if secrets.secret_key_base.nil? + key_file = Rails.root.join("tmp/development_secret.txt") + + if !File.exist?(key_file) + random_key = SecureRandom.hex(64) + File.binwrite(key_file, random_key) + end + + secrets.secret_key_base = File.binread(key_file) + end + + secrets.secret_key_base + end + def build_request(env) req = super env["ORIGINAL_FULLPATH"] = req.fullpath diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb index 73773602a3..377dab1a13 100644 --- a/railties/test/application/configuration_test.rb +++ b/railties/test/application/configuration_test.rb @@ -596,6 +596,27 @@ def index assert_equal "some_value", verifier.verify(message) end + test "application will generate secret_key_base in tmp file if blank in development" do + app_file "config/initializers/secret_token.rb", <<-RUBY + Rails.application.credentials.secret_key_base = nil + RUBY + + app "development" + + assert_not_nil app.secrets.secret_key_base + assert File.exist?(app_path("tmp/development_secret.txt")) + end + + test "application will not generate secret_key_base in tmp file if blank in production" do + app_file "config/initializers/secret_token.rb", <<-RUBY + Rails.application.credentials.secret_key_base = nil + RUBY + + assert_raises ArgumentError do + app "production" + end + end + test "raises when secret_key_base is blank" do app_file "config/initializers/secret_token.rb", <<-RUBY Rails.application.credentials.secret_key_base = nil @@ -619,7 +640,6 @@ def index test "application verifier can build different verifiers" do make_basic_app do |application| - application.credentials.secret_key_base = "b3c631c314c0bbca50c1b2843150fe33" application.config.session_store :disabled end diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb index 3f1638a516..b10701aa55 100644 --- a/railties/test/isolation/abstract_unit.rb +++ b/railties/test/isolation/abstract_unit.rb @@ -226,6 +226,7 @@ def self.name; "RailtiesTestApp"; end @app.config.session_store :cookie_store, key: "_myapp_session" @app.config.active_support.deprecation = :log @app.config.log_level = :info + @app.secrets.secret_key_base = "b3c631c314c0bbca50c1b2843150fe33" yield @app if block_given? @app.initialize! -- 2.21.0