Compare commits
	
		
			2 Commits
		
	
	
		
			dependabot
			...
			version-20
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| aba857753b | |||
| 022ff9b3a8 | 
@ -1,5 +1,5 @@
 | 
			
		||||
[bumpversion]
 | 
			
		||||
current_version = 2023.6.1
 | 
			
		||||
current_version = 2023.6.2
 | 
			
		||||
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.6.1"
 | 
			
		||||
__version__ = "2023.6.2"
 | 
			
		||||
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.6.1}
 | 
			
		||||
    image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2023.6.2}
 | 
			
		||||
    restart: unless-stopped
 | 
			
		||||
    command: server
 | 
			
		||||
    environment:
 | 
			
		||||
@ -53,7 +53,7 @@ services:
 | 
			
		||||
      - postgresql
 | 
			
		||||
      - redis
 | 
			
		||||
  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
 | 
			
		||||
    command: worker
 | 
			
		||||
    environment:
 | 
			
		||||
 | 
			
		||||
@ -29,4 +29,4 @@ func UserAgent() string {
 | 
			
		||||
	return fmt.Sprintf("authentik@%s", FullVersion())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const VERSION = "2023.6.1"
 | 
			
		||||
const VERSION = "2023.6.2"
 | 
			
		||||
 | 
			
		||||
@ -113,7 +113,7 @@ filterwarnings = [
 | 
			
		||||
 | 
			
		||||
[tool.poetry]
 | 
			
		||||
name = "authentik"
 | 
			
		||||
version = "2023.6.1"
 | 
			
		||||
version = "2023.6.2"
 | 
			
		||||
description = ""
 | 
			
		||||
authors = ["authentik Team <hello@goauthentik.io>"]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
openapi: 3.0.3
 | 
			
		||||
info:
 | 
			
		||||
  title: authentik
 | 
			
		||||
  version: 2023.6.1
 | 
			
		||||
  version: 2023.6.2
 | 
			
		||||
  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.6.1";
 | 
			
		||||
export const VERSION = "2023.6.2";
 | 
			
		||||
export const TITLE_DEFAULT = "authentik";
 | 
			
		||||
export const ROUTE_SEPARATOR = ";";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -152,6 +152,10 @@ image:
 | 
			
		||||
 | 
			
		||||
-   \*: 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
 | 
			
		||||
 | 
			
		||||
@ -81,6 +81,18 @@ image:
 | 
			
		||||
-   web/user: refactor LibraryPage for testing, add CTA (#5665)
 | 
			
		||||
-   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
 | 
			
		||||
 | 
			
		||||
#### 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: [
 | 
			
		||||
                "security/policy",
 | 
			
		||||
                "security/CVE-2023-39522",
 | 
			
		||||
                "security/CVE-2023-36456",
 | 
			
		||||
                "security/2023-06-cure53",
 | 
			
		||||
                "security/CVE-2023-26481",
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user