diff --git a/authentik/core/api/tokens.py b/authentik/core/api/tokens.py index a615f0d30e..7ce26e06de 100644 --- a/authentik/core/api/tokens.py +++ b/authentik/core/api/tokens.py @@ -44,6 +44,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 c70d1b98d3..15eda3f53d 100644 --- a/authentik/core/tests/test_token_api.py +++ b/authentik/core/tests/test_token_api.py @@ -13,9 +13,8 @@ from authentik.core.models import ( USER_ATTRIBUTE_TOKEN_MAXIMUM_LIFETIME, Token, TokenIntents, - User, ) -from authentik.core.tests.utils import create_test_admin_user +from authentik.core.tests.utils import create_test_admin_user, create_test_user from authentik.lib.generators import generate_id @@ -24,7 +23,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) @@ -154,6 +153,24 @@ class TestTokenAPI(APITestCase): self.assertEqual(token.expiring, True) self.assertNotEqual(token.expires.timestamp(), expires.timestamp()) + 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 e71adf30f5..390de639f6 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -511,6 +511,7 @@ const docsSidebar = { "security/security-hardening", "security/policy", "security/CVE-2024-38371", + "security/CVE-2024-37905", "security/CVE-2024-23647", "security/CVE-2024-21637", "security/CVE-2023-48228",