Skip to content

Instantly share code, notes, and snippets.

@mattetti
Last active September 23, 2020 07:04
Show Gist options
  • Save mattetti/7624413 to your computer and use it in GitHub Desktop.
Save mattetti/7624413 to your computer and use it in GitHub Desktop.
This is a monkey patch to change Rails 4's default session/signed cookie serializer from Marshal to JSON for security and compatibility reasons. Note that this is a hack, a pretty terrible one and you should only use it if you know what you're doing. Also, I only wrote this patch for my own personal use, so don't be surprised if it doesn't work …
# Hack to change the Rails cookie serializer from Marshal to JSON and therefore allow the session
# to be shared between different languages but also avoid that someone knowing the
# cookie secret key could execute arbitrary code on the server by unmarshalling
# modified Ruby code added to the session/permanent cookie.
#
# Note that all users will beed to login again since both the remember me cookie and the session cookies
# won't be valid. Note also that the remember me cookie is tested multiple times per request even when it fails.
# for performance reasons you might want to delete it if these extra cycles are too costly for you.
#
# Rails 4 (not tested on Rails 3).
# Author: Matt Aimonetti / mattetti
# Rails issue (more info, discussion, updates): https://github.com/rails/rails/issues/12881
# Wrapper module around `JSON` so we can rescue a parsing error
module JsonSessionSerializer
def self.load(value)
begin
JSON.parse(value)
rescue JSON::ParserError
nil
end
end
def self.dump(value)
JSON.generate(value)
end
end
# Make cookies use JSON as serializer instead of Marshal (Rails 4)
module ActionDispatch
class Cookies
# used by the remember me and other permanent cookies.
class SignedCookieJar #:nodoc:
include ChainedCookieJars
def initialize(parent_jar, key_generator, options = {})
@parent_jar = parent_jar
@options = options
secret = key_generator.generate_key(@options[:signed_cookie_salt])
# The only actual change is to pass a serializer options with a default set to our JSON serializer.
@verifier = ActiveSupport::MessageVerifier.new(secret, { serializer: options[:serializer] || JsonSessionSerializer })
end
end
# used by the session cookie.
class EncryptedCookieJar #:nodoc:
include ChainedCookieJars
def initialize(parent_jar, key_generator, options = {})
if ActiveSupport::LegacyKeyGenerator === key_generator
raise "You didn't set config.secret_key_base, which is required for this cookie jar. " +
"Read the upgrade documentation to learn more about this new config option."
end
@parent_jar = parent_jar
@options = options
secret = key_generator.generate_key(@options[:encrypted_cookie_salt])
sign_secret = key_generator.generate_key(@options[:encrypted_signed_cookie_salt])
# The only actual change is to pass a serializer options with a default set to our JSON serializer.
@encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, { serializer: options[:serializer] || JsonSessionSerializer } )
end
end
end
end
@jeffrafter
Copy link

Thanks Matt!

Note: if you want to allow for values that are not hashes or arrays (i.e., single strings) you need to enable quirks_mode when generating and parsing. See https://gist.github.com/jeffrafter/7950832

@ndvbd
Copy link

ndvbd commented Dec 10, 2018

I think it's enough to configure a :serializer => JSON, like this for example:

config.cache_store = :dalli_store, { :namespace => 'mynamespace', :expires_in => 1.day, :serializer => JSON, :compress => false, :value_max_bytes => 15000000 }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment