108 lines
3.5 KiB
Diff
108 lines
3.5 KiB
Diff
From 640ff67e9d59d7acd786683bbab422d8aa17211c Mon Sep 17 00:00:00 2001
|
|
From: "Brian C. Lane" <bcl@redhat.com>
|
|
Date: Tue, 9 May 2023 15:36:11 -0700
|
|
Subject: [PATCH 5/5] Backport support for the accessed attribute
|
|
|
|
This is added to the SessionMixin and SecureCookieSession to support
|
|
fixing CVE-2023-30861.
|
|
|
|
Related: rhbz#2196683
|
|
---
|
|
flask/sessions.py | 40 +++++++++++++++++++++++++++++++++++++++-
|
|
tests/test_basic.py | 8 ++++++++
|
|
2 files changed, 47 insertions(+), 1 deletion(-)
|
|
|
|
diff --git a/flask/sessions.py b/flask/sessions.py
|
|
index 2f60e7eb..278c3583 100644
|
|
--- a/flask/sessions.py
|
|
+++ b/flask/sessions.py
|
|
@@ -48,6 +48,11 @@ class SessionMixin(object):
|
|
#: The default mixin implementation just hardcodes ``True`` in.
|
|
modified = True
|
|
|
|
+ #: Some implementations can detect when session data is read or
|
|
+ #: written and set this when that happens. The mixin default is hard
|
|
+ #: coded to ``True``.
|
|
+ accessed = True
|
|
+
|
|
|
|
def _tag(value):
|
|
if isinstance(value, tuple):
|
|
@@ -111,14 +116,47 @@ session_json_serializer = TaggedJSONSerializer()
|
|
|
|
|
|
class SecureCookieSession(CallbackDict, SessionMixin):
|
|
- """Base class for sessions based on signed cookies."""
|
|
+ """Base class for sessions based on signed cookies.
|
|
+
|
|
+ This session backend will set the :attr:`modified` and
|
|
+ :attr:`accessed` attributes. It cannot reliably track whether a
|
|
+ session is new (vs. empty), so :attr:`new` remains hard coded to
|
|
+ ``False``.
|
|
+ """
|
|
+
|
|
+ #: When data is changed, this is set to ``True``. Only the session
|
|
+ #: dictionary itself is tracked; if the session contains mutable
|
|
+ #: data (for example a nested dict) then this must be set to
|
|
+ #: ``True`` manually when modifying that data. The session cookie
|
|
+ #: will only be written to the response if this is ``True``.
|
|
+ modified = False
|
|
+
|
|
+ #: When data is read or written, this is set to ``True``. Used by
|
|
+ # :class:`.SecureCookieSessionInterface` to add a ``Vary: Cookie``
|
|
+ #: header, which allows caching proxies to cache different pages for
|
|
+ #: different users.
|
|
+ accessed = False
|
|
+
|
|
|
|
def __init__(self, initial=None):
|
|
def on_update(self):
|
|
self.modified = True
|
|
+ self.accessed = True
|
|
CallbackDict.__init__(self, initial, on_update)
|
|
self.modified = False
|
|
|
|
+ def __getitem__(self, key):
|
|
+ self.accessed = True
|
|
+ return super().__getitem__(key)
|
|
+
|
|
+ def get(self, key, default=None):
|
|
+ self.accessed = True
|
|
+ return super().get(key, default)
|
|
+
|
|
+ def setdefault(self, key, default=None):
|
|
+ self.accessed = True
|
|
+ return super().setdefault(key, default)
|
|
+
|
|
|
|
class NullSession(SecureCookieSession):
|
|
"""Class used to generate nicer error messages if sessions are not
|
|
diff --git a/tests/test_basic.py b/tests/test_basic.py
|
|
index 85555300..5ad8c3de 100644
|
|
--- a/tests/test_basic.py
|
|
+++ b/tests/test_basic.py
|
|
@@ -192,12 +192,20 @@ def test_session():
|
|
|
|
@app.route('/set', methods=['POST'])
|
|
def set():
|
|
+ assert not flask.session.accessed
|
|
+ assert not flask.session.modified
|
|
flask.session['value'] = flask.request.form['value']
|
|
+ assert flask.session.accessed
|
|
+ assert flask.session.modified
|
|
return 'value set'
|
|
|
|
@app.route('/get')
|
|
def get():
|
|
+ assert not flask.session.accessed
|
|
+ assert not flask.session.modified
|
|
v = flask.session.get("value", "None")
|
|
+ assert flask.session.accessed
|
|
+ assert not flask.session.modified
|
|
return v
|
|
|
|
c = app.test_client()
|
|
--
|
|
2.40.1
|
|
|