From 974c98a45ac78dc8cd9e6b5cefe4d7e32fcdf981 Mon Sep 17 00:00:00 2001 From: Mateusz Mandera Date: Fri, 18 Feb 2022 21:49:17 +0100 Subject: [PATCH] CVE-2021-3967: Only regenerate the API key by authing with the old key. --- static/js/settings_account.js | 9 ++++++++- zerver/tests/test_settings.py | 10 ++++++++++ zproject/urls.py | 6 +++++- 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/static/js/settings_account.js b/static/js/settings_account.js index 31edc6333b..7716ab99ff 100644 --- a/static/js/settings_account.js +++ b/static/js/settings_account.js @@ -345,8 +345,15 @@ export function set_up() { } $("#show_api_key").on("click", "button.regenerate_api_key", (e) => { + const email = page_params.delivery_email; + const api_key = $("#api_key_value").text(); + const authorization_header = "Basic " + btoa(`${email}:${api_key}`); + channel.post({ - url: "/json/users/me/api_key/regenerate", + // This endpoint is only accessible with the previous API key, + // via our usual HTTP Basic auth mechanism. + url: "/api/v1/users/me/api_key/regenerate", + headers: {Authorization: authorization_header}, success(data) { $("#api_key_value").text(data.api_key); }, diff --git a/zerver/tests/test_settings.py b/zerver/tests/test_settings.py index 93b93df65b..c924f22109 100644 --- a/zerver/tests/test_settings.py +++ b/zerver/tests/test_settings.py @@ -431,7 +431,17 @@ class UserChangesTest(ZulipTestCase): for api_key in old_api_keys: self.assertEqual(get_user_profile_by_api_key(api_key).email, email) + # First verify this endpoint is not registered in the /json/... path + # to prevent access with only a session. result = self.client_post("/json/users/me/api_key/regenerate") + self.assertEqual(result.status_code, 404) + + # A logged-in session doesn't allow access to an /api/v1/ endpoint + # of course. + result = self.client_post("/api/v1/users/me/api_key/regenerate") + self.assertEqual(result.status_code, 401) + + result = self.api_post(user, "/api/v1/users/me/api_key/regenerate") self.assert_json_success(result) new_api_key = result.json()["api_key"] self.assertNotIn(new_api_key, old_api_keys) diff --git a/zproject/urls.py b/zproject/urls.py index 75c1a5dd60..279f56f394 100644 --- a/zproject/urls.py +++ b/zproject/urls.py @@ -382,7 +382,6 @@ v1_api_and_json_patterns = [ rest_path("user_groups/", PATCH=edit_user_group, DELETE=delete_user_group), rest_path("user_groups//members", POST=update_user_group_backend), # users/me -> zerver.views.user_settings - rest_path("users/me/api_key/regenerate", POST=regenerate_api_key), rest_path( "users/me/enter-sends", POST=( @@ -711,6 +710,11 @@ v1_api_mobile_patterns = [ # This json format view used by the mobile apps accepts a username # password/pair and returns an API key. path("fetch_api_key", api_fetch_api_key), + # The endpoint for regenerating and obtaining a new API key + # should only be available by authenticating with the current + # API key - as we consider access to the API key sensitive + # and just having a logged-in session should be insufficient. + rest_path("users/me/api_key/regenerate", POST=regenerate_api_key), ] # View for uploading messages from email mirror