python-flask/0004-Backport-Vary-Cookie-fix-from-upstream.patch
Brian C. Lane d170540219 - Copy missing tests over from distgit
- Backport fix for CVE-2023-30861
  Resolves: rhbz#2196683
2023-05-10 13:35:57 -07:00

183 lines
5.8 KiB
Diff

From 4195dc236d4286d6d82b3a40dc595a1437afcdc9 Mon Sep 17 00:00:00 2001
From: "Brian C. Lane" <bcl@redhat.com>
Date: Tue, 9 May 2023 15:13:34 -0700
Subject: [PATCH 4/5] Backport Vary: Cookie fix from upstream
This fixes CVE-2023-30861 by backporting the patch and tests from this
upstream commit:
https://github.com/pallets/flask/commit/70f906c51ce49c485f1d355703e9cc3386b1cc2b
Resolves: rhbz#2196683
---
flask/sessions.py | 6 +++
tests/test_basic.py | 90 ++++++++++++++++++++++++++++++++++++++++++++-
tests/test_ext.py | 2 +-
3 files changed, 95 insertions(+), 3 deletions(-)
diff --git a/flask/sessions.py b/flask/sessions.py
index 525ff246..2f60e7eb 100644
--- a/flask/sessions.py
+++ b/flask/sessions.py
@@ -338,6 +338,10 @@ class SecureCookieSessionInterface(SessionInterface):
domain = self.get_cookie_domain(app)
path = self.get_cookie_path(app)
+ # Add a "Vary: Cookie" header if the session was accessed at all.
+ if session.accessed:
+ response.vary.add("Cookie")
+
# Delete case. If there is no session we bail early.
# If the session was modified to be empty we remove the
# whole cookie.
@@ -345,6 +349,7 @@ class SecureCookieSessionInterface(SessionInterface):
if session.modified:
response.delete_cookie(app.session_cookie_name,
domain=domain, path=path)
+ response.vary.add("Cookie")
return
# Modification case. There are upsides and downsides to
@@ -364,3 +369,4 @@ class SecureCookieSessionInterface(SessionInterface):
response.set_cookie(app.session_cookie_name, val,
expires=expires, httponly=httponly,
domain=domain, path=path, secure=secure)
+ response.vary.add("Cookie")
diff --git a/tests/test_basic.py b/tests/test_basic.py
index c5ec9f5c..85555300 100644
--- a/tests/test_basic.py
+++ b/tests/test_basic.py
@@ -11,6 +11,7 @@
import pytest
+import os
import re
import uuid
import time
@@ -196,7 +197,8 @@ def test_session():
@app.route('/get')
def get():
- return flask.session['value']
+ v = flask.session.get("value", "None")
+ return v
c = app.test_client()
assert c.post('/set', data={'value': '42'}).data == b'value set'
@@ -333,7 +335,7 @@ def test_session_expiration():
client = app.test_client()
rv = client.get('/')
assert 'set-cookie' in rv.headers
- match = re.search(r'\bexpires=([^;]+)(?i)', rv.headers['set-cookie'])
+ match = re.search(r'(?i)\bexpires=([^;]+)', rv.headers['set-cookie'])
expires = parse_date(match.group())
expected = datetime.utcnow() + app.permanent_session_lifetime
assert expires.year == expected.year
@@ -445,6 +447,90 @@ def test_session_cookie_setting():
run_test(expect_header=False)
+def test_session_vary_cookie():
+ app = flask.Flask("flask_test", root_path=os.path.dirname(__file__))
+ app.config.update(
+ TESTING=True,
+ SECRET_KEY="test key",
+ )
+ client = app.test_client()
+
+ @app.route("/set")
+ def set_session():
+ flask.session["test"] = "test"
+ return ""
+ @app.route("/get")
+ def get():
+ return flask.session.get("test")
+ @app.route("/getitem")
+ def getitem():
+ return flask.session["test"]
+ @app.route("/setdefault")
+ def setdefault():
+ return flask.session.setdefault("test", "default")
+
+ @app.route("/clear")
+ def clear():
+ flask.session.clear()
+ return ""
+
+ @app.route("/vary-cookie-header-set")
+ def vary_cookie_header_set():
+ response = flask.Response()
+ response.vary.add("Cookie")
+ flask.session["test"] = "test"
+ return response
+ @app.route("/vary-header-set")
+ def vary_header_set():
+ response = flask.Response()
+ response.vary.update(("Accept-Encoding", "Accept-Language"))
+ flask.session["test"] = "test"
+ return response
+ @app.route("/no-vary-header")
+ def no_vary_header():
+ return ""
+ def expect(path, header_value="Cookie"):
+ rv = client.get(path)
+ if header_value:
+ # The 'Vary' key should exist in the headers only once.
+ assert len(rv.headers.get_all("Vary")) == 1
+ assert rv.headers["Vary"] == header_value
+ else:
+ assert "Vary" not in rv.headers
+ expect("/set")
+ expect("/get")
+ expect("/getitem")
+ expect("/setdefault")
+ expect("/clear")
+ expect("/vary-cookie-header-set")
+ expect("/vary-header-set", "Accept-Encoding, Accept-Language, Cookie")
+ expect("/no-vary-header", None)
+
+
+def test_session_refresh_vary():
+ app = flask.Flask("flask_test", root_path=os.path.dirname(__file__))
+ app.config.update(
+ TESTING=True,
+ SECRET_KEY="test key",
+ )
+ client = app.test_client()
+
+ @app.route("/login")
+ def login():
+ flask.session["user_id"] = 1
+ flask.session.permanent = True
+ return ""
+
+ @app.route("/ignored")
+ def ignored():
+ return ""
+
+ rv = client.get("/login")
+ assert rv.headers["Vary"] == "Cookie"
+ rv = client.get("/ignored")
+ assert rv.headers["Vary"] == "Cookie"
+
+
def test_flashes():
app = flask.Flask(__name__)
app.secret_key = 'testkey'
diff --git a/tests/test_ext.py b/tests/test_ext.py
index d336e404..c3e23cdf 100644
--- a/tests/test_ext.py
+++ b/tests/test_ext.py
@@ -180,7 +180,7 @@ def test_no_error_swallowing(flaskext_broken):
with pytest.raises(ImportError) as excinfo:
import flask.ext.broken
- assert excinfo.type is ImportError
+ assert excinfo.type in (ImportError, ModuleNotFoundError)
if PY2:
message = 'No module named missing_module'
else:
--
2.40.1