CVE-2022-24751: Clear sessions outside of the transaction.

Clearing the sessions inside the transaction makes Zulip vulnerable to
a narrow window where the deleted session has not yet been committed,
but has been removed from the memcached cache.  During this window, a
request with the session-id which has just been deleted can
successfully re-fill the memcached cache, as the in-database delete is
not yet committed, and thus not yet visible.  After the delete
transaction commits, the cache will be left with a cached session,
which allows further site access until it expires (after
SESSION_COOKIE_AGE seconds), is ejected from the cache due to memory
pressure, or the server is upgraded.

Move the session deletion outside of the transaction.

Because the testsuite runs inside of a transaction, it is impossible
to test this is CI; the testsuite uses the non-caching
`django.contrib.sessions.backends.db` backend, regardless.  The test
added in this commit thus does not fail before this commit; it is
merely a base expression that the session should be deleted somehow,
and does not exercise the assert added in the previous commit.
This commit is contained in:
Alex Vandiver
2022-03-09 02:03:42 +00:00
parent c28d1169c3
commit e6eace307e
2 changed files with 20 additions and 1 deletions

View File

@@ -1170,7 +1170,6 @@ def do_deactivate_user(
change_user_is_active(user_profile, False)
delete_user_sessions(user_profile)
clear_scheduled_emails(user_profile.id)
event_time = timezone_now()
@@ -1196,6 +1195,7 @@ def do_deactivate_user(
if settings.BILLING_ENABLED:
update_license_ledger_if_needed(user_profile.realm, event_time)
delete_user_sessions(user_profile)
event = dict(
type="realm_user",
op="remove",

View File

@@ -6,6 +6,7 @@ from unittest import mock
import orjson
from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.contrib.sessions.models import Session
from django.core.exceptions import ValidationError
from django.test import override_settings
from django.utils.timezone import now as timezone_now
@@ -1393,6 +1394,24 @@ class ActivateTest(ZulipTestCase):
)
self.assert_json_error(result, "Insufficient permission")
def test_clear_sessions(self) -> None:
user = self.example_user("hamlet")
self.login_user(user)
session_key = self.client.session.session_key
self.assertTrue(session_key)
result = self.client_get("/json/users")
self.assert_json_success(result)
self.assertEqual(Session.objects.filter(pk=session_key).count(), 1)
do_deactivate_user(user, acting_user=None)
self.assertEqual(Session.objects.filter(pk=session_key).count(), 0)
result = self.client_get("/json/users")
self.assert_json_error(
result, "Not logged in: API authentication or user session required", 401
)
def test_clear_scheduled_jobs(self) -> None:
user = self.example_user("hamlet")
send_future_email(