Compare commits
	
		
			3 Commits
		
	
	
		
			version/20
			...
			version/20
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 3925f5a208 | |||
| 6add4a62b9 | |||
| 54d5aa20ba | 
| @ -1,5 +1,5 @@ | ||||
| [bumpversion] | ||||
| current_version = 2023.5.5 | ||||
| current_version = 2023.5.6 | ||||
| tag = True | ||||
| commit = True | ||||
| parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+) | ||||
|  | ||||
| @ -2,7 +2,7 @@ | ||||
| from os import environ | ||||
| from typing import Optional | ||||
|  | ||||
| __version__ = "2023.5.5" | ||||
| __version__ = "2023.5.6" | ||||
| ENV_GIT_HASH_KEY = "GIT_BUILD_HASH" | ||||
|  | ||||
|  | ||||
|  | ||||
| @ -12,7 +12,7 @@ from rest_framework.fields import CharField | ||||
| from rest_framework.serializers import ValidationError | ||||
|  | ||||
| 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.stage import ChallengeStageView | ||||
| 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 | ||||
|         already checked that there is a 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) | ||||
|         if not email: | ||||
|             email = pending_user.email | ||||
|  | ||||
| @ -5,18 +5,20 @@ from unittest.mock import MagicMock, PropertyMock, patch | ||||
| from django.core import mail | ||||
| from django.core.mail.backends.locmem import EmailBackend | ||||
| 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.events.models import Event, EventAction | ||||
| from authentik.flows.markers import StageMarker | ||||
| from authentik.flows.models import FlowDesignation, FlowStageBinding | ||||
| 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.lib.generators import generate_id | ||||
| from authentik.stages.email.models import EmailStage | ||||
|  | ||||
|  | ||||
| class TestEmailStageSending(APITestCase): | ||||
| class TestEmailStageSending(FlowTestCase): | ||||
|     """Email tests""" | ||||
|  | ||||
|     def setUp(self): | ||||
| @ -44,6 +46,13 @@ class TestEmailStageSending(APITestCase): | ||||
|         ): | ||||
|             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), 1) | ||||
|             self.assertEqual(mail.outbox[0].subject, "authentik") | ||||
|             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["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): | ||||
|         """Test error during sending (sending will be retried)""" | ||||
|         plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()]) | ||||
|  | ||||
| @ -118,8 +118,12 @@ class IdentificationChallengeResponse(ChallengeResponse): | ||||
|                 username=uid_field, | ||||
|                 email=uid_field, | ||||
|             ) | ||||
|             self.pre_user = self.stage.executor.plan.context[PLAN_CONTEXT_PENDING_USER] | ||||
|             if not current_stage.show_matched_user: | ||||
|                 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.") | ||||
|         self.pre_user = pre_user | ||||
|         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""" | ||||
|         flow = create_test_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): | ||||
|         """Test API validation""" | ||||
|         self.assertTrue( | ||||
|  | ||||
| @ -32,7 +32,7 @@ services: | ||||
|     volumes: | ||||
|       - redis:/data | ||||
|   server: | ||||
|     image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2023.5.5} | ||||
|     image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2023.5.6} | ||||
|     restart: unless-stopped | ||||
|     command: server | ||||
|     environment: | ||||
| @ -50,7 +50,7 @@ services: | ||||
|       - "${COMPOSE_PORT_HTTP:-9000}:9000" | ||||
|       - "${COMPOSE_PORT_HTTPS:-9443}:9443" | ||||
|   worker: | ||||
|     image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2023.5.5} | ||||
|     image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2023.5.6} | ||||
|     restart: unless-stopped | ||||
|     command: worker | ||||
|     environment: | ||||
|  | ||||
| @ -29,4 +29,4 @@ func UserAgent() string { | ||||
| 	return fmt.Sprintf("authentik@%s", FullVersion()) | ||||
| } | ||||
|  | ||||
| const VERSION = "2023.5.5" | ||||
| const VERSION = "2023.5.6" | ||||
|  | ||||
| @ -113,7 +113,7 @@ filterwarnings = [ | ||||
|  | ||||
| [tool.poetry] | ||||
| name = "authentik" | ||||
| version = "2023.5.5" | ||||
| version = "2023.5.6" | ||||
| description = "" | ||||
| authors = ["authentik Team <hello@goauthentik.io>"] | ||||
|  | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
| openapi: 3.0.3 | ||||
| info: | ||||
|   title: authentik | ||||
|   version: 2023.5.5 | ||||
|   version: 2023.5.6 | ||||
|   description: Making authentication simple. | ||||
|   contact: | ||||
|     email: hello@goauthentik.io | ||||
|  | ||||
| @ -3,7 +3,7 @@ export const SUCCESS_CLASS = "pf-m-success"; | ||||
| export const ERROR_CLASS = "pf-m-danger"; | ||||
| export const PROGRESS_CLASS = "pf-m-in-progress"; | ||||
| export const CURRENT_CLASS = "pf-m-current"; | ||||
| export const VERSION = "2023.5.5"; | ||||
| export const VERSION = "2023.5.6"; | ||||
| export const TITLE_DEFAULT = "authentik"; | ||||
| export const ROUTE_SEPARATOR = ";"; | ||||
|  | ||||
|  | ||||
| @ -118,6 +118,37 @@ image: | ||||
| -   web/flows: improve UI for TOTP code input (#5676) | ||||
| -   web/flows: update flow background (#5639) | ||||
|  | ||||
| ## Fixed in 2023.5.2 | ||||
|  | ||||
| -   blueprints: fix check for file path not being run on worker (#5703) | ||||
| -   blueprints: support custom ports for OCI blueprints (#5727) | ||||
| -   core: bump coverage from 7.2.5 to 7.2.6 (#5738) | ||||
| -   core: make groups field for user optional (#5702) | ||||
| -   events: fix ak_create_event using wrong request for event creation (#5731) | ||||
| -   lib: add tests for ak_create_event (#5710) | ||||
| -   outposts: fix missing radius outpost controller (#5730) | ||||
| -   web/user: fix MFA enroll dropdown broken when password stage has no configuration flow (#5744) | ||||
|  | ||||
| ## Fixed in 2023.5.3 | ||||
|  | ||||
| -   blueprints: fix API validation with OCI blueprint path (#5822) | ||||
| -   ci: build outpost binaries statically linked (#5823) | ||||
| -   ci: replace github bot account with github app (#5819) | ||||
| -   providers/ldap: fix LDAP Outpost application selection (#5812) | ||||
| -   web/flows: fix RedirectStage not detecting absolute URLs correctly (#5781) | ||||
|  | ||||
| ## Fixed in 2023.5.4 | ||||
|  | ||||
| -   security: Address pen-test findings from the [2023-06 Cure53 Code audit](../../security/2023-06-cure53.md) | ||||
|  | ||||
| ## Fixed in 2023.5.5 | ||||
|  | ||||
| -   \*: 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 | ||||
|  | ||||
| #### What's Changed | ||||
|  | ||||
							
								
								
									
										67
									
								
								website/docs/security/2023-06-cure53.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								website/docs/security/2023-06-cure53.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,67 @@ | ||||
| # 2023-06 Cure53 Code audit | ||||
|  | ||||
| In May/June of 2023, we've had a Pen-test conducted by [Cure53](https://cure53.de). The following security updates, 2023.4.2 and 2023.5.3 were released as a response to the found issues. | ||||
|  | ||||
| From the complete report, these are the points we're addressing with this update: | ||||
|  | ||||
| ### ATH-01-001: Path traversal on blueprints allows arbitrary file-read (Medium) | ||||
|  | ||||
| This had accidentally been patched by a previous commit already; and was also only possible for users with superuser permissions. | ||||
|  | ||||
| ### ATH-01-003: CSS injection via faulty string replacement in Mermaid (Low) | ||||
|  | ||||
| This is an unrelated issue that was found with a third-party dependency ([Mermaid](https://mermaid.js.org/)), fixed with https://github.com/mermaid-js/mermaid/releases/tag/v10.2.2 | ||||
|  | ||||
| Additionally we've also taken steps to further mitigate possible issues that could be caused in this way. | ||||
|  | ||||
| ### ATH-01-008: User-passwords disclosed to third-party service (High) | ||||
|  | ||||
| In certain circumstances, using the Enter key to submit some forms instead of clicking submit would cause the frontend to change the URL instead of calling the API, which could lead to sensitive data being disclosed. | ||||
|  | ||||
| ### ATH-01-009: Lack of CSRF protection in impersonate feature (Low) | ||||
|  | ||||
| Previous the URL to start an impersonation was a simple GET URL request, which was susceptible to CSRF. This has been changed to an API Post request. | ||||
|  | ||||
| ### ATH-01-010: Web authentication bypass via key confusion (High) | ||||
|  | ||||
| When using WebAuthn to authenticate, the owner of the WebAuthn device wasn't checked. However to exploit this, an attacker would need to be able to already intercept HTTP traffic and read the data. | ||||
|  | ||||
| ### ATH-01-014: Authentication challenges abused by foreign flow (Medium) | ||||
|  | ||||
| Previously it was possible to use an MFA authenticator class that wasn't allowed in a flow, if another flow existed that allowed this class. The patch changes data to be isolated per flow to prevent this issue. | ||||
|  | ||||
| ### ATH-01-004: Information disclosure on system endpoint (Info) | ||||
|  | ||||
| The `/api/v3/admin/system/` (only accessible to superusers) endpoint returns a large amount of system info (mostly used for debugging), like the HTTP headers sent to the server. It also included all environment variables set for authentik. The environment variables have been removed. | ||||
|  | ||||
| ### ATH-01-005: Timing-unsafe comparison in API authentication (Info) | ||||
|  | ||||
| In the API authentication that is used by the embedded outpost (API authentication via Secret key), a timing-unsafe comparison was used. | ||||
|  | ||||
| ### ATH-01-012: Unintended diagram created due to unescaped quotes (Info) | ||||
|  | ||||
| Related to ATH-01-003, it was possible to insert unintended diagrams into generated diagrams. | ||||
|  | ||||
| ## Additional info | ||||
|  | ||||
| In addition to the points above, several of the findings are classified as intended features (such as the expression policies), however these are points where we do also see room for improvement that we will address in the future. | ||||
|  | ||||
| ### ATH-01-002: Stored XSS in help text of prompt module (Medium) | ||||
|  | ||||
| Prompt help texts can use HTML to add markup, which also includes the option to include JavaScript. This is only possible to configure for superusers, and in the future we're planning to add an additional toggle to limit this. | ||||
|  | ||||
| ### ATH-01-006: Arbitrary code execution via expressions (Critical) | ||||
|  | ||||
| This is the intended function of expression policies/property mappings, which also requires superuser permissions to edit. We're planning to also add a toggle to limit the functions that can be executed to the ones provided by authentik, and prevent the importing of modules. | ||||
|  | ||||
| ### ATH-01-007: SSRF via blueprints feature for fetching manifests (Medium) | ||||
|  | ||||
| Blueprints can be fetched via OCI registries, which could be potentially used for server-side request forgery. This can only be accessed by superusers, and we're planning to add an option to limit the resolved IP ranges this functionality can connect to. | ||||
|  | ||||
| ### ATH-01-013: XSS via CAPTCHA JavaScript URL (Medium) | ||||
|  | ||||
| Similar to ATH-01-002, any arbitrary JavaScript can be loaded using the Captcha stage. This is also limited to superusers. | ||||
|  | ||||
| ### ATH-01-011: Weak default configs in logout/change password flows (Info) | ||||
|  | ||||
| The default logout flow does not do any additional validation and logs the user out with a single GET request. The default password-change flow does not verify the users current password, nor does it show the current users info. | ||||
							
								
								
									
										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) | ||||
| @ -321,6 +321,7 @@ module.exports = { | ||||
|             }, | ||||
|             items: [ | ||||
|                 "security/policy", | ||||
|                 "security/CVE-2023-39522", | ||||
|                 "security/CVE-2023-36456", | ||||
|                 "security/CVE-2023-26481", | ||||
|                 "security/CVE-2022-23555", | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	