CVE-2026-41577
Description
Authentik SAML source improperly handles assertion conditions, allowing expired or misdirected assertions to be replayed or accepted.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Authentik SAML source improperly handles assertion conditions, allowing expired or misdirected assertions to be replayed or accepted.
Vulnerability
Authentik's SAML source response processor (ResponseProcessor.parse()) fails to validate the Conditions element within SAML assertions. Specifically, the NotBefore, NotOnOrAfter, and AudienceRestriction attributes are ignored. This vulnerability affects versions prior to 2025.12.5 and 2026.2.3 [1].
Exploitation
An attacker can exploit this vulnerability by replaying previously captured SAML assertions, even if they have expired according to their NotOnOrAfter timestamp. Additionally, assertions intended for different service providers can be accepted if they are valid in other respects. No specific user interaction or authentication beyond possessing a valid SAML assertion is required [1].
Impact
Successful exploitation allows an attacker to reuse expired SAML assertions, effectively bypassing time-based validity checks. It also enables the acceptance of assertions meant for other service providers, potentially leading to unauthorized access or impersonation within the affected authentik instance [1].
Mitigation
This issue has been fixed in authentik versions 2025.12.5 and 2026.2.3. For versions where a patch is not yet available, a workaround can be applied. Further details on the workaround are not provided in the available references [1].
AI Insight generated on Jun 2, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
1Patches
1ee954d64f8feinternal: Automated internal backport: CVE-2026-41577.sec.patch to authentik-main (#22302)
4 files changed · +35 −0
authentik/sources/saml/processors/response.py+17 −0 modified@@ -1,6 +1,7 @@ """authentik saml source processor""" from base64 import b64decode +from datetime import UTC, datetime from time import mktime from typing import TYPE_CHECKING @@ -40,6 +41,7 @@ InvalidSignature, MismatchedRequestID, MissingSAMLResponse, + SAMLException, UnsupportedNameIDFormat, ) from authentik.sources.saml.models import ( @@ -95,6 +97,7 @@ def parse(self): self._verify_request_id() self._verify_status() + self._verify_conditions() def _decrypt_response(self): """Decrypt SAMLResponse EncryptedAssertion Element""" @@ -126,6 +129,20 @@ def _decrypt_response(self): ) self._assertion = decrypted_assertion + def _verify_conditions(self): + conditions = self.get_assertion().find(f"{{{NS_SAML_ASSERTION}}}Conditions") + if conditions is None: + return + _now = now() + before = conditions.attrib.get("NotBefore") + if before: + if datetime.fromisoformat(before).replace(tzinfo=UTC) > _now: + raise SAMLException("Assertion is not valid yet or expired.") + on_or_after = conditions.attrib.get("NotOnOrAfter") + if on_or_after: + if datetime.fromisoformat(on_or_after).replace(tzinfo=UTC) < _now: + raise SAMLException("Assertion is not valid yet or expired.") + def _verify_signature(self, signature_node: _Element): """Verify a single signature node""" xmlsec.tree.add_ids(self._root, ["ID"])
authentik/sources/saml/tests/test_property_mappings.py+4 −0 modified@@ -4,6 +4,7 @@ from defusedxml.lxml import fromstring from django.test import TestCase +from freezegun import freeze_time from authentik.common.saml.constants import NS_SAML_ASSERTION from authentik.core.tests.utils import RequestFactory, create_test_flow @@ -34,6 +35,7 @@ def setUp(self): pre_authentication_flow=create_test_flow(), ) + @freeze_time("2022-10-14T14:15:00") def test_user_base_properties(self): """Test user base properties""" properties = self.source.get_base_user_properties( @@ -61,6 +63,7 @@ def test_group_base_properties(self): properties = self.source.get_base_group_properties(root=ROOT, group_id=group_id) self.assertEqual(properties, {"name": group_id}) + @freeze_time("2022-10-14T14:15:00") def test_user_property_mappings(self): """Test user property mappings""" self.source.user_property_mappings.add( @@ -94,6 +97,7 @@ def test_user_property_mappings(self): }, ) + @freeze_time("2022-10-14T14:15:00") def test_group_property_mappings(self): """Test group property mappings""" self.source.group_property_mappings.add(
authentik/sources/saml/tests/test_response.py+11 −0 modified@@ -47,6 +47,7 @@ def test_status_error(self): ): ResponseProcessor(self.source, request).parse() + @freeze_time("2022-10-14T14:15:00") def test_success(self): """Test success""" request = self.factory.post( @@ -73,6 +74,7 @@ def test_success(self): }, ) + @freeze_time("2022-10-14T14:16:40Z") def test_success_with_status_message_and_detail(self): """Test success with StatusMessage and StatusDetail present (should not raise error)""" request = self.factory.post( @@ -89,6 +91,7 @@ def test_success_with_status_message_and_detail(self): sfm = parser.prepare_flow_manager() self.assertEqual(sfm.user_properties["username"], "jens@goauthentik.io") + @freeze_time("2022-10-14T14:16:40Z") def test_error_with_message_and_detail(self): """Test error status with StatusMessage and StatusDetail includes both in error""" request = self.factory.post( @@ -106,6 +109,7 @@ def test_error_with_message_and_detail(self): self.assertIn("User account is disabled", str(ctx.exception)) self.assertIn("Authentication failed", str(ctx.exception)) + @freeze_time("2024-08-07T15:48:09.325Z") def test_encrypted_correct(self): """Test encrypted""" key = load_fixture("fixtures/encrypted-key.pem") @@ -143,6 +147,7 @@ def test_encrypted_incorrect_key(self): with self.assertRaises(InvalidEncryption): parser.parse() + @freeze_time("2022-10-14T14:16:40Z") def test_verification_assertion(self): """Test verifying signature inside assertion""" key = load_fixture("fixtures/signature_cert.pem") @@ -165,6 +170,7 @@ def test_verification_assertion(self): parser = ResponseProcessor(self.source, request) parser.parse() + @freeze_time("2014-07-17T01:02:18Z") def test_verification_assertion_duplicate(self): """Test verifying signature inside assertion, where the response has another assertion before our signed assertion""" @@ -215,6 +221,7 @@ def test_name_id_comment(self): parser.parse() self.assertEqual(parser._get_name_id()[1], "_ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7") + @freeze_time("2014-07-17T01:02:18Z") def test_verification_response(self): """Test verifying signature inside response""" key = load_fixture("fixtures/signature_cert.pem") @@ -237,6 +244,7 @@ def test_verification_response(self): parser = ResponseProcessor(self.source, request) parser.parse() + @freeze_time("2024-01-18T06:20:48Z") def test_verification_response_and_assertion(self): """Test verifying signature inside response and assertion""" key = load_fixture("fixtures/signature_cert.pem") @@ -283,6 +291,7 @@ def test_verification_wrong_signature(self): with self.assertRaisesMessage(InvalidSignature, ""): parser.parse() + @freeze_time("2022-10-14T14:15:00") def test_verification_no_signature(self): """Test rejecting response without signature when signed_assertion is True""" key = load_fixture("fixtures/signature_cert.pem") @@ -329,6 +338,7 @@ def test_verification_incorrect_response(self): with self.assertRaisesMessage(InvalidSignature, ""): parser.parse() + @freeze_time("2025-10-30T05:45:47.619Z") def test_signed_encrypted_response(self): """Test signed & encrypted response""" verification_key = load_fixture("fixtures/signature_cert2.pem") @@ -356,6 +366,7 @@ def test_signed_encrypted_response(self): parser = ResponseProcessor(self.source, request) parser.parse() + @freeze_time("2026-01-21T14:23") def test_transient(self): """Test SAML transient NameID""" verification_key = load_fixture("fixtures/signature_cert2.pem")
authentik/sources/saml/tests/test_views.py+3 −0 modified@@ -4,6 +4,7 @@ from django.test import RequestFactory, TestCase from django.urls import reverse +from freezegun import freeze_time from authentik.core.tests.utils import create_test_flow from authentik.flows.planner import PLAN_CONTEXT_REDIRECT, FlowPlan @@ -26,6 +27,7 @@ def setUp(self): pre_authentication_flow=create_test_flow(), ) + @freeze_time("2022-10-14T14:15:00") def test_enroll(self): """Enroll""" flow = create_test_flow() @@ -52,6 +54,7 @@ def test_enroll(self): plan: FlowPlan = self.client.session.get(SESSION_KEY_PLAN) self.assertIsNotNone(plan) + @freeze_time("2022-10-14T14:15:00") def test_enroll_redirect(self): """Enroll when attempting to access a provider""" initial_redirect = f"http://{generate_id()}"
Vulnerability mechanics
Root cause
"The SAML response processor fails to validate the Conditions element within assertions."
Attack vector
An attacker can craft a SAML assertion that has expired or is intended for a different service provider. The `ResponseProcessor.parse()` method in the SAML source processor does not check the `NotBefore`, `NotOnOrAfter`, or `AudienceRestriction` attributes within the assertion's Conditions element. This allows an attacker to replay old assertions or use assertions meant for other parties, bypassing intended security controls.
Affected code
The vulnerability resides in the `ResponseProcessor.parse()` method within `authentik/sources/saml/processors/response.py`. The fix involves adding a call to the new `_verify_conditions` method within `parse()`, and the implementation of `_verify_conditions` itself, which parses and validates the `Conditions` element's attributes.
What the fix does
The patch introduces a new method `_verify_conditions` to the `ResponseProcessor` class. This method checks the `NotBefore` and `NotOnOrAfter` attributes within the assertion's Conditions element. If the assertion is not valid yet or has expired based on these timestamps, a `SAMLException` is raised, preventing the use of invalid assertions. This addresses the vulnerability by enforcing time-based validity checks on SAML assertions [patch_id=4524240].
Preconditions
- configThe authentik instance must be configured with a SAML source.
Generated on Jun 2, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
1News mentions
0No linked articles in our index yet.