From 80c468c2ad60bed33306589391a8e1f0bebff3fc Mon Sep 17 00:00:00 2001
From: Aaron Patterson <aaron.patterson@gmail.com>
Date: Fri, 25 Sep 2015 17:12:46 -0700
Subject: [PATCH 1/3] inherit from our AbstractStore

---
 .../lib/action_dispatch/middleware/session/cookie_store.rb  | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)

diff --git a/lib/action_dispatch/middleware/session/cookie_store.rb b/lib/action_dispatch/middleware/session/cookie_store.rb
index 02b6cfe72779..0e636b82577e 100644
--- a/lib/action_dispatch/middleware/session/cookie_store.rb
+++ b/lib/action_dispatch/middleware/session/cookie_store.rb
@@ -52,11 +52,7 @@ module Session
     # JavaScript before upgrading.
     #
     # Note that changing the secret key will invalidate all existing sessions!
-    class CookieStore < Rack::Session::Abstract::ID
-      include Compatibility
-      include StaleSessionCheck
-      include SessionObject
-
+    class CookieStore < AbstractStore
       def initialize(app, options={})
         super(app, options.merge!(:cookie_only => true))
       end

From 2a52a38cb51b65d71cf91fc960777213cf96f962 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= <rafael@franca.dev>
Date: Tue, 17 Dec 2019 18:44:59 -0300
Subject: [PATCH 2/3] Fix possible information leak / session hijacking
 vulnerability.

The `ActionDispatch::Session::MemcacheStore` is still vulnerable
given it requires the gem dalli to be updated as well.

CVE-2019-16782
---
 actionpack/lib/action_dispatch.rb             |  9 +--
 .../middleware/session/abstract_store.rb      | 14 ++++
 .../middleware/session/cache_store.rb         | 17 +++--
 .../middleware/session/cookie_store.rb        | 24 +++++--
 .../lib/action_dispatch/request/session.rb    |  8 ++-
 .../session/abstract_secure_store_test.rb     | 67 +++++++++++++++++++
 .../dispatch/session/cookie_store_test.rb     |  2 +-
 11 files changed, 180 insertions(+), 28 deletions(-)

diff --git a/lib/action_dispatch.rb b/lib/action_dispatch.rb
index 0822cdc0a6758..545f01696dd7d 100644
--- a/lib/action_dispatch.rb
+++ b/lib/action_dispatch.rb
@@ -79,10 +79,11 @@ module Http
   end
 
   module Session
-    autoload :AbstractStore,     'action_dispatch/middleware/session/abstract_store'
-    autoload :CookieStore,       'action_dispatch/middleware/session/cookie_store'
-    autoload :MemCacheStore,     'action_dispatch/middleware/session/mem_cache_store'
-    autoload :CacheStore,        'action_dispatch/middleware/session/cache_store'
+    autoload :AbstractStore,       'action_dispatch/middleware/session/abstract_store'
+    autoload :AbstractSecureStore, 'action_dispatch/middleware/session/abstract_store'
+    autoload :CookieStore,         'action_dispatch/middleware/session/cookie_store'
+    autoload :MemCacheStore,       'action_dispatch/middleware/session/mem_cache_store'
+    autoload :CacheStore,          'action_dispatch/middleware/session/cache_store'
   end
 
   mattr_accessor :test_app
diff --git a/lib/action_dispatch/middleware/session/abstract_store.rb b/lib/action_dispatch/middleware/session/abstract_store.rb
index 5b0be962235a7..ebe9a4a300ba5 100644
--- a/lib/action_dispatch/middleware/session/abstract_store.rb
+++ b/lib/action_dispatch/middleware/session/abstract_store.rb
@@ -86,5 +86,22 @@ class AbstractStore < Rack::Session::Abstract::ID
         request.cookie_jar[key] = cookie
       end
     end
+
+    class AbstractSecureStore < Rack::Session::Abstract::PersistedSecure
+      include Compatibility
+      include StaleSessionCheck
+      include SessionObject
+
+      def generate_sid
+        Rack::Session::SessionId.new(super)
+      end
+
+      private
+
+      def set_cookie(env, session_id, cookie)
+        request = ActionDispatch::Request.new(env)
+        request.cookie_jar[key] = cookie
+      end
+    end
   end
 end
diff --git a/lib/action_dispatch/middleware/session/cache_store.rb b/lib/action_dispatch/middleware/session/cache_store.rb
index a6d965a644148..c7b61f96ef8e5 100644
--- a/lib/action_dispatch/middleware/session/cache_store.rb
+++ b/lib/action_dispatch/middleware/session/cache_store.rb
@@ -5,7 +5,7 @@ module Session
     # Session store that uses an ActiveSupport::Cache::Store to store the sessions. This store is most useful
     # if you don't store critical data in your sessions and you don't need them to live for extended periods
     # of time.
-    class CacheStore < AbstractStore
+    class CacheStore < AbstractSecureStore
       # Create a new store. The cache to use can be passed in the <tt>:cache</tt> option. If it is
       # not specified, <tt>Rails.cache</tt> will be used.
       def initialize(app, options = {})
@@ -16,7 +16,7 @@ def initialize(app, options = {})
 
       # Get a session from the cache.
       def get_session(env, sid)
-        unless sid and session = @cache.read(cache_key(sid))
+        unless sid and session = get_session_with_fallback(sid)
           sid, session = generate_sid, {}
         end
         [sid, session]
@@ -24,7 +24,7 @@ def get_session(env, sid)
 
       # Set a session in the cache.
       def set_session(env, sid, session, options)
-        key = cache_key(sid)
+        key = cache_key(sid.private_id)
         if session
           @cache.write(key, session, :expires_in => options[:expire_after])
         else
@@ -35,14 +35,19 @@ def set_session(env, sid, session, options)
 
       # Remove a session from the cache.
       def destroy_session(env, sid, options)
-        @cache.delete(cache_key(sid))
+        @cache.delete(cache_key(sid.private_id))
+        @cache.delete(cache_key(sid.public_id))
         generate_sid
       end
 
       private
         # Turn the session id into a cache key.
-        def cache_key(sid)
-          "_session_id:#{sid}"
+        def cache_key(id)
+          "_session_id:#{id}"
+        end
+
+        def get_session_with_fallback(sid)
+          @cache.read(cache_key(sid.private_id)) || @cache.read(cache_key(sid.public_id))
         end
     end
   end
diff --git a/lib/action_dispatch/middleware/session/cookie_store.rb b/lib/action_dispatch/middleware/session/cookie_store.rb
index b7475d3682cfe..572be8bd9ecb4 100644
--- a/lib/action_dispatch/middleware/session/cookie_store.rb
+++ b/lib/action_dispatch/middleware/session/cookie_store.rb
@@ -52,7 +52,16 @@ module Session
     # JavaScript before upgrading.
     #
     # Note that changing the secret key will invalidate all existing sessions!
-    class CookieStore < AbstractStore
+    class CookieStore < AbstractSecureStore
+      class SessionId < DelegateClass(Rack::Session::SessionId)
+        attr_reader :cookie_value
+
+        def initialize(session_id, cookie_value = {})
+          super(session_id)
+          @cookie_value = cookie_value
+        end
+      end
+
       def initialize(app, options={})
         super(app, options.merge!(:cookie_only => true))
       end
@@ -60,7 +69,7 @@ def initialize(app, options = {})
       def destroy_session(env, session_id, options)
         new_sid = generate_sid unless options[:drop]
         # Reset hash and Assign the new session id
-        env["action_dispatch.request.unsigned_session_cookie"] = new_sid ? { "session_id" => new_sid } : {}
+        env["action_dispatch.request.unsigned_session_cookie"] = new_sid ? { "session_id" => new_sid.public_id } : {}
         new_sid
       end
 
@@ -68,7 +77,7 @@ def load_session(env)
         stale_session_check! do
           data = unpacked_cookie_data(env)
           data = persistent_session_id!(data)
-          [data["session_id"], data]
+          [Rack::Session::SessionId.new(data["session_id"]), data]
         end
       end
 
@@ -76,7 +85,8 @@ def load_session(env)
 
       def extract_session_id(env)
         stale_session_check! do
-          unpacked_cookie_data(env)["session_id"]
+          sid = unpacked_cookie_data(env)["session_id"]
+          sid && Rack::Session::SessionId.new(sid)
         end
       end
 
@@ -93,13 +103,13 @@ def unpacked_cookie_data(env)
 
       def persistent_session_id!(data, sid=nil)
         data ||= {}
-        data["session_id"] ||= sid || generate_sid
+        data["session_id"] ||= sid || generate_sid.public_id
         data
       end
 
       def set_session(env, sid, session_data, options)
-        session_data["session_id"] = sid
-        session_data
+        session_data["session_id"] = sid.public_id
+        SessionId.new(sid, session_data)
       end
 
       def set_cookie(env, session_id, cookie)
diff --git a/lib/action_dispatch/request/session.rb b/lib/action_dispatch/request/session.rb
index 000847e193e20..5c31cffbc1b12 100644
--- a/lib/action_dispatch/request/session.rb
+++ b/lib/action_dispatch/request/session.rb
@@ -87,7 +87,13 @@ def destroy
 
       def [](key)
         load_for_read!
-        @delegate[key.to_s]
+        key = key.to_s
+
+        if key == "session_id"
+          id&.public_id
+        else
+          @delegate[key]
+        end
       end
 
       def has_key?(key)
From 892eab777c418135ce0646e91bc9ebb08a29ab9b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= <rafael@franca.dev>
Date: Wed, 18 Dec 2019 17:36:20 -0300
Subject: [PATCH 3/3] Fix syntax error with Ruby 2.2

---
 lib/action_dispatch/request/session.rb | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/action_dispatch/request/session.rb b/lib/action_dispatch/request/session.rb
index 5c31cffbc1b12..4bfcf20548a34 100644
--- a/lib/action_dispatch/request/session.rb
+++ b/lib/action_dispatch/request/session.rb
@@ -90,7 +90,7 @@ def [](key)
         key = key.to_s
 
         if key == "session_id"
-          id&.public_id
+          id && id.public_id
         else
           @delegate[key]
         end
