Set SameSite=Lax and use the __Host- prefix for the session cookie
The own SessionInterface ignored SESSION_COOKIE_SAMESITE, so the cookie was sent with every cross-site request in Firefox and Safari (CSRF, clickjacking in iframes, keeping the idle timeout alive). The cookie name "session" could also be set by other subdomains or via plain HTTP, the precondition for session fixation. - SameSite=Lax. No GET changes state, so links from mails keep working. - In production the cookie is named __Host-session, which browsers only accept with Secure, Path=/ and without Domain. The debug server keeps "session", since it runs without Secure. - set_cookie and delete_cookie use the same attributes, since browsers ignore the deletion of a __Host- cookie without Secure. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8e32727fcd
commit
9aed6b5227
2 files changed
+16
-13
No files matched your search
+2
-1
@@ -47,8 +47,9 @@ def main():
|
||||
SECRET_KEY=(
|
||||
"123456" if app.debug else base64.b64encode(os.urandom(64)).decode("utf-8")
|
||||
),
|
||||
SESSION_COOKIE_NAME="session" if app.debug else "__Host-session",
|
||||
SESSION_COOKIE_SECURE=not app.debug,
|
||||
SESSION_COOKIE_SAMESITE=False,
|
||||
SESSION_COOKIE_SAMESITE="Lax",
|
||||
)
|
||||
add_middleware_stack(
|
||||
app,
|
||||
|
||||
+14
-12
@@ -139,19 +139,27 @@ class SessionInterface(FlaskSessionInterface):
|
||||
return self.make_session_object()
|
||||
return session
|
||||
|
||||
def get_cookie_attributes(self, app):
|
||||
# Attributes for setting and deleting the cookie must match, because
|
||||
# browsers ignore a deletion of a __Host- cookie without Secure and Path.
|
||||
return dict(
|
||||
domain=self.get_cookie_domain(app),
|
||||
path=self.get_cookie_path(app),
|
||||
secure=self.get_cookie_secure(app),
|
||||
httponly=self.get_cookie_httponly(app),
|
||||
samesite=self.get_cookie_samesite(app),
|
||||
)
|
||||
|
||||
def save_session(self, app, session, response):
|
||||
domain = self.get_cookie_domain(app)
|
||||
path = self.get_cookie_path(app)
|
||||
cookie_name = self.get_cookie_name(app)
|
||||
cookie_attributes = self.get_cookie_attributes(app)
|
||||
if session.is_empty():
|
||||
if session.modified:
|
||||
del session_storage[session.id]
|
||||
# Only send the deletion header if the client sent a cookie
|
||||
if cookie_name in request.cookies:
|
||||
response.delete_cookie(cookie_name, domain=domain, path=path)
|
||||
response.delete_cookie(cookie_name, **cookie_attributes)
|
||||
else:
|
||||
httponly = self.get_cookie_httponly(app)
|
||||
secure = self.get_cookie_secure(app)
|
||||
expires = self.get_expiration_time(app, session)
|
||||
session.last_seen = time.monotonic()
|
||||
if session.created_at is None:
|
||||
@@ -170,11 +178,5 @@ class SessionInterface(FlaskSessionInterface):
|
||||
self._get_signer(app).sign(want_bytes(session.id)).decode("utf-8")
|
||||
)
|
||||
response.set_cookie(
|
||||
cookie_name,
|
||||
session_id,
|
||||
expires=expires,
|
||||
httponly=httponly,
|
||||
domain=domain,
|
||||
path=path,
|
||||
secure=secure,
|
||||
cookie_name, session_id, expires=expires, **cookie_attributes
|
||||
)
|
||||
Reference in new issue
Block a user