Compare commits
	
		
			2 Commits
		
	
	
		
			version/20
			...
			version/20
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| aba857753b | |||
| 022ff9b3a8 | 
| @ -1,5 +1,5 @@ | |||||||
| [bumpversion] | [bumpversion] | ||||||
| current_version = 2023.6.1 | current_version = 2023.6.2 | ||||||
| tag = True | tag = True | ||||||
| commit = True | commit = True | ||||||
| parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+) | parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+) | ||||||
|  | |||||||
| @ -2,7 +2,7 @@ | |||||||
| from os import environ | from os import environ | ||||||
| from typing import Optional | from typing import Optional | ||||||
|  |  | ||||||
| __version__ = "2023.6.1" | __version__ = "2023.6.2" | ||||||
| ENV_GIT_HASH_KEY = "GIT_BUILD_HASH" | ENV_GIT_HASH_KEY = "GIT_BUILD_HASH" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | |||||||
| @ -12,7 +12,7 @@ from rest_framework.fields import CharField | |||||||
| from rest_framework.serializers import ValidationError | from rest_framework.serializers import ValidationError | ||||||
|  |  | ||||||
| from authentik.flows.challenge import Challenge, ChallengeResponse, ChallengeTypes | from authentik.flows.challenge import Challenge, ChallengeResponse, ChallengeTypes | ||||||
| from authentik.flows.models import FlowToken | from authentik.flows.models import FlowDesignation, FlowToken | ||||||
| from authentik.flows.planner import PLAN_CONTEXT_IS_RESTORED, PLAN_CONTEXT_PENDING_USER | from authentik.flows.planner import PLAN_CONTEXT_IS_RESTORED, PLAN_CONTEXT_PENDING_USER | ||||||
| from authentik.flows.stage import ChallengeStageView | from authentik.flows.stage import ChallengeStageView | ||||||
| from authentik.flows.views.executor import QS_KEY_TOKEN | from authentik.flows.views.executor import QS_KEY_TOKEN | ||||||
| @ -82,6 +82,11 @@ class EmailStageView(ChallengeStageView): | |||||||
|         """Helper function that sends the actual email. Implies that you've |         """Helper function that sends the actual email. Implies that you've | ||||||
|         already checked that there is a pending user.""" |         already checked that there is a pending user.""" | ||||||
|         pending_user = self.get_pending_user() |         pending_user = self.get_pending_user() | ||||||
|  |         if not pending_user.pk and self.executor.flow.designation == FlowDesignation.RECOVERY: | ||||||
|  |             # Pending user does not have a primary key, and we're in a recovery flow, | ||||||
|  |             # which means the user entered an invalid identifier, so we pretend to send the | ||||||
|  |             # email, to not disclose if the user exists | ||||||
|  |             return | ||||||
|         email = self.executor.plan.context.get(PLAN_CONTEXT_EMAIL_OVERRIDE, None) |         email = self.executor.plan.context.get(PLAN_CONTEXT_EMAIL_OVERRIDE, None) | ||||||
|         if not email: |         if not email: | ||||||
|             email = pending_user.email |             email = pending_user.email | ||||||
|  | |||||||
| @ -5,18 +5,20 @@ from unittest.mock import MagicMock, PropertyMock, patch | |||||||
| from django.core import mail | from django.core import mail | ||||||
| from django.core.mail.backends.locmem import EmailBackend | from django.core.mail.backends.locmem import EmailBackend | ||||||
| from django.urls import reverse | from django.urls import reverse | ||||||
| from rest_framework.test import APITestCase |  | ||||||
|  |  | ||||||
|  | from authentik.core.models import User | ||||||
| from authentik.core.tests.utils import create_test_admin_user, create_test_flow | from authentik.core.tests.utils import create_test_admin_user, create_test_flow | ||||||
| from authentik.events.models import Event, EventAction | from authentik.events.models import Event, EventAction | ||||||
| from authentik.flows.markers import StageMarker | from authentik.flows.markers import StageMarker | ||||||
| from authentik.flows.models import FlowDesignation, FlowStageBinding | from authentik.flows.models import FlowDesignation, FlowStageBinding | ||||||
| from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan | from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan | ||||||
|  | from authentik.flows.tests import FlowTestCase | ||||||
| from authentik.flows.views.executor import SESSION_KEY_PLAN | from authentik.flows.views.executor import SESSION_KEY_PLAN | ||||||
|  | from authentik.lib.generators import generate_id | ||||||
| from authentik.stages.email.models import EmailStage | from authentik.stages.email.models import EmailStage | ||||||
|  |  | ||||||
|  |  | ||||||
| class TestEmailStageSending(APITestCase): | class TestEmailStageSending(FlowTestCase): | ||||||
|     """Email tests""" |     """Email tests""" | ||||||
|  |  | ||||||
|     def setUp(self): |     def setUp(self): | ||||||
| @ -44,6 +46,13 @@ class TestEmailStageSending(APITestCase): | |||||||
|         ): |         ): | ||||||
|             response = self.client.post(url) |             response = self.client.post(url) | ||||||
|             self.assertEqual(response.status_code, 200) |             self.assertEqual(response.status_code, 200) | ||||||
|  |             self.assertStageResponse( | ||||||
|  |                 response, | ||||||
|  |                 self.flow, | ||||||
|  |                 response_errors={ | ||||||
|  |                     "non_field_errors": [{"string": "email-sent", "code": "email-sent"}] | ||||||
|  |                 }, | ||||||
|  |             ) | ||||||
|             self.assertEqual(len(mail.outbox), 1) |             self.assertEqual(len(mail.outbox), 1) | ||||||
|             self.assertEqual(mail.outbox[0].subject, "authentik") |             self.assertEqual(mail.outbox[0].subject, "authentik") | ||||||
|             events = Event.objects.filter(action=EventAction.EMAIL_SENT) |             events = Event.objects.filter(action=EventAction.EMAIL_SENT) | ||||||
| @ -54,6 +63,32 @@ class TestEmailStageSending(APITestCase): | |||||||
|             self.assertEqual(event.context["to_email"], [self.user.email]) |             self.assertEqual(event.context["to_email"], [self.user.email]) | ||||||
|             self.assertEqual(event.context["from_email"], "system@authentik.local") |             self.assertEqual(event.context["from_email"], "system@authentik.local") | ||||||
|  |  | ||||||
|  |     def test_pending_fake_user(self): | ||||||
|  |         """Test with pending (fake) user""" | ||||||
|  |         self.flow.designation = FlowDesignation.RECOVERY | ||||||
|  |         self.flow.save() | ||||||
|  |         plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()]) | ||||||
|  |         plan.context[PLAN_CONTEXT_PENDING_USER] = User(username=generate_id()) | ||||||
|  |         session = self.client.session | ||||||
|  |         session[SESSION_KEY_PLAN] = plan | ||||||
|  |         session.save() | ||||||
|  |  | ||||||
|  |         url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}) | ||||||
|  |         with patch( | ||||||
|  |             "authentik.stages.email.models.EmailStage.backend_class", | ||||||
|  |             PropertyMock(return_value=EmailBackend), | ||||||
|  |         ): | ||||||
|  |             response = self.client.post(url) | ||||||
|  |             self.assertEqual(response.status_code, 200) | ||||||
|  |             self.assertStageResponse( | ||||||
|  |                 response, | ||||||
|  |                 self.flow, | ||||||
|  |                 response_errors={ | ||||||
|  |                     "non_field_errors": [{"string": "email-sent", "code": "email-sent"}] | ||||||
|  |                 }, | ||||||
|  |             ) | ||||||
|  |             self.assertEqual(len(mail.outbox), 0) | ||||||
|  |  | ||||||
|     def test_send_error(self): |     def test_send_error(self): | ||||||
|         """Test error during sending (sending will be retried)""" |         """Test error during sending (sending will be retried)""" | ||||||
|         plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()]) |         plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()]) | ||||||
|  | |||||||
| @ -118,8 +118,12 @@ class IdentificationChallengeResponse(ChallengeResponse): | |||||||
|                 username=uid_field, |                 username=uid_field, | ||||||
|                 email=uid_field, |                 email=uid_field, | ||||||
|             ) |             ) | ||||||
|  |             self.pre_user = self.stage.executor.plan.context[PLAN_CONTEXT_PENDING_USER] | ||||||
|             if not current_stage.show_matched_user: |             if not current_stage.show_matched_user: | ||||||
|                 self.stage.executor.plan.context[PLAN_CONTEXT_PENDING_USER_IDENTIFIER] = uid_field |                 self.stage.executor.plan.context[PLAN_CONTEXT_PENDING_USER_IDENTIFIER] = uid_field | ||||||
|  |             if self.stage.executor.flow.designation == FlowDesignation.RECOVERY: | ||||||
|  |                 # When used in a recovery flow, always continue to not disclose if a user exists | ||||||
|  |                 return attrs | ||||||
|             raise ValidationError("Failed to authenticate.") |             raise ValidationError("Failed to authenticate.") | ||||||
|         self.pre_user = pre_user |         self.pre_user = pre_user | ||||||
|         if not current_stage.password_stage: |         if not current_stage.password_stage: | ||||||
|  | |||||||
| @ -188,7 +188,7 @@ class TestIdentificationStage(FlowTestCase): | |||||||
|             ], |             ], | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|     def test_recovery_flow(self): |     def test_link_recovery_flow(self): | ||||||
|         """Test that recovery flow is linked correctly""" |         """Test that recovery flow is linked correctly""" | ||||||
|         flow = create_test_flow() |         flow = create_test_flow() | ||||||
|         self.stage.recovery_flow = flow |         self.stage.recovery_flow = flow | ||||||
| @ -226,6 +226,38 @@ class TestIdentificationStage(FlowTestCase): | |||||||
|             ], |             ], | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|  |     def test_recovery_flow_invalid_user(self): | ||||||
|  |         """Test that an invalid user can proceed in a recovery flow""" | ||||||
|  |         self.flow.designation = FlowDesignation.RECOVERY | ||||||
|  |         self.flow.save() | ||||||
|  |         response = self.client.get( | ||||||
|  |             reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), | ||||||
|  |         ) | ||||||
|  |         self.assertStageResponse( | ||||||
|  |             response, | ||||||
|  |             self.flow, | ||||||
|  |             component="ak-stage-identification", | ||||||
|  |             user_fields=["email"], | ||||||
|  |             password_fields=False, | ||||||
|  |             show_source_labels=False, | ||||||
|  |             primary_action="Continue", | ||||||
|  |             sources=[ | ||||||
|  |                 { | ||||||
|  |                     "challenge": { | ||||||
|  |                         "component": "xak-flow-redirect", | ||||||
|  |                         "to": "/source/oauth/login/test/", | ||||||
|  |                         "type": ChallengeTypes.REDIRECT.value, | ||||||
|  |                     }, | ||||||
|  |                     "icon_url": "/static/authentik/sources/default.svg", | ||||||
|  |                     "name": "test", | ||||||
|  |                 } | ||||||
|  |             ], | ||||||
|  |         ) | ||||||
|  |         form_data = {"uid_field": generate_id()} | ||||||
|  |         url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}) | ||||||
|  |         response = self.client.post(url, form_data) | ||||||
|  |         self.assertEqual(response.status_code, 200) | ||||||
|  |  | ||||||
|     def test_api_validate(self): |     def test_api_validate(self): | ||||||
|         """Test API validation""" |         """Test API validation""" | ||||||
|         self.assertTrue( |         self.assertTrue( | ||||||
|  | |||||||
| @ -32,7 +32,7 @@ services: | |||||||
|     volumes: |     volumes: | ||||||
|       - redis:/data |       - redis:/data | ||||||
|   server: |   server: | ||||||
|     image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2023.6.1} |     image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2023.6.2} | ||||||
|     restart: unless-stopped |     restart: unless-stopped | ||||||
|     command: server |     command: server | ||||||
|     environment: |     environment: | ||||||
| @ -53,7 +53,7 @@ services: | |||||||
|       - postgresql |       - postgresql | ||||||
|       - redis |       - redis | ||||||
|   worker: |   worker: | ||||||
|     image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2023.6.1} |     image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2023.6.2} | ||||||
|     restart: unless-stopped |     restart: unless-stopped | ||||||
|     command: worker |     command: worker | ||||||
|     environment: |     environment: | ||||||
|  | |||||||
| @ -29,4 +29,4 @@ func UserAgent() string { | |||||||
| 	return fmt.Sprintf("authentik@%s", FullVersion()) | 	return fmt.Sprintf("authentik@%s", FullVersion()) | ||||||
| } | } | ||||||
|  |  | ||||||
| const VERSION = "2023.6.1" | const VERSION = "2023.6.2" | ||||||
|  | |||||||
| @ -113,7 +113,7 @@ filterwarnings = [ | |||||||
|  |  | ||||||
| [tool.poetry] | [tool.poetry] | ||||||
| name = "authentik" | name = "authentik" | ||||||
| version = "2023.6.1" | version = "2023.6.2" | ||||||
| description = "" | description = "" | ||||||
| authors = ["authentik Team <hello@goauthentik.io>"] | authors = ["authentik Team <hello@goauthentik.io>"] | ||||||
|  |  | ||||||
|  | |||||||
| @ -1,7 +1,7 @@ | |||||||
| openapi: 3.0.3 | openapi: 3.0.3 | ||||||
| info: | info: | ||||||
|   title: authentik |   title: authentik | ||||||
|   version: 2023.6.1 |   version: 2023.6.2 | ||||||
|   description: Making authentication simple. |   description: Making authentication simple. | ||||||
|   contact: |   contact: | ||||||
|     email: hello@goauthentik.io |     email: hello@goauthentik.io | ||||||
|  | |||||||
| @ -3,7 +3,7 @@ export const SUCCESS_CLASS = "pf-m-success"; | |||||||
| export const ERROR_CLASS = "pf-m-danger"; | export const ERROR_CLASS = "pf-m-danger"; | ||||||
| export const PROGRESS_CLASS = "pf-m-in-progress"; | export const PROGRESS_CLASS = "pf-m-in-progress"; | ||||||
| export const CURRENT_CLASS = "pf-m-current"; | export const CURRENT_CLASS = "pf-m-current"; | ||||||
| export const VERSION = "2023.6.1"; | export const VERSION = "2023.6.2"; | ||||||
| export const TITLE_DEFAULT = "authentik"; | export const TITLE_DEFAULT = "authentik"; | ||||||
| export const ROUTE_SEPARATOR = ";"; | export const ROUTE_SEPARATOR = ";"; | ||||||
|  |  | ||||||
|  | |||||||
| @ -152,6 +152,10 @@ image: | |||||||
|  |  | ||||||
| -   \*: fix [CVE-2023-36456](../security/CVE-2023-36456), Reported by [@thijsa](https://github.com/thijsa) | -   \*: fix [CVE-2023-36456](../security/CVE-2023-36456), Reported by [@thijsa](https://github.com/thijsa) | ||||||
|  |  | ||||||
|  | ## Fixed in 2023.5.6 | ||||||
|  |  | ||||||
|  | -   \*: fix [CVE-2023-39522](../security/CVE-2023-39522), Reported by [@markrassamni](https://github.com/markrassamni) | ||||||
|  |  | ||||||
| ## API Changes | ## API Changes | ||||||
|  |  | ||||||
| #### What's Changed | #### What's Changed | ||||||
|  | |||||||
| @ -81,6 +81,18 @@ image: | |||||||
| -   web/user: refactor LibraryPage for testing, add CTA (#5665) | -   web/user: refactor LibraryPage for testing, add CTA (#5665) | ||||||
| -   web: Replace lingui.js with lit-localize (#5761) | -   web: Replace lingui.js with lit-localize (#5761) | ||||||
|  |  | ||||||
|  | ## Fixed in 2023.6.1 | ||||||
|  |  | ||||||
|  | -   core: fix UUID filter field for users api (#6203) | ||||||
|  | -   outposts/ldap: revert attribute filtering (#6188) | ||||||
|  | -   outposts/ldap: add test for attribute filtering (#6189) | ||||||
|  | -   sources/ldap: fix more errors (#6191) | ||||||
|  | -   sources/ldap: fix page size (#6187) | ||||||
|  |  | ||||||
|  | ## Fixed in 2023.6.2 | ||||||
|  |  | ||||||
|  | -   \*: fix [CVE-2023-39522](../security/CVE-2023-39522), Reported by [@markrassamni](https://github.com/markrassamni) | ||||||
|  |  | ||||||
| ## API Changes | ## API Changes | ||||||
|  |  | ||||||
| #### What's New | #### What's New | ||||||
|  | |||||||
							
								
								
									
										27
									
								
								website/docs/security/CVE-2023-39522.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								website/docs/security/CVE-2023-39522.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,27 @@ | |||||||
|  | # CVE-2023-39522 | ||||||
|  |  | ||||||
|  | _Reported by [@markrassamni](https://github.com/markrassamni)_ | ||||||
|  |  | ||||||
|  | ## Username enumeration attack | ||||||
|  |  | ||||||
|  | ### Summary | ||||||
|  |  | ||||||
|  | Using a recovery flow with an identification stage an attacker is able to determine if a username exists. | ||||||
|  |  | ||||||
|  | ### Patches | ||||||
|  |  | ||||||
|  | authentik 2023.5.6 and 2023.6.2 fix this issue. | ||||||
|  |  | ||||||
|  | ### Impact | ||||||
|  |  | ||||||
|  | Only setups configured with a recovery flow are impacted by this. | ||||||
|  |  | ||||||
|  | ### Details | ||||||
|  |  | ||||||
|  | An attacker can easily enumerate and check users' existence using the recovery flow, as a clear message is shown when a user doesn't exist. Depending on configuration this can either be done by username, email, or both. | ||||||
|  |  | ||||||
|  | ### For more information | ||||||
|  |  | ||||||
|  | If you have any questions or comments about this advisory: | ||||||
|  |  | ||||||
|  | -   Email us at [security@goauthentik.io](mailto:security@goauthentik.io) | ||||||
| @ -322,6 +322,7 @@ module.exports = { | |||||||
|             }, |             }, | ||||||
|             items: [ |             items: [ | ||||||
|                 "security/policy", |                 "security/policy", | ||||||
|  |                 "security/CVE-2023-39522", | ||||||
|                 "security/CVE-2023-36456", |                 "security/CVE-2023-36456", | ||||||
|                 "security/2023-06-cure53", |                 "security/2023-06-cure53", | ||||||
|                 "security/CVE-2023-26481", |                 "security/CVE-2023-26481", | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	