From 00f0cfe6e40b6f0986099d2b1f119af69b61ab2a Mon Sep 17 00:00:00 2001 From: "authentik-automation[bot]" <135050075+authentik-automation[bot]@users.noreply.github.com> Date: Tue, 12 May 2026 20:26:13 +0200 Subject: [PATCH] internal: Automated internal backport: CVE-2026-41569.sec.patch to authentik-main (#22301) * Automated internal backport of patch CVE-2026-41569.sec.patch to authentik-main * fix spell Signed-off-by: Jens Langhammer --------- Signed-off-by: Jens Langhammer Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com> Co-authored-by: Jens Langhammer --- .../ws_federation/processors/sign_in.py | 5 ++- .../ws_federation/processors/sign_out.py | 5 ++- .../ws_federation/tests/test_sign_in.py | 15 +++++++++ locale/en/dictionaries/software-terms.txt | 1 + website/docs/security/cves/CVE-2026-41569.md | 33 +++++++++++++++++++ 5 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 website/docs/security/cves/CVE-2026-41569.md diff --git a/authentik/enterprise/providers/ws_federation/processors/sign_in.py b/authentik/enterprise/providers/ws_federation/processors/sign_in.py index d9388e0191..7c4d310c02 100644 --- a/authentik/enterprise/providers/ws_federation/processors/sign_in.py +++ b/authentik/enterprise/providers/ws_federation/processors/sign_in.py @@ -1,4 +1,5 @@ from dataclasses import dataclass +from urllib.parse import urlparse from django.http import HttpRequest from django.shortcuts import get_object_or_404 @@ -55,7 +56,9 @@ class SignInRequest: _, provider = req.get_app_provider() if not req.wreply: req.wreply = provider.acs_url - if not req.wreply.startswith(provider.acs_url): + reply = urlparse(req.wreply) + configured = urlparse(provider.acs_url) + if not (reply[:2] == configured[:2] and reply.path.startswith(configured.path)): raise ValueError("Invalid wreply") return req diff --git a/authentik/enterprise/providers/ws_federation/processors/sign_out.py b/authentik/enterprise/providers/ws_federation/processors/sign_out.py index 97514b1a5b..1446627f69 100644 --- a/authentik/enterprise/providers/ws_federation/processors/sign_out.py +++ b/authentik/enterprise/providers/ws_federation/processors/sign_out.py @@ -1,4 +1,5 @@ from dataclasses import dataclass +from urllib.parse import urlparse from django.http import HttpRequest from django.shortcuts import get_object_or_404 @@ -32,7 +33,9 @@ class SignOutRequest: _, provider = req.get_app_provider() if not req.wreply: req.wreply = provider.acs_url - if not req.wreply.startswith(provider.acs_url): + reply = urlparse(req.wreply) + configured = urlparse(provider.acs_url) + if not (reply[:2] == configured[:2] and reply.path.startswith(configured.path)): raise ValueError("Invalid wreply") return req diff --git a/authentik/enterprise/providers/ws_federation/tests/test_sign_in.py b/authentik/enterprise/providers/ws_federation/tests/test_sign_in.py index 803a9717dd..066e3135f7 100644 --- a/authentik/enterprise/providers/ws_federation/tests/test_sign_in.py +++ b/authentik/enterprise/providers/ws_federation/tests/test_sign_in.py @@ -27,12 +27,27 @@ class TestWSFedSignIn(TestCase): name=generate_id(), authorization_flow=self.flow, signing_kp=self.cert, + acs_url="https://t.goauthentik.io", + audience="foo", ) self.app = Application.objects.create( name=generate_id(), slug=generate_id(), provider=self.provider ) self.factory = RequestFactory() + def test_wreply(self): + request = self.factory.get( + "/?wreply=https://t.goauthentik.io/foo&wa=wsignin1.0&wtrealm=foo", + user=get_anonymous_user(), + ) + SignInRequest.parse(request) + with self.assertRaises(ValueError): + request = self.factory.get( + "/?wreply=https://t.goauthentik.io.invalid.com&wa=wsignin1.0&wtrealm=foo", + user=get_anonymous_user(), + ) + SignInRequest.parse(request) + def test_token_gen(self): request = self.factory.get("/", user=get_anonymous_user()) proc = SignInProcessor( diff --git a/locale/en/dictionaries/software-terms.txt b/locale/en/dictionaries/software-terms.txt index 6ba502af83..8c631ba414 100644 --- a/locale/en/dictionaries/software-terms.txt +++ b/locale/en/dictionaries/software-terms.txt @@ -164,3 +164,4 @@ yamltags zxcvbn ~uuid ~uuids +wreply diff --git a/website/docs/security/cves/CVE-2026-41569.md b/website/docs/security/cves/CVE-2026-41569.md new file mode 100644 index 0000000000..b4cd1e242b --- /dev/null +++ b/website/docs/security/cves/CVE-2026-41569.md @@ -0,0 +1,33 @@ +# CVE-2026-41569 + +_Reported by [@jmecom](https://github.com/jmecom) and [@AyushParkara](https://github.com/AyushParkara)_ + +## WS-Federation wreply Origin Bypass (CVE-2026-41569) + +### Summary + +The WS-Federation provider validates the user-supplied `wreply` parameter using a raw string prefix check rather than proper URL parsing. An attacker who can craft a login link can supply a `wreply` value on a different origin that passes the check (e.g. `https://portal.example.com.evil.tld/`), causing the victim's browser to POST the signed WS-Federation login response to attacker-controlled infrastructure. + +### Patches + +authentik 2025.12.5 and 2026.2.3 fix this issue. + +### Impact + +The WS-Federation sign-in processor accepted any `wreply` whose string value started with the configured Reply URL, not correctly comparing the domain. + +Once accepted, the attacker-controlled `wreply` is used as the autosubmit destination, and the victim's browser immediately POSTs the signed WS-Federation response (`wresult`) to that URL. The response is a valid signed authentication artifact; in many relying-party configurations it is replayable to the legitimate ACS endpoint, enabling victim impersonation in the target application. + +The fix replaces the string prefix check with proper URL parsing, comparing scheme, host, and path independently: + +Only WS-Federation providers (an enterprise feature) with a prefix-ambiguous Reply URL are affected. If the Reply URL is already path-specific (e.g. `https://portal.example.com/wsfed/acs`), the host-extension bypass does not apply. + +### Workarounds + +Configure the WS-Federation provider's Reply URL with a specific path (e.g. `https://portal.example.com/wsfed/acs`) rather than a bare hostname. This prevents the host-extension bypass without patching, though upgrading is strongly preferred. + +### For more information + +If you have any questions or comments about this advisory: + +- Email us at [security@goauthentik.io](mailto:security@goauthentik.io)