From 80df635ffd466fa7798f6031be5469b4d5dae069 Mon Sep 17 00:00:00 2001 From: Lex Date: Tue, 27 Feb 2024 16:09:34 +1000 Subject: [PATCH] Add regenerate method Closes #27 #39 #65 --- docs/config.rst | 2 - docs/index.rst | 1 + docs/{config_security.rst => security.rst} | 26 +++++++-- examples/hello.py | 11 ---- examples/kitchen-sink.py | 63 ++++++++++++++++++++++ src/flask_session/base.py | 13 +++++ 6 files changed, 99 insertions(+), 17 deletions(-) rename docs/{config_security.rst => security.rst} (56%) create mode 100644 examples/kitchen-sink.py diff --git a/docs/config.rst b/docs/config.rst index a66f5f63..b712963f 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -7,8 +7,6 @@ Configuration .. include:: config_cleanup.rst -.. include:: config_security.rst - .. include:: config_exceptions.rst .. include:: config_flask.rst diff --git a/docs/index.rst b/docs/index.rst index 087cd40d..462f0ae9 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -8,6 +8,7 @@ Table of Contents installation usage config + security api contributing license diff --git a/docs/config_security.rst b/docs/security.rst similarity index 56% rename from docs/config_security.rst rename to docs/security.rst index 3543a552..500d0718 100644 --- a/docs/config_security.rst +++ b/docs/security.rst @@ -1,11 +1,13 @@ - Security ----------------------- +========== .. warning:: Flask is a micro-framework and does not provide all security features out of the box. It is important to configure security settings for your application. +Flask configuration +------------------ + Please refer to documentation for `Flask`_, `OWASP`_, and other resources such as `MDN`_ for the latest information on best practice. Consider the following Flask configurations in production: @@ -25,11 +27,27 @@ Consider the following Flask configurations in production: You can use a security plugin such as `Flask-Talisman`_ to set these and more. -.. warning:: +Storage +------------------ - Take care to secure your storage and storage client connection. For example, setup SSL/TLS and storage authentication. +Take care to secure your storage and storage client connection. For example, setup SSL/TLS and storage authentication. +Session fixation +------------------ + +Session fixation is an attack that permits an attacker to hijack a valid user session. The attacker can fixate a user's session by providing them with a session identifier. The attacker can then use the session identifier to impersonate the user. + +As one tool among others that can mitigate session fixation, is regenerating the session identifier when a user logs in. This can be done by calling the ``session.regenerate()`` method. + +.. code-block:: python + + @app.route('/login') + def login(): + # your login logic ... + app.session_interface.regenerate(session) + # your response ... + .. _Flask: https://flask.palletsprojects.com/en/2.3.x/security/#set-cookie-options .. _MDN: https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies .. _OWASP: https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html diff --git a/examples/hello.py b/examples/hello.py index 55f54f4d..c5f17632 100644 --- a/examples/hello.py +++ b/examples/hello.py @@ -30,16 +30,5 @@ def delete(): return "deleted" -@app.route("/error/") -def error(): - raise RedisError("An error occurred with Redis") - - -@app.errorhandler(RedisError) -def handle_redis_error(error): - app.logger.error(f"Redis error encountered: {error}") - return "A problem occurred with our Redis service. Please try again later.", 500 - - if __name__ == "__main__": app.run(debug=True) diff --git a/examples/kitchen-sink.py b/examples/kitchen-sink.py new file mode 100644 index 00000000..539957e4 --- /dev/null +++ b/examples/kitchen-sink.py @@ -0,0 +1,63 @@ +from flask import Flask, session +from flask_session import Session +from redis.exceptions import RedisError + +app = Flask(__name__) +app.config.from_object(__name__) +app.config.update( + { + "SESSION_TYPE": "redis", + "SECRET_KEY": "sdfads", + "SESSION_SERIALIZATION_FORMAT": "json", + } +) + +Session(app) + + +@app.route("/") +def index(): + return "No cookies in this response if it is your first visit." + + +@app.route("/add-apple/") +def set(): + session["apple_count"] = session.get("apple_count", 0) + 1 + return "ok" + + +@app.route("/get-apples/") +def get(): + result = str(session.get("apple_count", "no apples")) + return result + + +@app.route("/login/") +def login(): + # Mitigate session fixation attacks + # If the session is not empty (/add-apple/ was previously visited), the session will be regenerated + app.session_interface.regenerate(session) + # Here you would authenticate the user first + session["logged_in"] = True + return "logged in" + + +@app.route("/logout/") +def delete(): + session.clear() + return "deleted" + + +@app.route("/error/") +def error(): + raise RedisError("An error occurred with Redis") + + +@app.errorhandler(RedisError) +def handle_redis_error(error): + app.logger.error(f"Redis error encountered: {error}") + return "A problem occurred with our Redis service. Please try again later.", 500 + + +if __name__ == "__main__": + app.run(debug=True) diff --git a/src/flask_session/base.py b/src/flask_session/base.py index a6c13fba..e9bf74e9 100644 --- a/src/flask_session/base.py +++ b/src/flask_session/base.py @@ -216,6 +216,19 @@ def _cleanup_n_requests(self) -> None: if self.cleanup_n_requests and random.randint(0, self.cleanup_n_requests) == 0: self._delete_expired_sessions() + # SECURITY API METHODS + + def regenerate(self, session: ServerSideSession) -> None: + """Regenerate the session id for the given session.""" + if session: + # Remove the old session from storage + self._delete_session(self._get_store_id(session.sid)) + # Generate a new session ID + new_sid = self._generate_sid(self.sid_length) + session.sid = new_sid + # Mark the session as modified to ensure it gets saved + session.modified = True + # METHODS OVERRIDE FLASK SESSION INTERFACE def save_session(