CVE-2019-25025
Description
The activerecord-session_store (aka Active Record Session Store) component through 1.1.3 for Ruby on Rails does not use a constant-time approach when delivering information about whether a guessed session ID is valid. Consequently, remote attackers can leverage timing discrepancies to achieve a correct guess in a relatively short amount of time. This is a related issue to CVE-2019-16782.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Active Record Session Store ≤1.1.3 leaks session ID validity via timing, enabling remote attackers to guess valid session IDs.
Vulnerability
Overview
The activerecord-session_store component for Ruby on Rails, through version 1.1.3, fails to use a constant-time comparison when checking whether a guessed session ID is valid [1]. This timing discrepancy allows a remote attacker to determine if a given session ID exists by measuring the response time of session lookup operations. The issue is related to CVE-2019-16782, which addressed a similar problem in the underlying session store logic [1].
Exploitation
Details
An attacker can exploit this vulnerability by sending a series of session ID guesses to the application and observing the time taken to respond. Because the comparison is not constant-time, valid session IDs will produce a measurably different response time compared to invalid ones [2]. No authentication is required; the attacker only needs network access to the Rails application. The attack can be performed remotely and does not require any special privileges.
Impact
Successful exploitation allows an attacker to enumerate valid session IDs, potentially leading to session hijacking. With a valid session ID, the attacker can impersonate the legitimate user associated with that session, gaining access to their account and data. The severity is heightened because session IDs are often used to maintain user authentication state [1].
Mitigation
The vulnerability is fixed in version 1.2.0 of the activerecord-session_store gem, which changes the session store to inherit from ActionDispatch::Session::AbstractSecureStore and uses constant-time session ID lookups [2][4]. Users are strongly advised to upgrade to the latest version. As a workaround, applications can configure the session store with secure_session_only: true to disable fallback to insecure sessions [3].
- NVD - CVE-2019-25025
- Address CVE-2019-16782 by kratob · Pull Request #151 · rails/activerecord-session_store
- GitHub - rails/activerecord-session_store: Active Record's Session Store extracted from Rails
- Merge pull request #151 from rails-lts/secure-session-store · rails/activerecord-session_store@9d4dd11
AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
activerecord-session_storeRubyGems | < 2.0.0 | 2.0.0 |
Affected products
163- Ruby on Rails/activerecord-session_storedescription
- ghsa-coords162 versionspkg:gem/activerecord-session_storepkg:rpm/suse/ardana-cobbler&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/ardana-cobbler&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/ardana-neutron&distro=SUSE%20OpenStack%20Cloud%209pkg:rpm/suse/ardana-swift&distro=SUSE%20OpenStack%20Cloud%209pkg:rpm/suse/cassandra&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/cassandra&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/cassandra&distro=SUSE%20OpenStack%20Cloud%209pkg:rpm/suse/cassandra&distro=SUSE%20OpenStack%20Cloud%20Crowbar%208pkg:rpm/suse/cassandra&distro=SUSE%20OpenStack%20Cloud%20Crowbar%209pkg:rpm/suse/crowbar-core&distro=SUSE%20OpenStack%20Cloud%20Crowbar%208pkg:rpm/suse/crowbar-openstack&distro=SUSE%20OpenStack%20Cloud%207pkg:rpm/suse/crowbar-openstack&distro=SUSE%20OpenStack%20Cloud%20Crowbar%208pkg:rpm/suse/crowbar-openstack&distro=SUSE%20OpenStack%20Cloud%20Crowbar%209pkg:rpm/suse/documentation-hpe-helion-openstack-installation&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/documentation-hpe-helion-openstack-operations&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/documentation-hpe-helion-openstack-opsconsole&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/documentation-hpe-helion-openstack-planning&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/documentation-hpe-helion-openstack-security&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/documentation-hpe-helion-openstack-user&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/documentation-suse-openstack-cloud-deployment&distro=SUSE%20OpenStack%20Cloud%20Crowbar%208pkg:rpm/suse/documentation-suse-openstack-cloud-installation&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/documentation-suse-openstack-cloud-operations&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/documentation-suse-openstack-cloud-opsconsole&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/documentation-suse-openstack-cloud-planning&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/documentation-suse-openstack-cloud-security&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/documentation-suse-openstack-cloud-supplement&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/documentation-suse-openstack-cloud-supplement&distro=SUSE%20OpenStack%20Cloud%20Crowbar%208pkg:rpm/suse/documentation-suse-openstack-cloud-upstream-admin&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/documentation-suse-openstack-cloud-upstream-admin&distro=SUSE%20OpenStack%20Cloud%20Crowbar%208pkg:rpm/suse/documentation-suse-openstack-cloud-upstream-user&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/documentation-suse-openstack-cloud-upstream-user&distro=SUSE%20OpenStack%20Cloud%20Crowbar%208pkg:rpm/suse/documentation-suse-openstack-cloud-user&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/grafana&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/grafana&distro=SUSE%20OpenStack%20Cloud%207pkg:rpm/suse/grafana&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/grafana&distro=SUSE%20OpenStack%20Cloud%209pkg:rpm/suse/grafana&distro=SUSE%20OpenStack%20Cloud%20Crowbar%208pkg:rpm/suse/grafana&distro=SUSE%20OpenStack%20Cloud%20Crowbar%209pkg:rpm/suse/kibana&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/kibana&distro=SUSE%20OpenStack%20Cloud%207pkg:rpm/suse/kibana&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/kibana&distro=SUSE%20OpenStack%20Cloud%209pkg:rpm/suse/kibana&distro=SUSE%20OpenStack%20Cloud%20Crowbar%208pkg:rpm/suse/kibana&distro=SUSE%20OpenStack%20Cloud%20Crowbar%209pkg:rpm/suse/monasca-installer&distro=SUSE%20OpenStack%20Cloud%207pkg:rpm/suse/openstack-dashboard&distro=SUSE%20OpenStack%20Cloud%209pkg:rpm/suse/openstack-dashboard&distro=SUSE%20OpenStack%20Cloud%20Crowbar%209pkg:rpm/suse/openstack-heat-templates&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/openstack-heat-templates&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/openstack-heat-templates&distro=SUSE%20OpenStack%20Cloud%20Crowbar%208pkg:rpm/suse/openstack-ironic&distro=SUSE%20OpenStack%20Cloud%209pkg:rpm/suse/openstack-ironic&distro=SUSE%20OpenStack%20Cloud%20Crowbar%209pkg:rpm/suse/openstack-monasca-installer&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/openstack-monasca-installer&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/openstack-monasca-installer&distro=SUSE%20OpenStack%20Cloud%20Crowbar%208pkg:rpm/suse/openstack-neutron&distro=SUSE%20OpenStack%20Cloud%209pkg:rpm/suse/openstack-neutron&distro=SUSE%20OpenStack%20Cloud%20Crowbar%209pkg:rpm/suse/openstack-neutron-gbp&distro=SUSE%20OpenStack%20Cloud%209pkg:rpm/suse/openstack-neutron-gbp&distro=SUSE%20OpenStack%20Cloud%20Crowbar%209pkg:rpm/suse/openstack-nova&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/openstack-nova&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/openstack-nova&distro=SUSE%20OpenStack%20Cloud%209pkg:rpm/suse/openstack-nova&distro=SUSE%20OpenStack%20Cloud%20Crowbar%208pkg:rpm/suse/openstack-nova&distro=SUSE%20OpenStack%20Cloud%20Crowbar%209pkg:rpm/suse/openstack-nova-doc&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/openstack-nova-doc&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/openstack-nova-doc&distro=SUSE%20OpenStack%20Cloud%20Crowbar%208pkg:rpm/suse/python-Django1&distro=SUSE%20OpenStack%20Cloud%209pkg:rpm/suse/python-Django1&distro=SUSE%20OpenStack%20Cloud%20Crowbar%209pkg:rpm/suse/python-Django&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/python-Django&distro=SUSE%20OpenStack%20Cloud%207pkg:rpm/suse/python-Django&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/python-Django&distro=SUSE%20OpenStack%20Cloud%20Crowbar%208pkg:rpm/suse/python-elementpath&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/python-elementpath&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/python-elementpath&distro=SUSE%20OpenStack%20Cloud%209pkg:rpm/suse/python-elementpath&distro=SUSE%20OpenStack%20Cloud%20Crowbar%208pkg:rpm/suse/python-elementpath&distro=SUSE%20OpenStack%20Cloud%20Crowbar%209pkg:rpm/suse/python-eventlet&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/python-eventlet&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/python-eventlet&distro=SUSE%20OpenStack%20Cloud%20Crowbar%208pkg:rpm/suse/python-py&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/python-py&distro=SUSE%20OpenStack%20Cloud%207pkg:rpm/suse/python-py&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/python-py&distro=SUSE%20OpenStack%20Cloud%209pkg:rpm/suse/python-py&distro=SUSE%20OpenStack%20Cloud%20Crowbar%208pkg:rpm/suse/python-py&distro=SUSE%20OpenStack%20Cloud%20Crowbar%209pkg:rpm/suse/python-pysaml2&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/python-pysaml2&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/python-pysaml2&distro=SUSE%20OpenStack%20Cloud%209pkg:rpm/suse/python-pysaml2&distro=SUSE%20OpenStack%20Cloud%20Crowbar%208pkg:rpm/suse/python-pysaml2&distro=SUSE%20OpenStack%20Cloud%20Crowbar%209pkg:rpm/suse/python-xmlschema&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/python-xmlschema&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/python-xmlschema&distro=SUSE%20OpenStack%20Cloud%209pkg:rpm/suse/python-xmlschema&distro=SUSE%20OpenStack%20Cloud%20Crowbar%208pkg:rpm/suse/python-xmlschema&distro=SUSE%20OpenStack%20Cloud%20Crowbar%209pkg:rpm/suse/rubygem-activerecord-session_store&distro=SUSE%20OpenStack%20Cloud%207pkg:rpm/suse/rubygem-activerecord-session_store&distro=SUSE%20OpenStack%20Cloud%20Crowbar%208pkg:rpm/suse/rubygem-activerecord-session_store&distro=SUSE%20OpenStack%20Cloud%20Crowbar%209pkg:rpm/suse/venv-openstack-aodh&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/venv-openstack-aodh&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/venv-openstack-barbican&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/venv-openstack-barbican&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/venv-openstack-barbican&distro=SUSE%20OpenStack%20Cloud%209pkg:rpm/suse/venv-openstack-ceilometer&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/venv-openstack-ceilometer&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/venv-openstack-cinder&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/venv-openstack-cinder&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/venv-openstack-cinder&distro=SUSE%20OpenStack%20Cloud%209pkg:rpm/suse/venv-openstack-designate&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/venv-openstack-designate&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/venv-openstack-designate&distro=SUSE%20OpenStack%20Cloud%209pkg:rpm/suse/venv-openstack-freezer&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/venv-openstack-freezer&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/venv-openstack-glance&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/venv-openstack-glance&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/venv-openstack-glance&distro=SUSE%20OpenStack%20Cloud%209pkg:rpm/suse/venv-openstack-heat&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/venv-openstack-heat&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/venv-openstack-heat&distro=SUSE%20OpenStack%20Cloud%209pkg:rpm/suse/venv-openstack-horizon&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/venv-openstack-horizon&distro=SUSE%20OpenStack%20Cloud%209pkg:rpm/suse/venv-openstack-horizon-hpe&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/venv-openstack-ironic&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/venv-openstack-ironic&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/venv-openstack-ironic&distro=SUSE%20OpenStack%20Cloud%209pkg:rpm/suse/venv-openstack-keystone&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/venv-openstack-keystone&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/venv-openstack-keystone&distro=SUSE%20OpenStack%20Cloud%209pkg:rpm/suse/venv-openstack-magnum&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/venv-openstack-magnum&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/venv-openstack-magnum&distro=SUSE%20OpenStack%20Cloud%209pkg:rpm/suse/venv-openstack-manila&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/venv-openstack-manila&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/venv-openstack-manila&distro=SUSE%20OpenStack%20Cloud%209pkg:rpm/suse/venv-openstack-monasca-ceilometer&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/venv-openstack-monasca-ceilometer&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/venv-openstack-monasca-ceilometer&distro=SUSE%20OpenStack%20Cloud%209pkg:rpm/suse/venv-openstack-monasca&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/venv-openstack-monasca&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/venv-openstack-monasca&distro=SUSE%20OpenStack%20Cloud%209pkg:rpm/suse/venv-openstack-murano&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/venv-openstack-murano&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/venv-openstack-neutron&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/venv-openstack-neutron&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/venv-openstack-neutron&distro=SUSE%20OpenStack%20Cloud%209pkg:rpm/suse/venv-openstack-nova&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/venv-openstack-nova&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/venv-openstack-nova&distro=SUSE%20OpenStack%20Cloud%209pkg:rpm/suse/venv-openstack-octavia&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/venv-openstack-octavia&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/venv-openstack-octavia&distro=SUSE%20OpenStack%20Cloud%209pkg:rpm/suse/venv-openstack-sahara&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/venv-openstack-sahara&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/venv-openstack-sahara&distro=SUSE%20OpenStack%20Cloud%209pkg:rpm/suse/venv-openstack-swift&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/venv-openstack-swift&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/venv-openstack-swift&distro=SUSE%20OpenStack%20Cloud%209pkg:rpm/suse/venv-openstack-trove&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/venv-openstack-trove&distro=SUSE%20OpenStack%20Cloud%208
< 2.0.0+ 161 more
- (no CPE)range: < 2.0.0
- (no CPE)range: < 8.0+git.1614096566.e8c2b27-3.44.3
- (no CPE)range: < 8.0+git.1614096566.e8c2b27-3.44.3
- (no CPE)range: < 9.0+git.1615223676.777f0b3-3.25.2
- (no CPE)range: < 9.0+git.1618235096.90974ed-3.10.2
- (no CPE)range: < 3.11.10-5.3.5
- (no CPE)range: < 3.11.10-5.3.5
- (no CPE)range: < 3.11.10-3.3.3
- (no CPE)range: < 3.11.10-5.3.5
- (no CPE)range: < 3.11.10-3.3.3
- (no CPE)range: < 5.0+git.1622489449.a8e60e238-3.50.4
- (no CPE)range: < 4.0+git.1616146720.44daffca0-9.81.2
- (no CPE)range: < 5.0+git.1616001417.67fd9c2a1-4.52.5
- (no CPE)range: < 6.0+git.1616146717.a89ae0f4e-3.34.4
- (no CPE)range: < 8.20210512-1.32.5
- (no CPE)range: < 8.20210512-1.32.5
- (no CPE)range: < 8.20210512-1.32.5
- (no CPE)range: < 8.20210512-1.32.5
- (no CPE)range: < 8.20210512-1.32.5
- (no CPE)range: < 8.20210512-1.32.5
- (no CPE)range: < 8.20210512-1.32.5
- (no CPE)range: < 8.20210512-1.32.5
- (no CPE)range: < 8.20210512-1.32.5
- (no CPE)range: < 8.20210512-1.32.5
- (no CPE)range: < 8.20210512-1.32.5
- (no CPE)range: < 8.20210512-1.32.5
- (no CPE)range: < 8.20210512-1.32.5
- (no CPE)range: < 8.20210512-1.32.5
- (no CPE)range: < 8.20210512-1.32.5
- (no CPE)range: < 8.20210512-1.32.5
- (no CPE)range: < 8.20210512-1.32.5
- (no CPE)range: < 8.20210512-1.32.5
- (no CPE)range: < 8.20210512-1.32.5
- (no CPE)range: < 6.7.4-4.18.2
- (no CPE)range: < 6.7.4-1.24.2
- (no CPE)range: < 6.7.4-4.18.2
- (no CPE)range: < 6.7.4-3.23.2
- (no CPE)range: < 6.7.4-4.18.2
- (no CPE)range: < 6.7.4-3.23.2
- (no CPE)range: < 4.6.6-3.9.2
- (no CPE)range: < 4.6.6-9.2
- (no CPE)range: < 4.6.6-3.9.2
- (no CPE)range: < 4.6.6-4.9.2
- (no CPE)range: < 4.6.6-3.9.2
- (no CPE)range: < 4.6.6-4.9.2
- (no CPE)range: < 20180608_12.47-16.2
- (no CPE)range: < 14.1.1~dev11-3.24.6
- (no CPE)range: < 14.1.1~dev11-3.24.6
- (no CPE)range: < 0.0.0+git.1623056900.7917e18-3.21.3
- (no CPE)range: < 0.0.0+git.1623056900.7917e18-3.21.3
- (no CPE)range: < 0.0.0+git.1623056900.7917e18-3.21.3
- (no CPE)range: < 11.1.5~dev17-3.25.5
- (no CPE)range: < 11.1.5~dev17-3.25.5
- (no CPE)range: < 20190923_16.32-3.18.2
- (no CPE)range: < 20190923_16.32-3.18.2
- (no CPE)range: < 20190923_16.32-3.18.2
- (no CPE)range: < 13.0.8~dev164-3.37.4
- (no CPE)range: < 13.0.8~dev164-3.37.4
- (no CPE)range: < 12.0.1~dev29-3.25.3
- (no CPE)range: < 12.0.1~dev29-3.25.3
- (no CPE)range: < 16.1.9~dev92-3.48.5
- (no CPE)range: < 16.1.9~dev92-3.48.5
- (no CPE)range: < 18.3.1~dev82-3.37.6
- (no CPE)range: < 16.1.9~dev92-3.48.5
- (no CPE)range: < 18.3.1~dev82-3.37.6
- (no CPE)range: < 16.1.9~dev92-3.48.5
- (no CPE)range: < 16.1.9~dev92-3.48.5
- (no CPE)range: < 16.1.9~dev92-3.48.5
- (no CPE)range: < 1.11.29-3.25.1
- (no CPE)range: < 1.11.29-3.25.1
- (no CPE)range: < 1.11.29-3.25.3
- (no CPE)range: < 1.8.19-3.29.1
- (no CPE)range: < 1.11.29-3.25.3
- (no CPE)range: < 1.11.29-3.25.3
- (no CPE)range: < 1.3.1-1.3.2
- (no CPE)range: < 1.3.1-1.3.2
- (no CPE)range: < 1.3.1-1.3.2
- (no CPE)range: < 1.3.1-1.3.2
- (no CPE)range: < 1.3.1-1.3.2
- (no CPE)range: < 0.20.0-6.3.3
- (no CPE)range: < 0.20.0-6.3.3
- (no CPE)range: < 0.20.0-6.3.3
- (no CPE)range: < 1.4.34-3.3.3
- (no CPE)range: < 1.8.1-11.16.2
- (no CPE)range: < 1.4.34-3.3.3
- (no CPE)range: < 1.5.4-3.3.2
- (no CPE)range: < 1.4.34-3.3.3
- (no CPE)range: < 1.5.4-3.3.2
- (no CPE)range: < 4.0.2-5.9.2
- (no CPE)range: < 4.0.2-5.9.2
- (no CPE)range: < 4.5.0-4.6.2
- (no CPE)range: < 4.0.2-5.9.2
- (no CPE)range: < 4.5.0-4.6.2
- (no CPE)range: < 1.0.18-1.3.3
- (no CPE)range: < 1.0.18-1.3.3
- (no CPE)range: < 1.0.18-1.3.2
- (no CPE)range: < 1.0.18-1.3.3
- (no CPE)range: < 1.0.18-1.3.2
- (no CPE)range: < 0.1.2-3.4.2
- (no CPE)range: < 0.1.2-3.3.2
- (no CPE)range: < 0.1.2-4.3.2
- (no CPE)range: < 5.1.1~dev7-12.32.3
- (no CPE)range: < 5.1.1~dev7-12.32.3
- (no CPE)range: < 5.0.2~dev3-12.33.3
- (no CPE)range: < 5.0.2~dev3-12.33.3
- (no CPE)range: < 7.0.1~dev24-3.23.1
- (no CPE)range: < 9.0.8~dev7-12.30.3
- (no CPE)range: < 9.0.8~dev7-12.30.3
- (no CPE)range: < 11.2.3~dev29-14.34.2
- (no CPE)range: < 11.2.3~dev29-14.34.2
- (no CPE)range: < 13.0.10~dev20-3.26.1
- (no CPE)range: < 5.0.3~dev7-12.31.3
- (no CPE)range: < 5.0.3~dev7-12.31.3
- (no CPE)range: < 7.0.2~dev2-3.23.1
- (no CPE)range: < 5.0.0.0~xrc2~dev2-10.28.3
- (no CPE)range: < 5.0.0.0~xrc2~dev2-10.28.3
- (no CPE)range: < 15.0.3~dev3-12.31.3
- (no CPE)range: < 15.0.3~dev3-12.31.3
- (no CPE)range: < 17.0.1~dev30-3.21.1
- (no CPE)range: < 9.0.8~dev22-12.33.2
- (no CPE)range: < 9.0.8~dev22-12.33.2
- (no CPE)range: < 11.0.4~dev4-3.23.1
- (no CPE)range: < 12.0.5~dev6-14.36.6
- (no CPE)range: < 14.1.1~dev11-4.27.3
- (no CPE)range: < 12.0.5~dev6-14.36.3
- (no CPE)range: < 9.1.8~dev8-12.33.3
- (no CPE)range: < 9.1.8~dev8-12.33.3
- (no CPE)range: < 11.1.5~dev17-4.21.2
- (no CPE)range: < 12.0.4~dev11-11.35.3
- (no CPE)range: < 12.0.4~dev11-11.35.3
- (no CPE)range: < 14.2.1~dev4-3.24.3
- (no CPE)range: < 5.0.2_5.0.2_5.0.2~dev31-11.32.2
- (no CPE)range: < 5.0.2_5.0.2_5.0.2~dev31-11.32.2
- (no CPE)range: < 7.2.1~dev1-4.23.1
- (no CPE)range: < 5.1.1~dev5-12.37.3
- (no CPE)range: < 5.1.1~dev5-12.37.3
- (no CPE)range: < 7.4.2~dev60-3.29.1
- (no CPE)range: < 1.5.1_1.5.1_1.5.1~dev3-8.28.3
- (no CPE)range: < 1.5.1_1.5.1_1.5.1~dev3-8.28.3
- (no CPE)range: < 1.8.2~dev3-3.23.2
- (no CPE)range: < 2.2.2~dev1-11.28.3
- (no CPE)range: < 2.2.2~dev1-11.28.3
- (no CPE)range: < 2.7.1~dev10-3.21.1
- (no CPE)range: < 4.0.2~dev2-12.28.3
- (no CPE)range: < 4.0.2~dev2-12.28.3
- (no CPE)range: < 11.0.9~dev69-13.38.3
- (no CPE)range: < 11.0.9~dev69-13.38.3
- (no CPE)range: < 13.0.8~dev164-6.27.3
- (no CPE)range: < 16.1.9~dev92-11.36.3
- (no CPE)range: < 16.1.9~dev92-11.36.3
- (no CPE)range: < 18.3.1~dev82-3.27.3
- (no CPE)range: < 1.0.6~dev3-12.33.3
- (no CPE)range: < 1.0.6~dev3-12.33.3
- (no CPE)range: < 3.2.3~dev7-4.23.1
- (no CPE)range: < 7.0.5~dev4-11.32.3
- (no CPE)range: < 7.0.5~dev4-11.32.3
- (no CPE)range: < 9.0.2~dev15-3.23.1
- (no CPE)range: < 2.15.2_2.15.2_2.15.2~dev32-11.23.3
- (no CPE)range: < 2.15.2_2.15.2_2.15.2~dev32-11.23.3
- (no CPE)range: < 2.19.2~dev48-2.18.1
- (no CPE)range: < 8.0.2~dev2-11.32.3
- (no CPE)range: < 8.0.2~dev2-11.32.3
Patches
19d4dd113d301Merge pull request #151 from rails-lts/secure-session-store
6 files changed · +187 −27
activerecord-session_store.gemspec+4 −4 modified@@ -19,10 +19,10 @@ Gem::Specification.new do |s| s.extra_rdoc_files = %w( README.md ) s.rdoc_options.concat ['--main', 'README.md'] - s.add_dependency('activerecord', '>= 5.2') - s.add_dependency('actionpack', '>= 5.2') - s.add_dependency('railties', '>= 5.2') - s.add_dependency('rack', '>= 2.0.0', '< 3') + s.add_dependency('activerecord', '>= 5.2.4.1') + s.add_dependency('actionpack', '>= 5.2.4.1') + s.add_dependency('railties', '>= 5.2.4.1') + s.add_dependency('rack', '>= 2.0.8', '< 3') s.add_dependency('multi_json', '~> 1.11', '>= 1.11.2') s.add_development_dependency('sqlite3')
lib/action_dispatch/session/active_record_store.rb+29 −13 modified@@ -52,7 +52,7 @@ module Session # # The example SqlBypass class is a generic SQL session store. You may # use it as a basis for high-performance database-specific stores. - class ActiveRecordStore < ActionDispatch::Session::AbstractStore + class ActiveRecordStore < ActionDispatch::Session::AbstractSecureStore # The class used for session storage. Defaults to # ActiveRecord::SessionStore::Session cattr_accessor :session_class @@ -63,11 +63,11 @@ class ActiveRecordStore < ActionDispatch::Session::AbstractStore private def get_session(request, sid) logger.silence do - unless sid and session = @@session_class.find_by_session_id(sid) + unless sid and session = get_session_with_fallback(sid) # If the sid was nil or if there is no pre-existing session under the sid, # force the generation of a new sid and associate a new session associated with the new sid sid = generate_sid - session = @@session_class.new(:session_id => sid, :data => {}) + session = @@session_class.new(:session_id => sid.private_id, :data => {}) end request.env[SESSION_RECORD_KEY] = session [sid, session.data] @@ -76,7 +76,7 @@ def get_session(request, sid) def write_session(request, sid, session_data, options) logger.silence do - record = get_session_model(request, sid) + record, sid = get_session_model(request, sid) record.data = session_data return false unless record.save @@ -94,7 +94,7 @@ def write_session(request, sid, session_data, options) def delete_session(request, session_id, options) logger.silence do if sid = current_session_id(request) - if model = @@session_class.find_by_session_id(sid) + if model = get_session_with_fallback(sid) data = model.data model.destroy end @@ -106,7 +106,7 @@ def delete_session(request, session_id, options) new_sid = generate_sid if options[:renew] - new_model = @@session_class.new(:session_id => new_sid, :data => data) + new_model = @@session_class.new(:session_id => new_sid.private_id, :data => data) new_model.save request.env[SESSION_RECORD_KEY] = new_model end @@ -117,24 +117,35 @@ def delete_session(request, session_id, options) def get_session_model(request, id) logger.silence do - model = @@session_class.find_by_session_id(id) - if !model + model = get_session_with_fallback(id) + unless model id = generate_sid - model = @@session_class.new(:session_id => id, :data => {}) + model = @@session_class.new(:session_id => id.private_id, :data => {}) model.save end if request.env[ENV_SESSION_OPTIONS_KEY][:id].nil? request.env[SESSION_RECORD_KEY] = model else request.env[SESSION_RECORD_KEY] ||= model end - model + [model, id] + end + end + + def get_session_with_fallback(sid) + if sid && !self.class.private_session_id?(sid.public_id) + if (secure_session = @@session_class.find_by_session_id(sid.private_id)) + secure_session + elsif (insecure_session = @@session_class.find_by_session_id(sid.public_id)) + insecure_session.session_id = sid.private_id # this causes the session to be secured + insecure_session + end end end def find_session(request, id) - model = get_session_model(request, id) - [model.session_id, model.data] + model, id = get_session_model(request, id) + [id, model.data] end module NilLogger @@ -146,7 +157,12 @@ def self.silence def logger ActiveRecord::Base.logger || NilLogger end + + def self.private_session_id?(session_id) + # user tried to retrieve a session by a private key? + session_id =~ /\A\d+::/ + end + end end end -
lib/active_record/session_store/session.rb+23 −2 modified@@ -38,8 +38,8 @@ def setup_sessid_compatibility! # Reset column info since it may be stale. reset_column_information if columns_hash['sessid'] - def self.find_by_session_id(*args) - find_by_sessid(*args) + def self.find_by_session_id(session_id) + find_by_sessid(session_id) end define_method(:session_id) { sessid } @@ -71,6 +71,27 @@ def loaded? @data end + # This method was introduced when addressing CVE-2019-16782 + # (see https://github.com/rack/rack/security/advisories/GHSA-hrqr-hxpp-chr3). + # Sessions created on version <= 1.1.3 were guessable via a timing attack. + # To secure sessions created on those old versions, this method can be called + # on all existing sessions in the database. Users will not lose their session + # when this is done. + def secure! + session_id_column = if self.class.columns_hash['sessid'] + :sessid + else + :session_id + end + raw_session_id = read_attribute(session_id_column) + if ActionDispatch::Session::ActiveRecordStore.private_session_id?(raw_session_id) + # is already private, nothing to do + else + session_id_object = Rack::Session::SessionId.new(raw_session_id) + update_column(session_id_column, session_id_object.private_id) + end + end + private def serialize_data! unless loaded?
lib/active_record/session_store/sql_bypass.rb+11 −7 modified@@ -60,15 +60,16 @@ def connection_pool # Look up a session by id and deserialize its data if found. def find_by_session_id(session_id) - if record = connection.select_one("SELECT #{connection.quote_column_name(data_column)} AS data FROM #{@@table_name} WHERE #{connection.quote_column_name(@@session_id_column)}=#{connection.quote(session_id.to_s)}") - new(:session_id => session_id, :serialized_data => record['data']) + if record = connection.select_one("SELECT #{connection.quote_column_name(data_column)} AS data FROM #{@@table_name} WHERE #{connection.quote_column_name(@@session_id_column)}=#{connection.quote(session_id)}") + new(:session_id => session_id, :retrieved_by => session_id, :serialized_data => record['data']) end end end delegate :connection, :connection=, :connection_pool, :connection_pool=, :to => self - attr_reader :session_id, :new_record + attr_reader :new_record + attr_accessor :session_id alias :new_record? :new_record attr_writer :data @@ -77,7 +78,8 @@ def find_by_session_id(session_id) # telling us to postpone deserializing until the data is requested. # We need to handle a normal data attribute in case of a new record. def initialize(attributes) - @session_id = attributes[:session_id] + @session_id = attributes[:session_id] + @retrieved_by = attributes[:retrieved_by] @data = attributes[:data] @serialized_data = attributes[:serialized_data] @new_record = @serialized_data.nil? @@ -122,8 +124,10 @@ def save else connect.update <<-end_sql, 'Update session' UPDATE #{table_name} - SET #{connect.quote_column_name(data_column)}=#{connect.quote(serialized_data)} - WHERE #{connect.quote_column_name(session_id_column)}=#{connect.quote(session_id)} + SET + #{connect.quote_column_name(data_column)}=#{connect.quote(serialized_data)}, + #{connect.quote_column_name(session_id_column)}=#{connect.quote(@session_id)} + WHERE #{connect.quote_column_name(session_id_column)}=#{connect.quote(@retrieved_by)} end_sql end end @@ -134,7 +138,7 @@ def destroy connect = connection connect.delete <<-end_sql, 'Destroy session' DELETE FROM #{table_name} - WHERE #{connect.quote_column_name(session_id_column)}=#{connect.quote(session_id.to_s)} + WHERE #{connect.quote_column_name(session_id_column)}=#{connect.quote(session_id)} end_sql end end
test/action_controller_test.rb+62 −1 modified@@ -18,7 +18,7 @@ def get_session_value end def get_session_id - render :plain => "#{request.session.id}" + render :plain => "#{request.session['session_id']}" end def call_reset_session @@ -257,4 +257,65 @@ def test_session_store_with_all_domains assert_response :success end end + + %w{ session sql_bypass }.each do |class_name| + define_method :"test_sessions_are_indexed_by_a_hashed_session_id_for_#{class_name}" do + with_store(class_name) do + with_test_route_set do + get '/set_session_value' + assert_response :success + public_session_id = cookies['_session_id'] + + session = ActiveRecord::SessionStore::Session.last + assert session + assert_not_equal public_session_id, session.session_id + + expected_private_id = Rack::Session::SessionId.new(public_session_id).private_id + + assert_equal expected_private_id, session.session_id + end + end + end + + define_method :"test_unsecured_sessions_are_retrieved_and_migrated_for_#{class_name}" do + with_store(class_name) do + with_test_route_set do + get '/set_session_value', params: { foo: 'baz' } + assert_response :success + public_session_id = cookies['_session_id'] + + session = ActiveRecord::SessionStore::Session.last + session.data # otherwise we cannot save + session.session_id = public_session_id + session.save! + + get '/get_session_value' + assert_response :success + assert_equal 'foo: "baz"', response.body + + session = ActiveRecord::SessionStore::Session.last + assert_not_equal public_session_id, session.read_attribute(:session_id) + end + end + end + + # to avoid a different kind of timing attack + define_method :"test_sessions_cannot_be_retrieved_by_their_private_session_id_for_#{class_name}" do + with_store(class_name) do + with_test_route_set do + get '/set_session_value', params: { foo: 'baz' } + assert_response :success + + session = ActiveRecord::SessionStore::Session.last + private_session_id = session.read_attribute(:session_id) + + cookies.merge("_session_id=#{private_session_id};path=/") + + get '/get_session_value' + assert_response :success + assert_equal 'foo: nil', response.body + end + end + end + end end
test/session_test.rb+58 −0 modified@@ -123,6 +123,64 @@ def test_loaded? s = Session.new assert !s.loaded?, 'session is not loaded' end + + def test_session_can_be_secured + Session.create_table! + session_id = 'unsecure' + session = session_klass.create!(:data => 'world', :session_id => 'foo') + session.update_column(:session_id, session_id) + + assert_equal 'unsecure', session.read_attribute(:session_id) + + session.secure! + + secured = Rack::Session::SessionId.new(session_id).private_id + assert_equal secured, session.reload.read_attribute(:session_id) + end + + def test_session_can_be_secured_with_sessid_compatibility + # Force class reload, as we need to redo the meta-programming + ActiveRecord::SessionStore.send(:remove_const, :Session) + load 'active_record/session_store/session.rb' + + Session.reset_column_information + klass = Class.new(Session) do + def self.session_id_column + 'sessid' + end + end + klass.create_table! + session_id = 'unsecure' + session = klass.create!(:data => 'world', :sessid => 'foo') + session.update_column(:sessid, session_id) + + assert_equal 'unsecure', session.read_attribute(:sessid) + + session.secure! + + secured = Rack::Session::SessionId.new(session_id).private_id + assert_equal secured, session.reload.read_attribute(:sessid) + ensure + klass.drop_table! + Session.reset_column_information + end + + def test_secure_is_idempotent + Session.create_table! + session_id = 'unsecure' + session = session_klass.create!(:data => 'world', :session_id => 'foo') + session.update_column(:session_id, session_id) + + assert_equal 'unsecure', session.read_attribute(:session_id) + + session.secure! + private_id = session.read_attribute(:session_id) + session.secure! + session.reload + session.secure! + + assert_equal private_id, session.reload.read_attribute(:session_id) + end end end end
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
7- github.com/advisories/GHSA-cvw2-xj8r-mjf7ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2019-25025ghsaADVISORY
- github.com/rails/activerecord-session_store/commit/9d4dd113d3010b82daaadf0b0ee6b9fb2afb2160ghsaWEB
- github.com/rails/activerecord-session_store/pull/151ghsax_refsource_MISCWEB
- github.com/rails/activerecord-session_store/releases/tag/v2.0.0ghsaWEB
- github.com/rubysec/ruby-advisory-db/blob/master/activerecord-session_store/CVE-2019-25025.ymlghsaWEB
- rubygems.org/gems/activerecord-session_storeghsaWEB
News mentions
0No linked articles in our index yet.