diff --git a/CHANGES.rst b/CHANGES.rst index 232b144a5c..9debc7ddf0 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -28,6 +28,8 @@ Unreleased it's disabled in config. Previously, only disabling worked. :issue:`5916` - ``Flask.select_jinja_autoescape`` uses case-insensitive comparison instead of only lower case file extensions. :pr:`6012` +- Reject scalar string or bytes values for ``SECRET_KEY_FALLBACKS`` instead + of treating each character as a fallback signing key. Version 3.1.3 diff --git a/src/flask/sessions.py b/src/flask/sessions.py index ad357706ff..f8c942d96c 100644 --- a/src/flask/sessions.py +++ b/src/flask/sessions.py @@ -307,6 +307,12 @@ def get_signing_serializer(self, app: Flask) -> URLSafeTimedSerializer | None: keys: list[str | bytes] = [] if fallbacks := app.config["SECRET_KEY_FALLBACKS"]: + if isinstance(fallbacks, (str, bytes)): + raise TypeError( + "SECRET_KEY_FALLBACKS must be a list of strings or bytes," + " not a single string or bytes value." + ) + keys.extend(fallbacks) keys.append(app.secret_key) # itsdangerous expects current key at top diff --git a/tests/test_basic.py b/tests/test_basic.py index 1d9d83f8cb..6f43880d82 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -11,6 +11,7 @@ import pytest import werkzeug.serving +from itsdangerous import URLSafeTimedSerializer from markupsafe import Markup from werkzeug.exceptions import BadRequest from werkzeug.exceptions import Forbidden @@ -418,6 +419,33 @@ def get_session() -> dict[str, t.Any]: assert client.get().json == {"a": 1} +@pytest.mark.parametrize("fallbacks", ["old-secret", b"old-secret"]) +def test_session_secret_key_fallbacks_rejects_string_or_bytes( + fallbacks, app, client +) -> None: + app.secret_key = "new-secret" + app.config["SECRET_KEY_FALLBACKS"] = fallbacks + + @app.get("/") + def get_session() -> dict[str, t.Any]: + return dict(flask.session) + + signer = URLSafeTimedSerializer( + "o", + salt=app.session_interface.salt, + serializer=app.session_interface.serializer, + signer_kwargs={ + "key_derivation": app.session_interface.key_derivation, + "digest_method": app.session_interface.digest_method, + }, + ) + + client.set_cookie(app.config["SESSION_COOKIE_NAME"], signer.dumps({"a": 1})) + + with pytest.raises(TypeError, match="SECRET_KEY_FALLBACKS"): + client.get() + + def test_session_expiration(app, client): permanent = True