diff --git a/authentik/core/api/tokens.py b/authentik/core/api/tokens.py index a615f0d30eef..7ce26e06de18 100644 --- a/authentik/core/api/tokens.py +++ b/authentik/core/api/tokens.py @@ -44,6 +44,13 @@ def __init__(self, *args, **kwargs) -> None: 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 c70d1b98d310..15eda3f53d86 100644 --- a/authentik/core/tests/test_token_api.py +++ b/authentik/core/tests/test_token_api.py @@ -13,9 +13,8 @@ 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 @@ def test_token_create_expiring_custom_api(self): 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 000000000000..3cc8ea7928a1 --- /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 e71adf30f5d3..390de639f62c 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",