registration: Check realm against PreregistrationUser realm.

We would allow a user with a valid invitation for one realm to use it
on a different realm instead.  On a server with multiple realms, an
authorized user of one realm could use this (by sending invites to
other email addresses they control) to create accounts on other
realms. (CVE-2017-0910)

With this commit, when sending an invitation, we record the inviting
user's realm on the PreregistrationUser row; and when registering a
user, we check that the PregistrationUser realm matches the realm the
user is trying to register on.  This resolves CVE-2017-0910 for
newly-sent invitations; the next commit completes the fix.

[greg: rewrote commit message]
This commit is contained in:
Vishnu Ks
2017-11-08 21:02:59 +00:00
committed by Greg Price
parent 8b935f4e99
commit 985768b2fd
4 changed files with 28 additions and 9 deletions

View File

@@ -4000,7 +4000,8 @@ def do_invite_users(user_profile, invitee_emails, streams, invite_as_admin=False
for email in validated_emails:
# The logged in user is the referrer.
prereg_user = PreregistrationUser(email=email, referred_by=user_profile,
invited_as_admin=invite_as_admin)
invited_as_admin=invite_as_admin,
realm=user_profile.realm)
prereg_user.save()
stream_ids = [stream.id for stream in streams]

View File

@@ -353,7 +353,7 @@ class LoginTest(ZulipTestCase):
with queries_captured() as queries:
self.register(self.nonreg_email('test'), "test")
# Ensure the number of queries we make is not O(streams)
self.assert_length(queries, 65)
self.assert_length(queries, 67)
user_profile = self.nonreg_user('test')
self.assertEqual(get_session_dict_user(self.client.session), user_profile.id)
self.assertFalse(user_profile.enable_stream_desktop_notifications)
@@ -1691,6 +1691,22 @@ class UserSignUpTest(ZulipTestCase):
mock_error.assert_called_once()
self.assertEqual(result.status_code, 302)
def test_replace_subdomain_in_confirmation_link(self) -> None:
"""
Check that manually changing the subdomain in a registration
confirmation link doesn't allow you to register to a different realm.
"""
email = "newuser@zulip.com"
self.client_post('/accounts/home/', {'email': email})
result = self.client_post(
'/accounts/register/',
{'password': "password",
'key': find_key_by_email(email),
'terms': True,
'full_name': "New User",
'from_confirmation': '1'}, subdomain="zephyr")
self.assert_in_success_response(["We couldn't find your confirmation link"], result)
def test_failed_signup_due_to_restricted_domain(self) -> None:
realm = get_realm('zulip')
realm.invite_required = False

View File

@@ -48,9 +48,13 @@ import ujson
def create_preregistration_user(email, request, realm_creation=False,
password_required=True):
# type: (Text, HttpRequest, bool, bool) -> HttpResponse
realm = None
if not realm_creation:
realm = get_realm(get_subdomain(request))
return PreregistrationUser.objects.create(email=email,
realm_creation=realm_creation,
password_required=password_required)
password_required=password_required,
realm=realm)
def maybe_send_to_registration(request, email, full_name='', password_required=True):
# type: (HttpRequest, Text, Text, bool) -> HttpResponse
@@ -74,7 +78,7 @@ def maybe_send_to_registration(request, email, full_name='', password_required=T
prereg_user = None
if settings.ONLY_SSO:
try:
prereg_user = PreregistrationUser.objects.filter(email__iexact=email).latest("invited_at")
prereg_user = PreregistrationUser.objects.filter(email__iexact=email, realm=realm).latest("invited_at")
except PreregistrationUser.DoesNotExist:
prereg_user = create_preregistration_user(email, request,
password_required=password_required)

View File

@@ -59,15 +59,13 @@ def accounts_register(request):
is_realm_admin = prereg_user.invited_as_admin or realm_creation
validators.validate_email(email)
if prereg_user.referred_by:
# If someone invited you, you are joining their realm regardless
# of your e-mail address.
realm = prereg_user.referred_by.realm
elif realm_creation:
if realm_creation:
# For creating a new realm, there is no existing realm or domain
realm = None
else:
realm = get_realm(get_subdomain(request))
if prereg_user.realm is not None and prereg_user.realm != realm:
return render(request, 'confirmation/link_does_not_exist.html')
if realm and not email_allowed_for_realm(email, realm):
return render(request, "zerver/closed_realm.html",