CVE-2007-6077: Incomplete Fix for Rails Session Fixation
An examination of CVE-2007-6077, where a flawed patch in Rails 1.2.4 failed to fully address session fixation due to mutable state in constants.
The Perils of Mutable State in Ruby
When we address security vulnerabilities in our applications, we often operate under intense pressure. We want to ship a fix quickly to protect our users. However, if we aren’t careful with how our language handles state and constants, our patches can introduce new flaws or fail to completely resolve the original issue.
This is exactly what happened with CVE-2007-6077, a moderate severity vulnerability affecting Ruby on Rails 1.2.4. To understand this flaw, we must first look at the vulnerability it was supposed to fix — CVE-2007-5380. The original issue allowed attackers to perform session fixation by passing a session identifier in the URL. To secure the framework, the Rails core team introduced a :cookie_only option to ensure session IDs were only accepted via cookies, not URL parameters.
While the intention was correct, the implementation in cgi_process.rb contained a subtle bug rooted in Ruby’s handling of constants.
The Flawed Implementation
In Ruby, constants are not entirely immutable. While reassigning a constant generates a warning, modifying the object that the constant references — like a Hash or an Array — is perfectly valid and happens silently.
When the Rails team attempted to enforce the new cookie-only behavior, they relied on a default options hash stored in a constant named DEFAULT_SESSION_OPTIONS. During the processing of a request, the CgiRequest class would read these options.
The flaw occurred because the code inadvertently removed the :cookie_only key directly from the DEFAULT_SESSION_OPTIONS hash after reading it. Because DEFAULT_SESSION_OPTIONS was a shared, mutable object in memory, this mutation affected the entire application globally.
Here is a conceptual illustration of the problem:
# An approximation of the underlying flaw in Rails 1.2.4
DEFAULT_SESSION_OPTIONS = {
database_manager: CGI::Session::PStore,
cookie_only: true
}
class CgiRequest
def initialize(options = {})
# The application merges the default options
@session_options = DEFAULT_SESSION_OPTIONS.merge(options)
# THE FLAW: The framework destructively removed the key
# from the original constant instead of the local copy.
cookie_only = DEFAULT_SESSION_OPTIONS.delete(:cookie_only)
if cookie_only
# Enforce cookie-only logic
end
end
end
The Consequence: A One-Time Fix
Because the :cookie_only attribute was deleted from the global constant, the protection only applied to the very first request the server processed.
When you booted up the Rails application, DEFAULT_SESSION_OPTIONS contained :cookie_only => true. The first instantiation of CgiRequest would successfully read this value, enforce the security check, and then permanently delete the key from the constant.
Every subsequent request handled by that server process would read DEFAULT_SESSION_OPTIONS, find no :cookie_only key, and fall back to the legacy behavior. This meant that after the first request, the application was fully vulnerable to the exact same session fixation attacks that the patch was designed to prevent. Attackers could once again append ?session_id=malicious_token to URLs and hijack user sessions.
Lessons for Durable Programming
This vulnerability teaches us a critical lesson about state management in long-running processes. When we build web applications, our classes and constants persist across thousands of requests.
We should adopt the following practices to avoid similar issues:
- Freeze Constants: If you define a constant that holds a configuration hash or array, you should freeze both the object itself and its contents to prevent accidental mutation. In modern Ruby, you can use
.freeze. - Duplicate Before Mutating: If a method needs to manipulate a shared configuration object, we must duplicate it (
.dupor.clone) before altering it. - Test State Across Requests: Unit tests often run in isolated environments where objects are instantiated fresh for every test case. This can mask bugs that only appear on the second or third request. We need integration tests that simulate multiple sequential requests to catch state-leakage issues.
By understanding how CVE-2007-6077 bypassed its intended fix, you can better protect your own Ruby applications against unintended state mutation.
Sponsored by Durable Programming
Need help maintaining or upgrading your Ruby on Rails application? Durable Programming specializes in keeping Rails apps secure, performant, and up-to-date.
Hire Durable Programming