Moderate severityNVD Advisory· Published Sep 8, 2009· Updated Apr 23, 2026
CVE-2009-3086
CVE-2009-3086
Description
A certain algorithm in Ruby on Rails 2.1.0 through 2.2.2, and 2.3.x before 2.3.4, leaks information about the complexity of message-digest signature verification in the cookie store, which might allow remote attackers to forge a digest via multiple attempts.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
actionpackRubyGems | >= 2.1.0, < 2.2.3 | 2.2.3 |
actionpackRubyGems | >= 2.3.0, < 2.3.4 | 2.3.4 |
activesupportRubyGems | >= 2.1.0, < 2.2.3 | 2.2.3 |
activesupportRubyGems | >= 2.3.0, < 2.3.4 | 2.3.4 |
Affected products
8cpe:2.3:a:rubyonrails:rails:2.1.0:*:*:*:*:*:*:*+ 7 more
- cpe:2.3:a:rubyonrails:rails:2.1.0:*:*:*:*:*:*:*
- cpe:2.3:a:rubyonrails:rails:2.1.1:*:*:*:*:*:*:*
- cpe:2.3:a:rubyonrails:rails:2.1.2:*:*:*:*:*:*:*
- cpe:2.3:a:rubyonrails:rails:2.2.0:*:*:*:*:*:*:*
- cpe:2.3:a:rubyonrails:rails:2.2.1:*:*:*:*:*:*:*
- cpe:2.3:a:rubyonrails:rails:2.2.2:*:*:*:*:*:*:*
- cpe:2.3:a:rubyonrails:rails:2.3.2:*:*:*:*:*:*:*
- cpe:2.3:a:rubyonrails:rails:2.3.3:*:*:*:*:*:*:*
Patches
3674f780d59a5Fix timing attack vulnerability in the Cookie Store
1 file changed · +14 −1
actionpack/lib/action_controller/session/cookie_store.rb+14 −1 modified@@ -140,7 +140,7 @@ def unmarshal(cookie) data, digest = cookie.split('--') # Do two checks to transparently support old double-escaped data. - unless digest == generate_digest(data) || digest == generate_digest(data = CGI.unescape(data)) + unless secure_compare(digest, generate_digest(data)) || secure_compare(digest, generate_digest(data = CGI.unescape(data))) delete raise TamperedWithCookie end @@ -164,4 +164,17 @@ def write_cookie(options) def clear_old_cookie_value @session.cgi.cookies[@cookie_options['name']].clear end + + # constant-time comparison algorithm to prevent timing attacks + def secure_compare(a, b) + if a.length == b.length + result = 0 + for i in 0..(a.length - 1) + result |= a[i] ^ b[i] + end + result == 0 + else + false + end + end end
1f07a89c5946Fix timing attack vulnerability in ActiveSupport::MessageVerifier.
1 file changed · +16 −3
activesupport/lib/active_support/message_verifier.rb+16 −3 modified@@ -25,10 +25,10 @@ def initialize(secret, digest = 'SHA1') def verify(signed_message) data, digest = signed_message.split("--") - if digest != generate_digest(data) - raise InvalidSignature - else + if secure_compare(digest, generate_digest(data)) Marshal.load(ActiveSupport::Base64.decode64(data)) + else + raise InvalidSignature end end @@ -38,6 +38,19 @@ def generate(value) end private + # constant-time comparison algorithm to prevent timing attacks + def secure_compare(a, b) + if a.length == b.length + result = 0 + for i in 0..(a.length - 1) + result |= a[i] ^ b[i] + end + result == 0 + else + false + end + end + def generate_digest(data) require 'openssl' unless defined?(OpenSSL) OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new(@digest), @secret, data)
d460c9a25560Add ActiveSupport::MessageVerifier to aid users who need to store tamper-proof messages in cookies etc.
4 files changed · +73 −0
activesupport/CHANGELOG+2 −0 modified@@ -1,5 +1,7 @@ *2.3.0 [Edge]* +* Added ActiveSupport::MessageVerifier to aid users who need to store signed messages. [Koz] + * Added ActiveSupport::BacktraceCleaner to cut down on backtrace noise according to filters and silencers [DHH] * Added Object#try. ( Taken from http://ozmm.org/posts/try.html ) [Chris Wanstrath]
activesupport/lib/active_support/message_verifier.rb+45 −0 added@@ -0,0 +1,45 @@ +module ActiveSupport + # MessageVerifier makes it easy to generate and verify messages which are signed + # to prevent tampering. + # + # This is useful for cases like remember-me tokens and auto-unsubscribe links where the + # session store isn't suitable or available. + # + # Remember Me: + # cookies[:remember_me] = @verifier.generate_message([@user.id, 2.weeks.from_now]) + # + # In the authentication filter: + # + # id, time = @verifier.verify_message(cookies[:remember_me]) + # if time < Time.now + # self.current_user = User.find(id) + # end + # + class MessageVerifier + class InvalidSignature < StandardError; end + + def initialize(secret, digest = 'SHA1') + @secret = secret + @digest = digest + end + + def verify_message(signed_message) + data, digest = signed_message.split("--") + if digest != generate_digest(data) + raise InvalidSignature + else + Marshal.load(ActiveSupport::Base64.decode64(data)) + end + end + + def generate_message(value) + data = ActiveSupport::Base64.encode64s(Marshal.dump(value)) + "#{data}--#{generate_digest(data)}" + end + + private + def generate_digest(data) + OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new(@digest), @secret, data) + end + end +end \ No newline at end of file
activesupport/lib/active_support.rb+1 −0 modified@@ -56,6 +56,7 @@ require 'active_support/time_with_zone' require 'active_support/secure_random' +require 'active_support/message_verifier' require 'active_support/rescuable'
activesupport/test/message_verifier_test.rb+25 −0 added@@ -0,0 +1,25 @@ +require 'abstract_unit' + +class MessageVerifierTest < Test::Unit::TestCase + def setup + @verifier = ActiveSupport::MessageVerifier.new("Hey, I'm a secret!") + @data = {:some=>"data", :now=>Time.now} + end + + def test_simple_round_tripping + message = @verifier.generate_message(@data) + assert_equal @data, @verifier.verify_message(message) + end + + def test_tampered_data_raises + data, hash = @verifier.generate_message(@data).split("--") + assert_not_verified("#{data.reverse}--#{hash}") + assert_not_verified("#{data}--#{hash.reverse}") + end + + def assert_not_verified(message) + assert_raises(ActiveSupport::MessageVerifier::InvalidSignature) do + @verifier.verify_message(message) + end + end +end
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
16- weblog.rubyonrails.org/2009/9/4/timing-weakness-in-ruby-on-railsnvdPatchWEB
- www.vupen.com/english/advisories/2009/2544nvdVendor Advisory
- github.com/advisories/GHSA-fg9w-g6m4-557jghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2009-3086ghsaADVISORY
- lists.opensuse.org/opensuse-security-announce/2009-10/msg00004.htmlnvdWEB
- www.debian.org/security/2011/dsa-2260nvdWEB
- github.com/rails/rails/commit/1f07a89c5946910fc28ea5ccd1da6af8a0f972a0ghsaWEB
- github.com/rails/rails/commit/674f780d59a5a7ec0301755d43a7b277a3ad2978ghsaWEB
- github.com/rails/rails/commit/d460c9a25560f43e7c3789abadf7b455053eb686ghsaWEB
- github.com/rubysec/ruby-advisory-db/blob/master/gems/actionpack/CVE-2009-3086.ymlghsaWEB
- github.com/rubysec/ruby-advisory-db/blob/master/gems/activesupport/CVE-2009-3086.ymlghsaWEB
- web.archive.org/web/20090906010200/http://www.vupen.com/english/advisories/2009/2544ghsaWEB
- web.archive.org/web/20090907001716/http://secunia.com/advisories/36600ghsaWEB
- web.archive.org/web/20200229150042/http://www.securityfocus.com/bid/37427ghsaWEB
- secunia.com/advisories/36600nvd
- www.securityfocus.com/bid/37427nvd
News mentions
0No linked articles in our index yet.