This is a spare key hidden under the mat of a house built in 2007
Tenable plugin 28333 tracks a Rails session fixation issue chain around CVE-2007-5380 and its incomplete fix CVE-2007-6077. The practical exposure is Ruby on Rails before 1.2.6, especially deployments that still accept URL-based sessions instead of forcing cookie-only session handling; Tenable explicitly recommends upgrading to 1.2.6+ and setting config.action_controller.session_options[:cookie_only] = true.
The vendor's MEDIUM label is technically fair in a vacuum, but operationally this deserves a downgrade to LOW for enterprise patch triage. The decisive friction is not exploit complexity but reachable population: this only lands on extremely old Rails 1.2-era apps, and even there the attacker still needs a victim to authenticate into an attacker-known session. If you truly have this, the bigger problem is that you still have a 2007-era internet app in production.
4 steps from start to impact.
Find a legacy Rails app that honors session IDs outside cookies
- Externally reachable Ruby on Rails application
- Rails version earlier than
1.2.6or incomplete fix state - URL-based sessions still accepted or cookie-only enforcement absent
- This is ancient software; most enterprises no longer run Rails
1.2.xin supported production stacks - Modern Rails defaults and common reverse-proxy patterns do not rely on URL session IDs
- Internet-wide fingerprinting for this exact condition is weak, so attacker target selection is noisy
28333 can flag this remotely, but precise confirmation still needs app-side version/config review.Pre-seed a session the attacker already knows
- Ability to generate or maintain a valid application session
- A delivery path to get the victim to use the attacker-controlled session
- Without XSS, header injection, or URL session acceptance, forcing the session ID onto a victim is materially harder
- If login correctly rotates the session ID with
reset_session, the chain dies here
_session_id parameters in URLs, but many legacy apps log this poorly.Victim authenticates into the fixed session
- Victim interaction
- Victim must successfully authenticate
- Application must not rotate the session after login
- This is a per-user compromise path, not unauthenticated mass RCE
- Short session lifetimes, explicit logout, and login-time session regeneration reduce utility
Reuse the authenticated session
- Valid authenticated session remains active
- Attacker can replay the session before expiry or invalidation
- Blast radius is bounded by one application session and victim privileges
- MFA at login does not help after the session is established, but it does make repeated account takeover attempts less reusable if sessions are rotated
config.action_controller.session_options[:cookie_only] = true or the equivalent control in the app stack so session IDs are not accepted in URLs. For a LOW finding there is no SLA; treat this as backlog hygiene and apply in the next normal maintenance change if the app cannot be retired first.reset_session or equivalent session regeneration. This directly kills the fixation step by invalidating the attacker-known session after the user authenticates; again, no SLA here beyond normal backlog handling._session_id or similar session tokens in query strings. This is a practical safety net for legacy apps where code changes move slowly; for LOW, deploy during routine hardening work.1.2.6 is a strategic end state.- TLS alone doesn't solve this; encryption protects the transport, not the app's willingness to accept an attacker-controlled session identifier.
- MFA at login doesn't prevent post-login session reuse if the application keeps the same session after authentication.
- Password resets alone may not evict an already-established application session unless the app invalidates active sessions on credential change.
The supporting signals.
| In-the-wild status | No current active exploitation signal found. I found no listing for CVE-2007-5380 or CVE-2007-6077 in CISA KEV, and Tenable marks exploit availability as false. |
|---|---|
| KEV status | Not in CISA KEV as checked against the current cisagov/kev-data catalog; no KEV dateAdded exists for either CVE. |
| Proof-of-concept availability | Manual technique exists; no meaningful modern weaponized PoC located. Rails' own security guide documents the session fixation workflow, but I did not find a currently maintained exploit repo worth treating as commodity tooling. |
| EPSS | CVE-2007-5380 is currently shown by a FIRST-derived feed at about 7.4% EPSS / 90.9th percentile. That sounds elevated until you remember EPSS does not account for the near-zero modern population of Rails 1.2.x. |
| CVSS vector | AV:N/AC:M/Au:N/C:P/I:P/A:P (6.8 / MEDIUM, CVSS v2). Translation: remotely reachable, no auth required, but the real attack still depends on session-seeding and victim login rather than direct server takeover. |
| Affected versions | CVE-2007-5380: Rails before 1.2.4. CVE-2007-6077: the 1.2.4 fix was incomplete, leaving Rails before 1.2.6 effectively exposed for this plugin's purposes. |
| Fixed versions | Upstream fix is Rails 1.2.6. Tenable also requires config.action_controller.session_options[:cookie_only] = true; distro advisories from Ubuntu/SUSE/Gentoo tracked this in late 2007. |
| Scanning/exposure data | Poor internet-wide fingerprintability. Shodan/Censys-style search is not a reliable measure for 'supports URL-based sessions', so exposure telemetry is weaker than for banner-identifiable CVEs. Practically, if you see this in your estate, it likely came from direct scanner interaction, not passive internet census. |
| Disclosure timeline | CVE-2007-5380 was published 2007-10-19 after the initial Rails 1.2.4 maintenance release on 2007-10-05. The incomplete-fix follow-up CVE-2007-6077 was published 2007-11-21, and Rails shipped 1.2.6 on 2007-11-24. |
| Reporter / source | Primary source is the Rails core team security/maintenance release chain; Tenable ties plugin 28333 directly to CVE-2007-5380 and CVE-2007-6077. |
Sources.
Why this verdict
- Down from vendor medium: reachable population is tiny. Requiring Rails
1.2.xin 2026 means you're dealing with a legacy outlier, not a broad enterprise patch wave. - Down again: this is session fixation, not server-side code execution. The attacker gets user-session hijack inside the app's privilege model, not immediate host takeover.
- Down again: the chain needs victim participation. The user still has to authenticate into an attacker-known session, and modern login flows that regenerate sessions break the exploit.
- Residual risk remains because the pre-auth attacker position is real. If an exposed legacy app still honors URL-based sessions, the attack can be mounted remotely against targeted users.
Why not higher?
This is not mass-exploitable infrastructure takeover. The attack requires a very old application, a legacy session model, and successful victim login into an attacker-prepared session. Each of those prerequisites materially narrows real-world abuse compared with what the CVSS base vector suggests.
Why not lower?
I would not mark this IGNORE because a confirmed finding still represents a genuine account-compromise path on a live app. If a scanner positively identifies this on an internet-facing system, defenders should verify it rather than dismiss it as pure archaeology.
Crowdsourced verification payload.
Run this on the application host or against a checked-out Rails app directory from CI. Invoke it as python3 verify_rails_session_fixation.py /path/to/app; no root privileges are needed, but the script needs read access to Gemfile.lock, config/environment.rb, or vendored Rails metadata.
#!/usr/bin/env python3
# verify_rails_session_fixation.py
# Detect likely exposure to Rails session fixation covered by Nessus plugin 28333.
# Exit codes: 0 PATCHED, 1 VULNERABLE, 2 UNKNOWN, 3 USAGE/ERROR
import os
import re
import sys
from pathlib import Path
USAGE = "Usage: python3 verify_rails_session_fixation.py /path/to/rails_app"
def parse_version_tuple(v):
parts = re.findall(r"\d+", v)
if not parts:
return None
nums = [int(x) for x in parts[:3]]
while len(nums) < 3:
nums.append(0)
return tuple(nums)
def find_rails_version(app):
# 1) Gemfile.lock
lock = app / "Gemfile.lock"
if lock.exists():
text = lock.read_text(errors="ignore")
m = re.search(r"^\s{4}rails \(([^)]+)\)", text, re.M)
if m:
return m.group(1).strip(), "Gemfile.lock"
# 2) vendor/rails/railties/lib/rails/version.rb or VERSION.yml
candidates = [
app / "vendor" / "rails" / "railties" / "lib" / "rails" / "version.rb",
app / "vendor" / "rails" / "railties" / "lib" / "rails" / "version.rb",
app / "vendor" / "rails" / "VERSION.yml",
app / "vendor" / "rails" / "version.rb",
]
for c in candidates:
if c.exists():
text = c.read_text(errors="ignore")
m = re.search(r"VERSION\s*=\s*['\"]([^'\"]+)['\"]", text)
if m:
return m.group(1).strip(), str(c.relative_to(app))
m = re.search(r"([0-9]+\.[0-9]+\.[0-9]+)", text)
if m:
return m.group(1).strip(), str(c.relative_to(app))
# 3) config/environment.rb RAILS_GEM_VERSION
envrb = app / "config" / "environment.rb"
if envrb.exists():
text = envrb.read_text(errors="ignore")
m = re.search(r"RAILS_GEM_VERSION\s*=\s*['\"]([^'\"]+)['\"]", text)
if m:
return m.group(1).strip(), "config/environment.rb"
return None, None
def detect_cookie_only(app):
paths = [
app / "config" / "environment.rb",
app / "config" / "initializers" / "session_store.rb",
]
patterns_true = [
r"session_options\s*\[\s*:cookie_only\s*\]\s*=\s*true",
r"session_options\s*\[\s*:cookie_session_id_only\s*\]\s*=\s*true",
]
patterns_false = [
r"session_options\s*\[\s*:cookie_only\s*\]\s*=\s*false",
r"session_options\s*\[\s*:cookie_session_id_only\s*\]\s*=\s*false",
]
found_files = []
for p in paths:
if p.exists():
text = p.read_text(errors="ignore")
found_files.append(str(p.relative_to(app)))
for pat in patterns_true:
if re.search(pat, text):
return True, found_files
for pat in patterns_false:
if re.search(pat, text):
return False, found_files
return None, found_files
def main():
if len(sys.argv) != 2:
print("UNKNOWN - " + USAGE)
sys.exit(3)
app = Path(sys.argv[1]).resolve()
if not app.exists() or not app.is_dir():
print(f"UNKNOWN - path not found or not a directory: {app}")
sys.exit(3)
version_str, source = find_rails_version(app)
if not version_str:
print("UNKNOWN - could not determine Rails version from Gemfile.lock, vendored Rails, or config/environment.rb")
sys.exit(2)
vt = parse_version_tuple(version_str)
if not vt:
print(f"UNKNOWN - unable to parse Rails version: {version_str}")
sys.exit(2)
cookie_only, checked = detect_cookie_only(app)
# Logic aligned to plugin 28333 / Rails 1.2.6 guidance.
if vt < (1, 2, 6):
print(f"VULNERABLE - Rails {version_str} detected via {source}; versions earlier than 1.2.6 fall in scope for session fixation / incomplete fix")
sys.exit(1)
if vt >= (1, 2, 6) and cookie_only is True:
checked_desc = ", ".join(checked) if checked else "config not inspected"
print(f"PATCHED - Rails {version_str} via {source}; cookie-only session enforcement detected in {checked_desc}")
sys.exit(0)
if vt >= (1, 2, 6) and cookie_only is False:
checked_desc = ", ".join(checked) if checked else "config not inspected"
print(f"UNKNOWN - Rails {version_str} via {source}; explicit non-cookie-only session setting found in {checked_desc}. Review manually against legacy session handling.")
sys.exit(2)
checked_desc = ", ".join(checked) if checked else "no relevant config files found"
print(f"UNKNOWN - Rails {version_str} via {source}; no explicit cookie-only setting found ({checked_desc}). Manual review required.")
sys.exit(2)
if __name__ == "__main__":
main()