From e2dbab5bca831b4c65d65f5e8aedfb3933293084 Mon Sep 17 00:00:00 2001 From: Marc 'risson' Schmitt Date: Wed, 26 Jun 2024 12:28:55 +0200 Subject: [PATCH] security: fix CVE-2024-37905 (cherry-pick #10230) (#10238) security: fix CVE-2024-37905 (#10230) Co-authored-by: Jens L --- authentik/core/api/tokens.py | 9 ++++++++- authentik/core/tests/test_token_api.py | 24 +++++++++++++++++++--- website/docs/security/CVE-2024-37905.md | 27 +++++++++++++++++++++++++ website/sidebars.js | 1 + 4 files changed, 57 insertions(+), 4 deletions(-) create mode 100644 website/docs/security/CVE-2024-37905.md diff --git a/authentik/core/api/tokens.py b/authentik/core/api/tokens.py index 9c94992c57..59b7fc943d 100644 --- a/authentik/core/api/tokens.py +++ b/authentik/core/api/tokens.py @@ -20,7 +20,7 @@ from authentik.blueprints.v1.importer import SERIALIZER_CONTEXT_BLUEPRINT from authentik.core.api.used_by import UsedByMixin from authentik.core.api.users import UserSerializer from authentik.core.api.utils import PassiveSerializer -from authentik.core.models import USER_ATTRIBUTE_TOKEN_EXPIRING, Token, TokenIntents +from authentik.core.models import USER_ATTRIBUTE_TOKEN_EXPIRING, Token, TokenIntents, User from authentik.events.models import Event, EventAction from authentik.events.utils import model_to_dict from authentik.rbac.decorators import permission_required @@ -36,6 +36,13 @@ class TokenSerializer(ManagedSerializer, ModelSerializer): if SERIALIZER_CONTEXT_BLUEPRINT in self.context: self.fields["key"] = CharField(required=False) + def validate_user(self, user: User): + """Ensure user of token cannot be changed""" + if self.instance and self.instance.user_id: + if user.pk != self.instance.user_id: + raise ValidationError("User cannot be changed") + return user + def validate(self, attrs: dict[Any, str]) -> dict[Any, str]: """Ensure only API or App password tokens are created.""" request: Request = self.context.get("request") diff --git a/authentik/core/tests/test_token_api.py b/authentik/core/tests/test_token_api.py index 26e6740604..48cba271f3 100644 --- a/authentik/core/tests/test_token_api.py +++ b/authentik/core/tests/test_token_api.py @@ -7,8 +7,8 @@ from guardian.shortcuts import get_anonymous_user from rest_framework.test import APITestCase from authentik.core.api.tokens import TokenSerializer -from authentik.core.models import USER_ATTRIBUTE_TOKEN_EXPIRING, Token, TokenIntents, User -from authentik.core.tests.utils import create_test_admin_user +from authentik.core.models import USER_ATTRIBUTE_TOKEN_EXPIRING, Token, TokenIntents +from authentik.core.tests.utils import create_test_admin_user, create_test_user from authentik.lib.generators import generate_id @@ -17,7 +17,7 @@ class TestTokenAPI(APITestCase): def setUp(self) -> None: super().setUp() - self.user = User.objects.create(username="testuser") + self.user = create_test_user() self.admin = create_test_admin_user() self.client.force_login(self.user) @@ -76,6 +76,24 @@ class TestTokenAPI(APITestCase): self.assertEqual(token.intent, TokenIntents.INTENT_API) self.assertEqual(token.expiring, False) + def test_token_change_user(self): + """Test creating a token and then changing the user""" + ident = generate_id() + response = self.client.post(reverse("authentik_api:token-list"), {"identifier": ident}) + self.assertEqual(response.status_code, 201) + token = Token.objects.get(identifier=ident) + self.assertEqual(token.user, self.user) + self.assertEqual(token.intent, TokenIntents.INTENT_API) + self.assertEqual(token.expiring, True) + self.assertTrue(self.user.has_perm("authentik_core.view_token_key", token)) + response = self.client.put( + reverse("authentik_api:token-detail", kwargs={"identifier": ident}), + data={"identifier": "user_token_poc_v3", "intent": "api", "user": self.admin.pk}, + ) + self.assertEqual(response.status_code, 400) + token.refresh_from_db() + self.assertEqual(token.user, self.user) + def test_list(self): """Test Token List (Test normal authentication)""" Token.objects.all().delete() diff --git a/website/docs/security/CVE-2024-37905.md b/website/docs/security/CVE-2024-37905.md new file mode 100644 index 0000000000..3cc8ea7928 --- /dev/null +++ b/website/docs/security/CVE-2024-37905.md @@ -0,0 +1,27 @@ +# CVE-2024-37905 + +_Reported by [@m2a2](https://github.com/m2a2)_ + +## Improper Authorization for Token modification + +### Summary + +Due to insufficient permission checks it was possible for any authenticated user to elevate their permissions to a superuser by creating an API token and changing the user the token belonged to. + +### Patches + +authentik 2024.6.0, 2024.4.3 and 2024.2.4 fix this issue, for other versions the workaround can be used. + +### Details + +By setting a token's user ID to the ID of a higher privileged user, the token will inherit the higher privileged access to the API. This can be used to change the password of the affected user or to modify the authentik configuration in a potentially malicious way. + +### Workarounds + +As a workaround it is possible to block any requests to `/api/v3/core/tokens*` at the reverse-proxy/load-balancer level. Doing so prevents this issue from being exploited. + +### For more information + +If you have any questions or comments about this advisory: + +- Email us at [security@goauthentik.io](mailto:security@goauthentik.io) diff --git a/website/sidebars.js b/website/sidebars.js index 7015aca2e4..5989e7e562 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -411,6 +411,7 @@ const docsSidebar = { items: [ "security/policy", "security/CVE-2024-38371", + "security/CVE-2024-37905", "security/CVE-2024-23647", "security/CVE-2024-21637", "security/CVE-2023-48228",