From 19318d4c00bb02c4ec3c4f8f15ac2e1dbe8d846c Mon Sep 17 00:00:00 2001 From: "gcp-cherry-pick-bot[bot]" <98988430+gcp-cherry-pick-bot[bot]@users.noreply.github.com> Date: Thu, 22 Aug 2024 17:18:55 +0200 Subject: [PATCH] security: fix CVE-2024-42490 (cherry-pick #11022) (#11024) security: fix CVE-2024-42490 (#11022) CVE-2024-42490 Signed-off-by: Jens Langhammer Co-authored-by: Jens L. --- authentik/core/api/used_by.py | 3 +- authentik/crypto/api.py | 5 +- authentik/crypto/tests.py | 60 +++++++++++++++++++ authentik/flows/api/flows.py | 3 +- authentik/outposts/api/service_connections.py | 3 +- website/docs/security/CVE-2024-42490.md | 31 ++++++++++ website/sidebars.js | 1 + 7 files changed, 101 insertions(+), 5 deletions(-) create mode 100644 website/docs/security/CVE-2024-42490.md diff --git a/authentik/core/api/used_by.py b/authentik/core/api/used_by.py index 73c3a6a790..18c1390c27 100644 --- a/authentik/core/api/used_by.py +++ b/authentik/core/api/used_by.py @@ -14,6 +14,7 @@ from rest_framework.request import Request from rest_framework.response import Response from authentik.core.api.utils import PassiveSerializer +from authentik.rbac.filters import ObjectFilter class DeleteAction(Enum): @@ -53,7 +54,7 @@ class UsedByMixin: @extend_schema( responses={200: UsedBySerializer(many=True)}, ) - @action(detail=True, pagination_class=None, filter_backends=[]) + @action(detail=True, pagination_class=None, filter_backends=[ObjectFilter]) def used_by(self, request: Request, *args, **kwargs) -> Response: """Get a list of all objects that use this object""" model: Model = self.get_object() diff --git a/authentik/crypto/api.py b/authentik/crypto/api.py index a3e174387d..1c16214f7f 100644 --- a/authentik/crypto/api.py +++ b/authentik/crypto/api.py @@ -36,6 +36,7 @@ from authentik.crypto.builder import CertificateBuilder, PrivateKeyAlg from authentik.crypto.models import CertificateKeyPair from authentik.events.models import Event, EventAction from authentik.rbac.decorators import permission_required +from authentik.rbac.filters import ObjectFilter LOGGER = get_logger() @@ -266,7 +267,7 @@ class CertificateKeyPairViewSet(UsedByMixin, ModelViewSet): ], responses={200: CertificateDataSerializer(many=False)}, ) - @action(detail=True, pagination_class=None, filter_backends=[]) + @action(detail=True, pagination_class=None, filter_backends=[ObjectFilter]) def view_certificate(self, request: Request, pk: str) -> Response: """Return certificate-key pairs certificate and log access""" certificate: CertificateKeyPair = self.get_object() @@ -296,7 +297,7 @@ class CertificateKeyPairViewSet(UsedByMixin, ModelViewSet): ], responses={200: CertificateDataSerializer(many=False)}, ) - @action(detail=True, pagination_class=None, filter_backends=[]) + @action(detail=True, pagination_class=None, filter_backends=[ObjectFilter]) def view_private_key(self, request: Request, pk: str) -> Response: """Return certificate-key pairs private key and log access""" certificate: CertificateKeyPair = self.get_object() diff --git a/authentik/crypto/tests.py b/authentik/crypto/tests.py index 1d43367413..9fea61de52 100644 --- a/authentik/crypto/tests.py +++ b/authentik/crypto/tests.py @@ -214,6 +214,46 @@ class TestCrypto(APITestCase): self.assertEqual(200, response.status_code) self.assertIn("Content-Disposition", response) + def test_certificate_download_denied(self): + """Test certificate export (download)""" + self.client.logout() + keypair = create_test_cert() + response = self.client.get( + reverse( + "authentik_api:certificatekeypair-view-certificate", + kwargs={"pk": keypair.pk}, + ) + ) + self.assertEqual(403, response.status_code) + response = self.client.get( + reverse( + "authentik_api:certificatekeypair-view-certificate", + kwargs={"pk": keypair.pk}, + ), + data={"download": True}, + ) + self.assertEqual(403, response.status_code) + + def test_private_key_download_denied(self): + """Test private_key export (download)""" + self.client.logout() + keypair = create_test_cert() + response = self.client.get( + reverse( + "authentik_api:certificatekeypair-view-private-key", + kwargs={"pk": keypair.pk}, + ) + ) + self.assertEqual(403, response.status_code) + response = self.client.get( + reverse( + "authentik_api:certificatekeypair-view-private-key", + kwargs={"pk": keypair.pk}, + ), + data={"download": True}, + ) + self.assertEqual(403, response.status_code) + def test_used_by(self): """Test used_by endpoint""" self.client.force_login(create_test_admin_user()) @@ -246,6 +286,26 @@ class TestCrypto(APITestCase): ], ) + def test_used_by_denied(self): + """Test used_by endpoint""" + self.client.logout() + keypair = create_test_cert() + OAuth2Provider.objects.create( + name=generate_id(), + client_id="test", + client_secret=generate_key(), + authorization_flow=create_test_flow(), + redirect_uris="http://localhost", + signing_key=keypair, + ) + response = self.client.get( + reverse( + "authentik_api:certificatekeypair-used-by", + kwargs={"pk": keypair.pk}, + ) + ) + self.assertEqual(403, response.status_code) + def test_discovery(self): """Test certificate discovery""" name = generate_id() diff --git a/authentik/flows/api/flows.py b/authentik/flows/api/flows.py index 121b3f8077..062c107f46 100644 --- a/authentik/flows/api/flows.py +++ b/authentik/flows/api/flows.py @@ -33,6 +33,7 @@ from authentik.lib.utils.file import ( ) from authentik.lib.views import bad_request_message from authentik.rbac.decorators import permission_required +from authentik.rbac.filters import ObjectFilter LOGGER = get_logger() @@ -277,7 +278,7 @@ class FlowViewSet(UsedByMixin, ModelViewSet): 400: OpenApiResponse(description="Flow not applicable"), }, ) - @action(detail=True, pagination_class=None, filter_backends=[]) + @action(detail=True, pagination_class=None, filter_backends=[ObjectFilter]) def execute(self, request: Request, slug: str): """Execute flow for current user""" # Because we pre-plan the flow here, and not in the planner, we need to manually clear diff --git a/authentik/outposts/api/service_connections.py b/authentik/outposts/api/service_connections.py index 6fe8300d22..b796227b1a 100644 --- a/authentik/outposts/api/service_connections.py +++ b/authentik/outposts/api/service_connections.py @@ -23,6 +23,7 @@ from authentik.outposts.models import ( KubernetesServiceConnection, OutpostServiceConnection, ) +from authentik.rbac.filters import ObjectFilter class ServiceConnectionSerializer(ModelSerializer, MetaNameSerializer): @@ -88,7 +89,7 @@ class ServiceConnectionViewSet( return Response(TypeCreateSerializer(data, many=True).data) @extend_schema(responses={200: ServiceConnectionStateSerializer(many=False)}) - @action(detail=True, pagination_class=None, filter_backends=[]) + @action(detail=True, pagination_class=None, filter_backends=[ObjectFilter]) def state(self, request: Request, pk: str) -> Response: """Get the service connection's state""" connection = self.get_object() diff --git a/website/docs/security/CVE-2024-42490.md b/website/docs/security/CVE-2024-42490.md new file mode 100644 index 0000000000..3a024aa80d --- /dev/null +++ b/website/docs/security/CVE-2024-42490.md @@ -0,0 +1,31 @@ +# CVE-2024-42490 + +_Reported by [@m2a2](https://github.com/m2a2)_ + +## Improper Authorization for Token modification + +### Summary + +Several API endpoints can be accessed by users without correct authentication/authorization. + +The main API endpoints affected by this: + +- `/api/v3/crypto/certificatekeypairs//view_certificate/` +- `/api/v3/crypto/certificatekeypairs//view_private_key/` +- `/api/v3/.../used_by/` + +Note that all of the affected API endpoints require the knowledge of the ID of an object, which especially for certificates is not accessible to an unprivileged user. Additionally the IDs for most objects are UUIDv4, meaning they are not easily guessable/enumerable. + +### Patches + +authentik 2024.4.4, 2024.6.4 and 2024.8.0 fix this issue. + +### Workarounds + +Access to the API endpoints can be blocked at a Reverse-proxy/Load balancer level to prevent 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 ba7b39f4b1..232aa7989c 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -436,6 +436,7 @@ const docsSidebar = { }, items: [ "security/policy", + "security/CVE-2024-42490", "security/CVE-2024-38371", "security/CVE-2024-37905", "security/CVE-2024-23647",