From d5db254ca8167995a1654d1c45ffc74b2fade39a 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 b4a6c7fe26518..c65f7c54d9fdc 100644 --- a/static/js/settings_account.js +++ b/static/js/settings_account.js @@ -359,8 +359,15 @@ export function set_up() { } $("#regenerate_api_key").on("click", (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 9478bdd999324..853f966e6c728 100644 --- a/zerver/tests/test_settings.py +++ b/zerver/tests/test_settings.py @@ -495,7 +495,17 @@ def test_update_api_key(self) -> None: 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 63ce28b9fe412..6a3a56b152be3 100644 --- a/zproject/urls.py +++ b/zproject/urls.py @@ -372,7 +372,6 @@ 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/avatar", POST=set_avatar_backend, DELETE=delete_avatar_backend), # users/me/hotspots -> zerver.views.hotspots rest_path( @@ -724,6 +723,11 @@ # 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