diff --git a/CVE-2023-30861-pre1.patch b/CVE-2023-30861-pre1.patch new file mode 100644 index 0000000000000000000000000000000000000000..c28b42d8704cae89cb6c91a3310d7c0bea5d3491 --- /dev/null +++ b/CVE-2023-30861-pre1.patch @@ -0,0 +1,157 @@ +From 0c0b31a789f8bfeadcbcf49d1fb38a00624b3065 Mon Sep 17 00:00:00 2001 +From: Doron Horwitz +Date: Tue, 24 Sep 2019 14:39:22 +0300 +Subject: [PATCH] get_cookie_name in SessionInterface for easier overriding in + SecureCookieSessionInterface + +--- + CHANGES.rst | 9 ++++++++ + src/flask/sessions.py | 16 +++++++++---- + tests/test_reqctx.py | 53 +++++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 73 insertions(+), 5 deletions(-) + +diff --git a/CHANGES.rst b/CHANGES.rst +index c0313eb73e..f1806fa4e3 100644 +--- a/CHANGES.rst ++++ b/CHANGES.rst +@@ -1,5 +1,14 @@ + .. currentmodule:: flask + ++Version 2.0.0 ++------------- ++ ++Unreleased ++ ++- Add :meth:`sessions.SessionInterface.get_cookie_name` to allow ++ setting the session cookie name dynamically. :pr:`3369` ++ ++ + Version 1.1.2 + ------------- + +diff --git a/src/flask/sessions.py b/src/flask/sessions.py +index c57ba29c4a..fe2a20eefa 100644 +--- a/src/flask/sessions.py ++++ b/src/flask/sessions.py +@@ -173,6 +173,13 @@ def is_null_session(self, obj): + """ + return isinstance(obj, self.null_session_class) + ++ def get_cookie_name(self, app): ++ """Returns the name of the session cookie. ++ ++ Uses ``app.session_cookie_name`` which is set to ``SESSION_COOKIE_NAME`` ++ """ ++ return app.session_cookie_name ++ + def get_cookie_domain(self, app): + """Returns the domain that should be set for the session cookie. + +@@ -340,7 +347,7 @@ def open_session(self, app, request): + s = self.get_signing_serializer(app) + if s is None: + return None +- val = request.cookies.get(app.session_cookie_name) ++ val = request.cookies.get(self.get_cookie_name(app)) + if not val: + return self.session_class() + max_age = total_seconds(app.permanent_session_lifetime) +@@ -351,6 +358,7 @@ def open_session(self, app, request): + return self.session_class() + + def save_session(self, app, session, response): ++ name = self.get_cookie_name(app) + domain = self.get_cookie_domain(app) + path = self.get_cookie_path(app) + +@@ -358,9 +366,7 @@ def save_session(self, app, session, response): + # If the session is empty, return without setting the cookie. + if not session: + if session.modified: +- response.delete_cookie( +- app.session_cookie_name, domain=domain, path=path +- ) ++ response.delete_cookie(name, domain=domain, path=path) + + return + +@@ -377,7 +383,7 @@ def save_session(self, app, session, response): + expires = self.get_expiration_time(app, session) + val = self.get_signing_serializer(app).dumps(dict(session)) + response.set_cookie( +- app.session_cookie_name, ++ name, + val, + expires=expires, + httponly=httponly, +diff --git a/tests/test_reqctx.py b/tests/test_reqctx.py +index 90eae9d8f1..8f00034b73 100644 +--- a/tests/test_reqctx.py ++++ b/tests/test_reqctx.py +@@ -11,6 +11,7 @@ + import pytest + + import flask ++from flask.sessions import SecureCookieSessionInterface + from flask.sessions import SessionInterface + + try: +@@ -229,6 +230,58 @@ def index(): + assert not flask.current_app + + ++def test_session_dynamic_cookie_name(): ++ ++ # This session interface will use a cookie with a different name if the ++ # requested url ends with the string "dynamic_cookie" ++ class PathAwareSessionInterface(SecureCookieSessionInterface): ++ def get_cookie_name(self, app): ++ if flask.request.url.endswith("dynamic_cookie"): ++ return "dynamic_cookie_name" ++ else: ++ return super(PathAwareSessionInterface, self).get_cookie_name(app) ++ ++ class CustomFlask(flask.Flask): ++ session_interface = PathAwareSessionInterface() ++ ++ app = CustomFlask(__name__) ++ app.secret_key = "secret_key" ++ ++ @app.route("/set", methods=["POST"]) ++ def set(): ++ flask.session["value"] = flask.request.form["value"] ++ return "value set" ++ ++ @app.route("/get") ++ def get(): ++ v = flask.session.get("value", "None") ++ return v ++ ++ @app.route("/set_dynamic_cookie", methods=["POST"]) ++ def set_dynamic_cookie(): ++ flask.session["value"] = flask.request.form["value"] ++ return "value set" ++ ++ @app.route("/get_dynamic_cookie") ++ def get_dynamic_cookie(): ++ v = flask.session.get("value", "None") ++ return v ++ ++ test_client = app.test_client() ++ ++ # first set the cookie in both /set urls but each with a different value ++ assert test_client.post("/set", data={"value": "42"}).data == b"value set" ++ assert ( ++ test_client.post("/set_dynamic_cookie", data={"value": "616"}).data ++ == b"value set" ++ ) ++ ++ # now check that the relevant values come back - meaning that different ++ # cookies are being used for the urls that end with "dynamic cookie" ++ assert test_client.get("/get").data == b"42" ++ assert test_client.get("/get_dynamic_cookie").data == b"616" ++ ++ + def test_bad_environ_raises_bad_request(): + app = flask.Flask(__name__) + diff --git a/CVE-2023-30861-pre2.patch b/CVE-2023-30861-pre2.patch new file mode 100644 index 0000000000000000000000000000000000000000..1ac8c21cc8681f36833ec4f19ff5a9fabac3009d --- /dev/null +++ b/CVE-2023-30861-pre2.patch @@ -0,0 +1,71 @@ +From 0210be68368a2f0a207e85f0fe84eddb091a983a Mon Sep 17 00:00:00 2001 +From: starlet-dx <15929766099@163.com> +Date: Thu, 11 May 2023 14:57:33 +0800 +Subject: [PATCH 1/1] include samesite and secure when removing session cookie + (#3726) + +Origin: +https://github.com/pallets/flask/commit/22987b68173e9bf56cc450cb7aeb93f5480a2660 +--- + src/flask/sessions.py | 8 +++++--- + tests/test_basic.py | 13 +++++++++++++ + 2 files changed, 18 insertions(+), 3 deletions(-) + +diff --git a/src/flask/sessions.py b/src/flask/sessions.py +index fe2a20e..57bd727 100644 +--- a/src/flask/sessions.py ++++ b/src/flask/sessions.py +@@ -361,12 +361,16 @@ class SecureCookieSessionInterface(SessionInterface): + name = self.get_cookie_name(app) + domain = self.get_cookie_domain(app) + path = self.get_cookie_path(app) ++ secure = self.get_cookie_secure(app) ++ samesite = self.get_cookie_samesite(app) + + # If the session is modified to be empty, remove the cookie. + # If the session is empty, return without setting the cookie. + if not session: + if session.modified: +- response.delete_cookie(name, domain=domain, path=path) ++ response.delete_cookie( ++ name, domain=domain, path=path, secure=secure, samesite=samesite ++ ) + + return + +@@ -378,8 +382,6 @@ class SecureCookieSessionInterface(SessionInterface): + return + + httponly = self.get_cookie_httponly(app) +- secure = self.get_cookie_secure(app) +- samesite = self.get_cookie_samesite(app) + expires = self.get_expiration_time(app, session) + val = self.get_signing_serializer(app).dumps(dict(session)) + response.set_cookie( +diff --git a/tests/test_basic.py b/tests/test_basic.py +index 4d3b7b0..c4af35c 100644 +--- a/tests/test_basic.py ++++ b/tests/test_basic.py +@@ -333,6 +333,19 @@ def test_session_using_session_settings(app, client): + assert "httponly" not in cookie + assert "samesite" in cookie + ++ @app.route("/clear") ++ def clear(): ++ flask.session.pop("testing", None) ++ return "Goodbye World" ++ ++ rv = client.get("/clear", "http://www.example.com:8080/test/") ++ cookie = rv.headers["set-cookie"].lower() ++ assert "session=;" in cookie ++ assert "domain=.example.com" in cookie ++ assert "path=/" in cookie ++ assert "secure" in cookie ++ assert "samesite" in cookie ++ + + def test_session_using_samesite_attribute(app, client): + @app.route("/") +-- +2.30.0 + diff --git a/CVE-2023-30861-pre3.patch b/CVE-2023-30861-pre3.patch new file mode 100644 index 0000000000000000000000000000000000000000..5596c401cac56a9b26ae1ae33c47b55d74eb47cb --- /dev/null +++ b/CVE-2023-30861-pre3.patch @@ -0,0 +1,49 @@ +From 07e9ba766fabc3d8936f49e691c045dd67f8bf1f Mon Sep 17 00:00:00 2001 +From: starlet-dx <15929766099@163.com> +Date: Thu, 11 May 2023 15:01:23 +0800 +Subject: [PATCH 1/1] Preserve HttpOnly flag when deleting session cookie + +fixes #4485 + +Origin: +https://github.com/pallets/flask/commit/b707bf443afe856b77e09b7a41592f10d46e02df +--- + src/flask/sessions.py | 9 +++++++-- + 1 file changed, 7 insertions(+), 2 deletions(-) + +diff --git a/src/flask/sessions.py b/src/flask/sessions.py +index 57bd727..f88f75b 100644 +--- a/src/flask/sessions.py ++++ b/src/flask/sessions.py +@@ -363,13 +363,19 @@ class SecureCookieSessionInterface(SessionInterface): + path = self.get_cookie_path(app) + secure = self.get_cookie_secure(app) + samesite = self.get_cookie_samesite(app) ++ httponly = self.get_cookie_httponly(app) + + # If the session is modified to be empty, remove the cookie. + # If the session is empty, return without setting the cookie. + if not session: + if session.modified: + response.delete_cookie( +- name, domain=domain, path=path, secure=secure, samesite=samesite ++ name, ++ domain=domain, ++ path=path, ++ secure=secure, ++ samesite=samesite, ++ httponly=httponly, + ) + + return +@@ -381,7 +387,6 @@ class SecureCookieSessionInterface(SessionInterface): + if not self.should_set_cookie(app, session): + return + +- httponly = self.get_cookie_httponly(app) + expires = self.get_expiration_time(app, session) + val = self.get_signing_serializer(app).dumps(dict(session)) + response.set_cookie( +-- +2.30.0 + diff --git a/CVE-2023-30861.patch b/CVE-2023-30861.patch new file mode 100644 index 0000000000000000000000000000000000000000..b7de8cedfc83d4a04012106b57e7a1fc20f5fd4c --- /dev/null +++ b/CVE-2023-30861.patch @@ -0,0 +1,96 @@ +From f640851087a8b3020914b70a53a3123d29ee8188 Mon Sep 17 00:00:00 2001 +From: starlet-dx <15929766099@163.com> +Date: Thu, 11 May 2023 15:04:55 +0800 +Subject: [PATCH 1/1] set `Vary: Cookie` header consistently for session + +Origin: +https://github.com/pallets/flask/commit/8646edca6f47e2cd57464081b3911218d4734f8d +--- + src/flask/sessions.py | 10 ++++++---- + tests/test_basic.py | 23 +++++++++++++++++++++++ + 2 files changed, 29 insertions(+), 4 deletions(-) + +diff --git a/src/flask/sessions.py b/src/flask/sessions.py +index f88f75b..fa3921b 100644 +--- a/src/flask/sessions.py ++++ b/src/flask/sessions.py +@@ -365,6 +365,10 @@ class SecureCookieSessionInterface(SessionInterface): + samesite = self.get_cookie_samesite(app) + httponly = self.get_cookie_httponly(app) + ++ # Add a "Vary: Cookie" header if the session was accessed at all. ++ if session.accessed: ++ response.vary.add("Cookie") ++ + # If the session is modified to be empty, remove the cookie. + # If the session is empty, return without setting the cookie. + if not session: +@@ -377,13 +381,10 @@ class SecureCookieSessionInterface(SessionInterface): + samesite=samesite, + httponly=httponly, + ) ++ response.vary.add("Cookie") + + return + +- # Add a "Vary: Cookie" header if the session was accessed at all. +- if session.accessed: +- response.vary.add("Cookie") +- + if not self.should_set_cookie(app, session): + return + +@@ -399,3 +400,4 @@ class SecureCookieSessionInterface(SessionInterface): + secure=secure, + samesite=samesite, + ) ++ response.vary.add("Cookie") +diff --git a/tests/test_basic.py b/tests/test_basic.py +index c4af35c..76d49e3 100644 +--- a/tests/test_basic.py ++++ b/tests/test_basic.py +@@ -551,6 +551,11 @@ def test_session_vary_cookie(app, client): + 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() +@@ -583,11 +588,29 @@ def test_session_vary_cookie(app, client): + 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, client): ++ @app.get("/login") ++ def login(): ++ flask.session["user_id"] = 1 ++ flask.session.permanent = True ++ return "" ++ ++ @app.get("/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, req_ctx): + assert not flask.session.modified + flask.flash("Zap") +-- +2.30.0 + diff --git a/python-flask.spec b/python-flask.spec index bcf82aed709c23e024bb45077070301e36d7d45d..a01873c4b8420750b547001e0707d30706eebded 100644 --- a/python-flask.spec +++ b/python-flask.spec @@ -1,11 +1,15 @@ Name: python-flask Version: 1.1.2 -Release: 3 +Release: 4 Epoch: 1 Summary: A lightweight WSGI web application framework License: BSD URL: https://palletsprojects.com/p/flask/ Source0: https://files.pythonhosted.org/packages/source/F/Flask/Flask-%{version}.tar.gz +Patch0: CVE-2023-30861-pre1.patch +Patch1: CVE-2023-30861-pre2.patch +Patch2: CVE-2023-30861-pre3.patch +Patch3: CVE-2023-30861.patch BuildArch: noarch BuildRequires: python3-devel python3-setuptools python3-pytest python3-jinja2 python3-werkzeug python3-itsdangerous python3-click @@ -26,7 +30,7 @@ Requires: python3-jinja2 python3-werkzeug python3-itsdangerous python3-cl Python-flask for python 3 version %prep -%autosetup -n Flask-%{version} +%autosetup -n Flask-%{version} -p1 %build %py3_build @@ -51,6 +55,9 @@ PYTHONPATH=%{buildroot}%{python3_sitelib} py.test-%{python3_version} -v || : %{python3_sitelib}/* %changelog +* Thu May 11 2023 yaoxin - 1:1.1.2-4 +- Fix CVE-2023-30861 + * Wed Oct 27 2021 Haiwei Li - 1.1.2-3 - backport add require pythonx-simplejson. details see issue #I4CGIS