Sign sessions using SECRET_KEY to simplify revocation (#1219)

* Sign sessions using `SECRET_KEY`
* Add `CTFd.utils.security.signing.sign` and `CTFd.utils.security.signing.unsign`
bulk-clear-sessions
Kevin Chung 2020-01-20 21:53:08 -05:00 committed by GitHub
parent 83efc4d5eb
commit 60c46af58a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 32 additions and 2 deletions

View File

@ -1,4 +1,5 @@
from flask import current_app from flask import current_app
from itsdangerous import Signer
from itsdangerous.url_safe import URLSafeTimedSerializer from itsdangerous.url_safe import URLSafeTimedSerializer
from itsdangerous.exc import ( # noqa: F401 from itsdangerous.exc import ( # noqa: F401
BadTimeSignature, BadTimeSignature,
@ -19,3 +20,17 @@ def unserialize(data, secret=None, max_age=432000):
secret = current_app.config["SECRET_KEY"] secret = current_app.config["SECRET_KEY"]
s = URLSafeTimedSerializer(secret) s = URLSafeTimedSerializer(secret)
return s.loads(data, max_age=max_age) return s.loads(data, max_age=max_age)
def sign(data, secret=None):
if secret is None:
secret = current_app.config["SECRET_KEY"]
s = Signer(secret)
return s.sign(data)
def unsign(data, secret=None):
if secret is None:
secret = current_app.config["SECRET_KEY"]
s = Signer(secret)
return s.unsign(data)

View File

@ -1,8 +1,10 @@
from flask.sessions import SessionInterface, SessionMixin from flask.sessions import SessionInterface, SessionMixin
from flask.json.tag import TaggedJSONSerializer from flask.json.tag import TaggedJSONSerializer
from werkzeug.datastructures import CallbackDict from werkzeug.datastructures import CallbackDict
from itsdangerous import BadSignature, want_bytes
from CTFd.cache import cache from CTFd.cache import cache
from CTFd.utils import text_type from CTFd.utils import text_type
from CTFd.utils.security.signing import sign, unsign
from uuid import uuid4 from uuid import uuid4
import six import six
@ -50,7 +52,7 @@ class CachingSessionInterface(SessionInterface):
def _generate_sid(self): def _generate_sid(self):
return str(uuid4()) return str(uuid4())
def __init__(self, key_prefix, use_signer=False, permanent=False): def __init__(self, key_prefix, use_signer=True, permanent=False):
self.key_prefix = key_prefix self.key_prefix = key_prefix
self.use_signer = use_signer self.use_signer = use_signer
self.permanent = permanent self.permanent = permanent
@ -61,6 +63,14 @@ class CachingSessionInterface(SessionInterface):
sid = self._generate_sid() sid = self._generate_sid()
return self.session_class(sid=sid, permanent=self.permanent) return self.session_class(sid=sid, permanent=self.permanent)
if self.use_signer:
try:
sid_as_bytes = unsign(sid)
sid = sid_as_bytes.decode()
except BadSignature:
sid = self._generate_sid()
return self.session_class(sid=sid, permanent=self.permanent)
if not six.PY2 and not isinstance(sid, text_type): if not six.PY2 and not isinstance(sid, text_type):
sid = sid.decode("utf-8", "strict") sid = sid.decode("utf-8", "strict")
val = cache.get(self.key_prefix + sid) val = cache.get(self.key_prefix + sid)
@ -99,7 +109,12 @@ class CachingSessionInterface(SessionInterface):
value=val, value=val,
timeout=total_seconds(app.permanent_session_lifetime), timeout=total_seconds(app.permanent_session_lifetime),
) )
session_id = session.sid
if self.use_signer:
session_id = sign(want_bytes(session.sid))
else:
session_id = session.sid
response.set_cookie( response.set_cookie(
app.session_cookie_name, app.session_cookie_name,
session_id, session_id,