diff --git a/lib/rack/session/pool.rb b/lib/rack/session/pool.rb index acdd8fe..c9b1e06 100644 --- a/lib/rack/session/pool.rb +++ b/lib/rack/session/pool.rb @@ -53,6 +53,7 @@ def find_session(req, sid) def write_session(req, session_id, new_session, options) @mutex.synchronize do + return false unless get_session_with_fallback(session_id) @pool.store session_id.private_id, new_session session_id end @@ -62,7 +63,12 @@ def delete_session(req, session_id, options) @mutex.synchronize do @pool.delete(session_id.public_id) @pool.delete(session_id.private_id) - generate_sid(use_mutex: false) unless options[:drop] + + unless options[:drop] + sid = generate_sid(use_mutex: false) + @pool.store(sid.private_id, {}) + sid + end end end diff --git a/lib/rack/session/version.rb b/lib/rack/session/version.rb index eadf093..8f27ff1 100644 --- a/lib/rack/session/version.rb +++ b/lib/rack/session/version.rb @@ -5,6 +5,6 @@ module Rack module Session - VERSION = "2.1.0" + VERSION = "2.1.1" end end diff --git a/releases.md b/releases.md index d29c5c3..a17e9b4 100644 --- a/releases.md +++ b/releases.md @@ -1,5 +1,9 @@ # Releases +## v2.1.1 + + - Prevent `Rack::Session::Pool` from recreating deleted sessions [CVE-2025-46336](https://github.com/rack/rack-session/security/advisories/GHSA-9j94-67jr-4cqj). + ## v2.1.0 - Improved compatibility with Ruby 3.3+ and Rack 3+. diff --git a/test/spec_session_pool.rb b/test/spec_session_pool.rb index f1c380d..51b6758 100644 --- a/test/spec_session_pool.rb +++ b/test/spec_session_pool.rb @@ -288,4 +288,52 @@ res = Rack::MockRequest.new(app).get("/") res["Set-Cookie"].must_be_nil end + + user_id_session = Rack::Lint.new(lambda do |env| + session = env["rack.session"] + + case env["PATH_INFO"] + when "/login" + session[:user_id] = 1 + when "/logout" + if session[:user_id].nil? + raise "User not logged in" + end + + session.delete(:user_id) + session.options[:renew] = true + when "/slow" + Fiber.yield + end + + Rack::Response.new(session.inspect).to_a + end) + + it "doesn't allow session id to be reused" do + app = Rack::Session::Pool.new(user_id_session) + + login_response = Rack::MockRequest.new(app).get("/login") + login_cookie = login_response["Set-Cookie"] + + slow_request = Fiber.new do + Rack::MockRequest.new(app).get("/slow", "HTTP_COOKIE" => login_cookie) + end + slow_request.resume + + # Check that the session is valid: + response = Rack::MockRequest.new(app).get("/", "HTTP_COOKIE" => login_cookie) + response.body.must_equal({"user_id" => 1}.to_s) + + logout_response = Rack::MockRequest.new(app).get("/logout", "HTTP_COOKIE" => login_cookie) + logout_cookie = logout_response["Set-Cookie"] + + # Check that the session id is different after logout: + login_cookie[session_match].wont_equal logout_cookie[session_match] + + slow_response = slow_request.resume + + # Check that the cookie can't be reused: + response = Rack::MockRequest.new(app).get("/", "HTTP_COOKIE" => login_cookie) + response.body.must_equal "{}" + end end